"""Filesystem Integration Tests"""
import pytest
from secafs_sdk import ErrnoException, Filesystem
@pytest.mark.asyncio
class TestFilesystemWriteOperations:
"""Filesystem write operations"""
async def test_write_and_read_simple_text_file(self, db):
"""Should write and read a simple text file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/test.txt", "Hello, World!")
content = await fs.read_file("/test.txt")
assert content == "Hello, World!"
async def test_write_files_in_subdirectories(self, db):
"""Should write and read files in subdirectories"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/subdir/file.txt", "nested content")
content = await fs.read_file("/dir/subdir/file.txt")
assert content == "nested content"
async def test_overwrite_existing_file(self, db):
"""Should overwrite existing file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/overwrite.txt", "original content")
await fs.write_file("/overwrite.txt", "new content")
content = await fs.read_file("/overwrite.txt")
assert content == "new content"
async def test_handle_empty_file_content(self, db):
"""Should handle empty file content"""
fs = await Filesystem.from_database(db)
await fs.write_file("/empty.txt", "")
content = await fs.read_file("/empty.txt")
assert content == ""
async def test_handle_large_file_content(self, db):
"""Should handle large file content"""
fs = await Filesystem.from_database(db)
large_content = "x" * 100000
await fs.write_file("/large.txt", large_content)
content = await fs.read_file("/large.txt")
assert content == large_content
async def test_special_characters_in_content(self, db):
"""Should handle files with special characters in content"""
fs = await Filesystem.from_database(db)
special_content = "Special chars: \n\t\r\"'\\"
await fs.write_file("/special.txt", special_content)
content = await fs.read_file("/special.txt")
assert content == special_content
@pytest.mark.asyncio
class TestFilesystemReadOperations:
"""Filesystem read operations"""
async def test_error_reading_nonexistent_file(self, db):
"""Should throw error when reading non-existent file"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException):
await fs.read_file("/non-existent.txt")
async def test_read_multiple_files(self, db):
"""Should read multiple different files"""
fs = await Filesystem.from_database(db)
await fs.write_file("/file1.txt", "content 1")
await fs.write_file("/file2.txt", "content 2")
await fs.write_file("/file3.txt", "content 3")
assert await fs.read_file("/file1.txt") == "content 1"
assert await fs.read_file("/file2.txt") == "content 2"
assert await fs.read_file("/file3.txt") == "content 3"
@pytest.mark.asyncio
class TestFilesystemDirectoryOperations:
"""Filesystem directory operations"""
async def test_list_files_in_root(self, db):
"""Should list files in root directory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/file1.txt", "content 1")
await fs.write_file("/file2.txt", "content 2")
await fs.write_file("/file3.txt", "content 3")
files = await fs.readdir("/")
assert "file1.txt" in files
assert "file2.txt" in files
assert "file3.txt" in files
assert len(files) == 3
async def test_list_files_in_subdirectory(self, db):
"""Should list files in subdirectory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file1.txt", "content 1")
await fs.write_file("/dir/file2.txt", "content 2")
await fs.write_file("/other/file3.txt", "content 3")
files = await fs.readdir("/dir")
assert "file1.txt" in files
assert "file2.txt" in files
assert "file3.txt" not in files
assert len(files) == 2
async def test_distinguish_files_in_different_directories(self, db):
"""Should distinguish between files in different directories"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir1/file.txt", "content 1")
await fs.write_file("/dir2/file.txt", "content 2")
files1 = await fs.readdir("/dir1")
files2 = await fs.readdir("/dir2")
assert "file.txt" in files1
assert "file.txt" in files2
assert len(files1) == 1
assert len(files2) == 1
async def test_list_subdirectories(self, db):
"""Should list subdirectories within a directory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/parent/child1/file.txt", "content")
await fs.write_file("/parent/child2/file.txt", "content")
await fs.write_file("/parent/file.txt", "content")
entries = await fs.readdir("/parent")
assert "file.txt" in entries
assert "child1" in entries
assert "child2" in entries
async def test_nested_directory_structures(self, db):
"""Should handle nested directory structures"""
fs = await Filesystem.from_database(db)
await fs.write_file("/a/b/c/d/file.txt", "deep content")
files = await fs.readdir("/a/b/c/d")
assert "file.txt" in files
@pytest.mark.asyncio
class TestFilesystemDeleteOperations:
"""Filesystem delete operations"""
async def test_delete_existing_file(self, db):
"""Should delete an existing file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/delete-me.txt", "content")
await fs.delete_file("/delete-me.txt")
with pytest.raises(ErrnoException):
await fs.read_file("/delete-me.txt")
async def test_delete_nonexistent_file(self, db):
"""Should handle deleting non-existent file"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.delete_file("/non-existent.txt")
async def test_delete_and_update_directory_listing(self, db):
"""Should delete file and update directory listing"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file1.txt", "content 1")
await fs.write_file("/dir/file2.txt", "content 2")
await fs.delete_file("/dir/file1.txt")
files = await fs.readdir("/dir")
assert "file1.txt" not in files
assert "file2.txt" in files
assert len(files) == 1
async def test_recreate_deleted_file(self, db):
"""Should allow recreating deleted file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/recreate.txt", "original")
await fs.delete_file("/recreate.txt")
await fs.write_file("/recreate.txt", "new content")
content = await fs.read_file("/recreate.txt")
assert content == "new content"
@pytest.mark.asyncio
class TestFilesystemPathHandling:
"""Filesystem path handling"""
async def test_paths_with_trailing_slashes(self, db):
"""Should handle paths with trailing slashes"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file.txt", "content")
files1 = await fs.readdir("/dir")
files2 = await fs.readdir("/dir/")
assert files1 == files2
async def test_paths_with_special_characters(self, db):
"""Should handle paths with special characters"""
fs = await Filesystem.from_database(db)
special_path = "/dir-with-dash/file_with_underscore.txt"
await fs.write_file(special_path, "content")
content = await fs.read_file(special_path)
assert content == "content"
@pytest.mark.asyncio
class TestFilesystemIntegrity:
"""Filesystem integrity tests"""
async def test_maintain_file_hierarchy_integrity(self, db):
"""Should maintain file hierarchy integrity"""
fs = await Filesystem.from_database(db)
await fs.write_file("/root.txt", "root")
await fs.write_file("/dir1/file.txt", "dir1")
await fs.write_file("/dir2/file.txt", "dir2")
await fs.write_file("/dir1/subdir/file.txt", "subdir")
assert await fs.read_file("/root.txt") == "root"
assert await fs.read_file("/dir1/file.txt") == "dir1"
assert await fs.read_file("/dir2/file.txt") == "dir2"
assert await fs.read_file("/dir1/subdir/file.txt") == "subdir"
root_files = await fs.readdir("/")
assert "root.txt" in root_files
assert "dir1" in root_files
assert "dir2" in root_files
async def test_multiple_files_same_name_different_directories(self, db):
"""Should support multiple files with same name in different directories"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir1/config.json", '{"version": 1}')
await fs.write_file("/dir2/config.json", '{"version": 2}')
assert await fs.read_file("/dir1/config.json") == '{"version": 1}'
assert await fs.read_file("/dir2/config.json") == '{"version": 2}'
@pytest.mark.asyncio
class TestFilesystemStandaloneUsage:
"""Filesystem standalone usage tests"""
async def test_basic_standalone_usage(self, db):
"""Should work as a standalone filesystem"""
fs = await Filesystem.from_database(db)
await fs.write_file("/test.txt", "standalone content")
content = await fs.read_file("/test.txt")
assert content == "standalone content"
@pytest.mark.skip(
reason="exercised SQLite-style independent in-memory databases; the "
"PostgreSQL backend shares a single connection per test, so two "
"independent stores cannot be created within one fixture"
)
async def test_maintain_isolation_between_instances(self, db):
"""Should maintain isolation between instances"""
@pytest.mark.asyncio
class TestFilesystemPersistence:
"""Filesystem persistence tests"""
async def test_persist_across_instances(self, db):
"""Should persist data across Filesystem instances"""
fs = await Filesystem.from_database(db)
await fs.write_file("/persist.txt", "persistent content")
new_fs = await Filesystem.from_database(db)
content = await new_fs.read_file("/persist.txt")
assert content == "persistent content"
@pytest.mark.asyncio
class TestFilesystemChunkSize:
"""Filesystem chunk size tests"""
async def test_default_chunk_size(self, db):
"""Should have default chunk size of 4096"""
fs = await Filesystem.from_database(db)
assert fs.get_chunk_size() == 4096
async def test_write_file_smaller_than_chunk_size(self, db):
"""Should write file smaller than chunk size"""
fs = await Filesystem.from_database(db)
data = "x" * 100
await fs.write_file("/small.txt", data)
read_data = await fs.read_file("/small.txt")
assert len(read_data) == 100
assert read_data == data
async def test_write_file_exact_chunk_size(self, db):
"""Should write file exactly chunk size"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
data = bytes(i % 256 for i in range(chunk_size))
await fs.write_file("/exact.txt", data)
read_data = await fs.read_file("/exact.txt", encoding=None)
assert len(read_data) == chunk_size
async def test_write_file_over_chunk_size(self, db):
"""Should write file one byte over chunk size"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
data = bytes(i % 256 for i in range(chunk_size + 1))
await fs.write_file("/overflow.txt", data)
read_data = await fs.read_file("/overflow.txt", encoding=None)
assert len(read_data) == chunk_size + 1
async def test_write_file_spanning_multiple_chunks(self, db):
"""Should write file spanning multiple chunks"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
data_size = int(chunk_size * 2.5)
data = bytes(i % 256 for i in range(data_size))
await fs.write_file("/multi.txt", data)
read_data = await fs.read_file("/multi.txt", encoding=None)
assert len(read_data) == data_size
@pytest.mark.asyncio
class TestFilesystemDataIntegrity:
"""Filesystem data integrity tests"""
async def test_roundtrip_data_byte_for_byte(self, db):
"""Should roundtrip data byte-for-byte"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
data_size = chunk_size * 3 + 123
data = bytes(i % 256 for i in range(data_size))
await fs.write_file("/roundtrip.bin", data)
read_data = await fs.read_file("/roundtrip.bin", encoding=None)
assert len(read_data) == data_size
assert read_data == data
async def test_handle_binary_data_with_null_bytes(self, db):
"""Should handle binary data with null bytes"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
data = bytearray(chunk_size * 2 + 100)
data[chunk_size - 1] = 0
data[chunk_size] = 0
data[chunk_size + 1] = 0
data[chunk_size - 2] = 0xFF
data[chunk_size + 2] = 0xFF
await fs.write_file("/nulls.bin", bytes(data))
read_data = await fs.read_file("/nulls.bin", encoding=None)
assert read_data[chunk_size - 2] == 0xFF
assert read_data[chunk_size - 1] == 0
assert read_data[chunk_size] == 0
assert read_data[chunk_size + 1] == 0
assert read_data[chunk_size + 2] == 0xFF
async def test_preserve_chunk_ordering(self, db):
"""Should preserve chunk ordering"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
data_size = chunk_size * 5
data = bytes(i % 256 for i in range(data_size))
await fs.write_file("/sequential.bin", data)
read_data = await fs.read_file("/sequential.bin", encoding=None)
for i in range(data_size):
assert read_data[i] == i % 256
@pytest.mark.asyncio
class TestFilesystemEdgeCases:
"""Filesystem edge case tests"""
async def test_empty_file_with_zero_chunks(self, db):
"""Should handle empty file with zero chunks"""
fs = await Filesystem.from_database(db)
await fs.write_file("/empty.txt", "")
read_data = await fs.read_file("/empty.txt")
assert read_data == ""
stats = await fs.stat("/empty.txt")
assert stats.size == 0
async def test_overwrite_large_file_with_smaller(self, db):
"""Should overwrite large file with smaller file and clean up chunks"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
initial_data = bytes(i % 256 for i in range(chunk_size * 3))
await fs.write_file("/overwrite.txt", initial_data)
new_data = "x" * 100
await fs.write_file("/overwrite.txt", new_data)
read_data = await fs.read_file("/overwrite.txt")
assert read_data == new_data
stats = await fs.stat("/overwrite.txt")
assert stats.size == 100
async def test_overwrite_small_file_with_larger(self, db):
"""Should overwrite small file with larger file"""
fs = await Filesystem.from_database(db)
chunk_size = fs.get_chunk_size()
initial_data = "x" * 100
await fs.write_file("/grow.txt", initial_data)
new_data = bytes(i % 256 for i in range(chunk_size * 3))
await fs.write_file("/grow.txt", new_data)
read_data = await fs.read_file("/grow.txt", encoding=None)
assert len(read_data) == chunk_size * 3
async def test_very_large_file(self, db):
"""Should handle very large file (1MB)"""
fs = await Filesystem.from_database(db)
data_size = 1024 * 1024
data = bytes(i % 256 for i in range(data_size))
await fs.write_file("/large.bin", data)
read_data = await fs.read_file("/large.bin", encoding=None)
assert len(read_data) == data_size
@pytest.mark.asyncio
class TestFilesystemStats:
"""Filesystem stats tests"""
async def test_stat_file(self, db):
"""Should get file statistics"""
fs = await Filesystem.from_database(db)
content = "Hello, World!"
await fs.write_file("/test.txt", content)
stats = await fs.stat("/test.txt")
assert stats.is_file()
assert not stats.is_directory()
assert stats.size == len(content)
assert stats.ino > 0
async def test_stat_directory(self, db):
"""Should get directory statistics"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file.txt", "content")
stats = await fs.stat("/dir")
assert stats.is_directory()
assert not stats.is_file()
async def test_stat_nonexistent_path(self, db):
"""Should throw error for non-existent path"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.stat("/nonexistent")
@pytest.mark.asyncio
class TestFilesystemMkdir:
"""Tests for mkdir() operation"""
async def test_create_directory(self, db):
"""Should create a directory with mkdir()"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/newdir")
entries = await fs.readdir("/")
assert "newdir" in entries
async def test_mkdir_throws_eexist_for_existing_directory(self, db):
"""Should throw EEXIST when mkdir() is called on an existing directory"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/exists")
with pytest.raises(ErrnoException, match="EEXIST"):
await fs.mkdir("/exists")
async def test_mkdir_throws_enoent_for_missing_parent(self, db):
"""Should throw ENOENT when parent directory does not exist"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.mkdir("/missing-parent/child")
@pytest.mark.asyncio
class TestFilesystemRm:
"""Tests for rm() operation"""
async def test_remove_file(self, db):
"""Should remove a file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/rmfile.txt", "content")
await fs.rm("/rmfile.txt")
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.read_file("/rmfile.txt")
async def test_rm_force_does_not_throw_for_missing_file(self, db):
"""Should not throw when force=True and path does not exist"""
fs = await Filesystem.from_database(db)
await fs.rm("/does-not-exist", force=True)
async def test_rm_throws_enoent_without_force(self, db):
"""Should throw ENOENT when force=False and path does not exist"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.rm("/does-not-exist")
async def test_rm_throws_eisdir_for_directory_without_recursive(self, db):
"""Should throw EISDIR when trying to rm a directory without recursive"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/rmdir")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.rm("/rmdir")
async def test_rm_recursive_removes_directory_tree(self, db):
"""Should remove a directory recursively"""
fs = await Filesystem.from_database(db)
await fs.write_file("/tree/a/b/c.txt", "content")
await fs.rm("/tree", recursive=True)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.readdir("/tree")
root = await fs.readdir("/")
assert "tree" not in root
@pytest.mark.asyncio
class TestFilesystemRmdir:
"""Tests for rmdir() operation"""
async def test_remove_empty_directory(self, db):
"""Should remove an empty directory"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/emptydir")
await fs.rmdir("/emptydir")
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.readdir("/emptydir")
root = await fs.readdir("/")
assert "emptydir" not in root
async def test_rmdir_throws_enotempty_for_non_empty_directory(self, db):
"""Should throw ENOTEMPTY when directory is not empty"""
fs = await Filesystem.from_database(db)
await fs.write_file("/nonempty/file.txt", "content")
with pytest.raises(ErrnoException, match="ENOTEMPTY"):
await fs.rmdir("/nonempty")
async def test_rmdir_throws_enotdir_for_file(self, db):
"""Should throw ENOTDIR when path is a file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/afile", "content")
with pytest.raises(ErrnoException, match="ENOTDIR"):
await fs.rmdir("/afile")
async def test_rmdir_throws_eperm_for_root(self, db):
"""Should throw EPERM when attempting to remove root"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="EPERM"):
await fs.rmdir("/")
@pytest.mark.asyncio
class TestFilesystemRename:
"""Tests for rename() operation"""
async def test_rename_file(self, db):
"""Should rename a file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/a.txt", "hello")
await fs.rename("/a.txt", "/b.txt")
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.read_file("/a.txt")
content = await fs.read_file("/b.txt", "utf-8")
assert content == "hello"
async def test_rename_directory_preserves_contents(self, db):
"""Should rename a directory and preserve its contents"""
fs = await Filesystem.from_database(db)
await fs.write_file("/olddir/sub/file.txt", "content")
await fs.rename("/olddir", "/newdir")
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.readdir("/olddir")
content = await fs.read_file("/newdir/sub/file.txt", "utf-8")
assert content == "content"
async def test_rename_overwrites_destination_file(self, db):
"""Should overwrite destination file if it exists"""
fs = await Filesystem.from_database(db)
await fs.write_file("/src.txt", "src")
await fs.write_file("/dst.txt", "dst")
await fs.rename("/src.txt", "/dst.txt")
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.read_file("/src.txt")
content = await fs.read_file("/dst.txt", "utf-8")
assert content == "src"
async def test_rename_throws_eisdir_for_file_to_directory(self, db):
"""Should throw EISDIR when renaming a file onto a directory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file.txt", "content")
await fs.write_file("/file.txt", "content")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.rename("/file.txt", "/dir")
async def test_rename_throws_enotdir_for_directory_to_file(self, db):
"""Should throw ENOTDIR when renaming a directory onto a file"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/somedir")
await fs.write_file("/somefile", "content")
with pytest.raises(ErrnoException, match="ENOTDIR"):
await fs.rename("/somedir", "/somefile")
async def test_rename_replaces_empty_directory(self, db):
"""Should replace an existing empty directory"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/fromdir")
await fs.mkdir("/todir")
await fs.rename("/fromdir", "/todir")
root = await fs.readdir("/")
assert "todir" in root
assert "fromdir" not in root
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.readdir("/fromdir")
async def test_rename_throws_enotempty_for_non_empty_destination(self, db):
"""Should throw ENOTEMPTY when replacing a non-empty directory"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/fromdir")
await fs.write_file("/todir/file.txt", "content")
with pytest.raises(ErrnoException, match="ENOTEMPTY"):
await fs.rename("/fromdir", "/todir")
async def test_rename_throws_eperm_for_root(self, db):
"""Should throw EPERM when attempting to rename root"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="EPERM"):
await fs.rename("/", "/x")
async def test_rename_throws_einval_for_directory_into_subdirectory(self, db):
"""Should throw EINVAL when renaming a directory into its own subdirectory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/cycle/sub/file.txt", "content")
with pytest.raises(ErrnoException, match="EINVAL"):
await fs.rename("/cycle", "/cycle/sub/moved")
@pytest.mark.asyncio
class TestFilesystemCopyFile:
"""Tests for copy_file() operation"""
async def test_copy_file(self, db):
"""Should copy a file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/src.txt", "hello")
await fs.copy_file("/src.txt", "/dst.txt")
src_content = await fs.read_file("/src.txt", "utf-8")
dst_content = await fs.read_file("/dst.txt", "utf-8")
assert src_content == "hello"
assert dst_content == "hello"
async def test_copy_file_overwrites_destination(self, db):
"""Should overwrite destination if it exists"""
fs = await Filesystem.from_database(db)
await fs.write_file("/src.txt", "src")
await fs.write_file("/dst.txt", "dst")
await fs.copy_file("/src.txt", "/dst.txt")
dst_content = await fs.read_file("/dst.txt", "utf-8")
assert dst_content == "src"
async def test_copy_file_throws_enoent_for_missing_source(self, db):
"""Should throw ENOENT when source does not exist"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.copy_file("/nope.txt", "/out.txt")
async def test_copy_file_throws_enoent_for_missing_destination_parent(self, db):
"""Should throw ENOENT when destination parent does not exist"""
fs = await Filesystem.from_database(db)
await fs.write_file("/src3.txt", "content")
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.copy_file("/src3.txt", "/missing/child.txt")
async def test_copy_file_throws_eisdir_for_directory_source(self, db):
"""Should throw EISDIR when source is a directory"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/asrcdir")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.copy_file("/asrcdir", "/out2.txt")
async def test_copy_file_throws_eisdir_for_directory_destination(self, db):
"""Should throw EISDIR when destination is a directory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/src4.txt", "content")
await fs.mkdir("/adstdir")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.copy_file("/src4.txt", "/adstdir")
async def test_copy_file_throws_einval_for_same_source_and_destination(self, db):
"""Should throw EINVAL when source and destination are the same"""
fs = await Filesystem.from_database(db)
await fs.write_file("/same.txt", "content")
with pytest.raises(ErrnoException, match="EINVAL"):
await fs.copy_file("/same.txt", "/same.txt")
@pytest.mark.asyncio
class TestFilesystemAccess:
"""Tests for access() operation"""
async def test_access_existing_file(self, db):
"""Should resolve when a file exists"""
fs = await Filesystem.from_database(db)
await fs.write_file("/exists.txt", "content")
await fs.access("/exists.txt")
async def test_access_existing_directory(self, db):
"""Should resolve when a directory exists"""
fs = await Filesystem.from_database(db)
await fs.mkdir("/existsdir")
await fs.access("/existsdir")
async def test_access_throws_enoent_for_nonexistent_path(self, db):
"""Should throw ENOENT when path does not exist"""
fs = await Filesystem.from_database(db)
with pytest.raises(ErrnoException, match="ENOENT"):
await fs.access("/does-not-exist")
@pytest.mark.asyncio
class TestFilesystemErrorCodes:
"""Tests for error code validation on existing methods"""
async def test_write_file_throws_eisdir_for_directory(self, db):
"""Should throw EISDIR when attempting to write to a directory path"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file.txt", "content")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.write_file("/dir", "nope")
async def test_write_file_throws_enotdir_for_file_in_path(self, db):
"""Should throw ENOTDIR when a parent path component is a file"""
fs = await Filesystem.from_database(db)
await fs.write_file("/a", "file-content")
with pytest.raises(ErrnoException, match="ENOTDIR"):
await fs.write_file("/a/b.txt", "child")
async def test_read_file_throws_eisdir_for_directory(self, db):
"""Should throw EISDIR when attempting to read a directory path"""
fs = await Filesystem.from_database(db)
await fs.write_file("/dir/file.txt", "content")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.read_file("/dir")
async def test_readdir_throws_enotdir_for_file(self, db):
"""Should throw ENOTDIR when attempting to readdir a file path"""
fs = await Filesystem.from_database(db)
await fs.write_file("/notadir.txt", "content")
with pytest.raises(ErrnoException, match="ENOTDIR"):
await fs.readdir("/notadir.txt")
async def test_unlink_throws_eisdir_for_directory(self, db):
"""Should throw EISDIR when attempting to unlink a directory"""
fs = await Filesystem.from_database(db)
await fs.write_file("/adir/file.txt", "content")
with pytest.raises(ErrnoException, match="EISDIR"):
await fs.unlink("/adir")