packages feed

atelier-core-0.1.0.0: test/Unit/Atelier/Effects/FileSystemSpec.hs

module Unit.Atelier.Effects.FileSystemSpec (spec_FileSystem) where

import Control.Exception (evaluate)
import Effectful (runPureEff)
import Effectful.State.Static.Shared (State, evalState)
import Test.Hspec (Spec, anyIOException, describe, it, shouldBe, shouldThrow)

import Data.ByteString.Lazy qualified as LBS
import Data.Map.Strict qualified as M

import Atelier.Effects.FileSystem


spec_FileSystem :: Spec
spec_FileSystem = describe "runFileSystemState" testFileSystemState


testFileSystemState :: Spec
testFileSystemState = do
    describe "readFileBs" do
        it "reads bytes of a file present in the state" do
            run (M.singleton "/a" "hello") (readFileBs "/a")
                `shouldBe` "hello"

        it "errors when the file is absent" do
            evaluate (run M.empty (readFileBs "/missing"))
                `shouldThrow` anyIOException

    describe "readFileLbs" do
        it "reads the entire file as lazy bytes" do
            run (M.singleton "/a" "hello") (readFileLbs "/a")
                `shouldBe` ("hello" :: LBS.ByteString)

        it "errors when the file is absent" do
            evaluate (run M.empty (readFileLbs "/missing"))
                `shouldThrow` anyIOException

    describe "readFileLbsFrom" do
        it "returns full content when offset is 0" do
            run (M.singleton "/a" "hello") (readFileLbsFrom "/a" 0)
                `shouldBe` ("hello" :: LBS.ByteString)

        it "skips the leading bytes up to the given offset" do
            run (M.singleton "/a" "hello") (readFileLbsFrom "/a" 2)
                `shouldBe` ("llo" :: LBS.ByteString)

        it "returns empty when offset equals the file length" do
            run (M.singleton "/a" "hello") (readFileLbsFrom "/a" 5)
                `shouldBe` ("" :: LBS.ByteString)

        it "errors when the file is absent" do
            evaluate (run M.empty (readFileLbsFrom "/missing" 0))
                `shouldThrow` anyIOException

    describe "doesFileExist" do
        it "returns True for a key present in the state" do
            run (M.singleton "/a" "") (doesFileExist "/a")
                `shouldBe` True

        it "returns False when the key is absent" do
            run M.empty (doesFileExist "/a")
                `shouldBe` False

    describe "doesPathExist" do
        it "returns True when a key with the path as a string prefix exists" do
            run (M.singleton "/dir/file" "") (doesPathExist "/dir")
                `shouldBe` True

        it "returns False when no key shares the prefix" do
            run M.empty (doesPathExist "/dir")
                `shouldBe` False

        it "returns False for an exact-match key with no children" do
            run (M.singleton "/dir" "") (doesPathExist "/dir")
                `shouldBe` False

    describe "listDirectory" do
        it "returns all keys that start with the given path" do
            let fs = M.fromList [("/dir/a", ""), ("/dir/b", ""), ("/other/c", "")]
            sort (run fs (listDirectory "/dir"))
                `shouldBe` ["/dir/a", "/dir/b"]

        it "returns empty list when no keys share the prefix" do
            run M.empty (listDirectory "/dir")
                `shouldBe` []

        it "excludes an exact-match key" do
            let fs = M.fromList [("/dir", ""), ("/dir/a", "")]
            run fs (listDirectory "/dir")
                `shouldBe` ["/dir/a"]

    describe "removeFile" do
        it "removes the file from the state" do
            let result = run (M.singleton "/a" "x") do
                    removeFile "/a"
                    doesFileExist "/a"
            result `shouldBe` False

        it "leaves other files unaffected" do
            let result = run (M.fromList [("/a", "x"), ("/b", "y")]) do
                    removeFile "/a"
                    doesFileExist "/b"
            result `shouldBe` True

        it "is a no-op when the file is absent" do
            let result = run M.empty do
                    removeFile "/missing"
                    doesFileExist "/missing"
            result `shouldBe` False

    describe "createDirectoryIfMissing" do
        it "is a no-op — does not add any entries to the state" do
            let result = run M.empty do
                    createDirectoryIfMissing True "/new/dir"
                    doesPathExist "/new/dir"
            result `shouldBe` False

    describe "canonicalizePath" do
        it "returns the path unchanged" do
            run M.empty (canonicalizePath "/some/./path")
                `shouldBe` "/some/./path"

    describe "getCurrentDirectory" do
        it "returns /" do
            run M.empty getCurrentDirectory
                `shouldBe` "/"

    describe "getXdgRuntimeDir" do
        it "returns /tmp" do
            run M.empty getXdgRuntimeDir
                `shouldBe` "/tmp"


run
    :: Map FilePath ByteString
    -> Eff '[FileSystem, State (Map FilePath ByteString)] a
    -> a
run fs = runPureEff . evalState fs . runFileSystemState