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