kolega-code 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kolega_code/__init__.py +151 -0
- kolega_code/agent/__init__.py +42 -0
- kolega_code/agent/baseagent.py +998 -0
- kolega_code/agent/browseragent.py +123 -0
- kolega_code/agent/coder.py +157 -0
- kolega_code/agent/common.py +41 -0
- kolega_code/agent/compression.py +81 -0
- kolega_code/agent/context.py +112 -0
- kolega_code/agent/conversation.py +408 -0
- kolega_code/agent/generalagent.py +146 -0
- kolega_code/agent/investigationagent.py +123 -0
- kolega_code/agent/planningagent.py +187 -0
- kolega_code/agent/prompt_provider.py +196 -0
- kolega_code/agent/prompt_templates/agents/browser.j2 +102 -0
- kolega_code/agent/prompt_templates/agents/coder_cli_mode.j2 +127 -0
- kolega_code/agent/prompt_templates/agents/general.j2 +68 -0
- kolega_code/agent/prompt_templates/agents/investigation.j2 +72 -0
- kolega_code/agent/prompt_templates/common/frontend_guidance.md +36 -0
- kolega_code/agent/prompt_templates/common/kolega_md_instructions.md +14 -0
- kolega_code/agent/prompt_templates/environment_variables/workspace_env_vars.md +11 -0
- kolega_code/agent/prompt_templates/template_guidance/expo-template.md +379 -0
- kolega_code/agent/prompt_templates/template_guidance/html-website-template.md +3 -0
- kolega_code/agent/prompt_templates/template_guidance/mern-stack-template.md +3 -0
- kolega_code/agent/prompt_templates/template_guidance/react-vite-shadcdn-template.md +182 -0
- kolega_code/agent/prompts.py +192 -0
- kolega_code/agent/tests/__init__.py +0 -0
- kolega_code/agent/tests/llm/__init__.py +0 -0
- kolega_code/agent/tests/llm/test_anthropic_token_counting.py +633 -0
- kolega_code/agent/tests/llm/test_billing_openai_cache.py +74 -0
- kolega_code/agent/tests/llm/test_client.py +773 -0
- kolega_code/agent/tests/llm/test_dashscope_mapping.py +32 -0
- kolega_code/agent/tests/llm/test_error_boundary.py +322 -0
- kolega_code/agent/tests/llm/test_exceptions.py +249 -0
- kolega_code/agent/tests/llm/test_instrumented_client.py +536 -0
- kolega_code/agent/tests/llm/test_instrumented_client_integration.py +547 -0
- kolega_code/agent/tests/llm/test_langfuse_normalization.py +39 -0
- kolega_code/agent/tests/llm/test_model_specs.py +17 -0
- kolega_code/agent/tests/llm/test_openai_cached_tokens.py +58 -0
- kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +74 -0
- kolega_code/agent/tests/llm/test_openai_message_conversion.py +30 -0
- kolega_code/agent/tests/llm/test_openai_token_counting.py +687 -0
- kolega_code/agent/tests/llm/test_tool_execution_ids.py +193 -0
- kolega_code/agent/tests/services/__init__.py +1 -0
- kolega_code/agent/tests/services/test_browser.py +447 -0
- kolega_code/agent/tests/services/test_browser_parity.py +353 -0
- kolega_code/agent/tests/services/test_file_system.py +699 -0
- kolega_code/agent/tests/services/test_sandbox_terminal_input.py +98 -0
- kolega_code/agent/tests/services/test_terminal.py +154 -0
- kolega_code/agent/tests/services/test_terminal_command_tracking.py +385 -0
- kolega_code/agent/tests/services/test_terminal_state_serializer.py +262 -0
- kolega_code/agent/tests/test_agent_tools_inventory.py +267 -0
- kolega_code/agent/tests/test_base_agent.py +1942 -0
- kolega_code/agent/tests/test_coder_attachments.py +330 -0
- kolega_code/agent/tests/test_coder_prompt_extensions.py +61 -0
- kolega_code/agent/tests/test_commands.py +179 -0
- kolega_code/agent/tests/test_duplicate_tool_results.py +556 -0
- kolega_code/agent/tests/test_empty_message_handling.py +48 -0
- kolega_code/agent/tests/test_general_agent.py +242 -0
- kolega_code/agent/tests/test_html.py +320 -0
- kolega_code/agent/tests/test_parallel_tool_calls.py +291 -0
- kolega_code/agent/tests/test_planning_agent.py +227 -0
- kolega_code/agent/tests/test_prompt_provider.py +271 -0
- kolega_code/agent/tests/test_tool_registry.py +102 -0
- kolega_code/agent/tests/test_tools.py +549 -0
- kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- kolega_code/agent/tests/tool_backend/test_agent_tool.py +356 -0
- kolega_code/agent/tests/tool_backend/test_base_tool.py +147 -0
- kolega_code/agent/tests/tool_backend/test_browser_tool.py +335 -0
- kolega_code/agent/tests/tool_backend/test_build_tool.py +93 -0
- kolega_code/agent/tests/tool_backend/test_create_file_tool.py +115 -0
- kolega_code/agent/tests/tool_backend/test_glob_tool.py +196 -0
- kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +230 -0
- kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +292 -0
- kolega_code/agent/tests/tool_backend/test_read_file_tool.py +173 -0
- kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +115 -0
- kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +141 -0
- kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +174 -0
- kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +228 -0
- kolega_code/agent/tests/tool_backend/test_terminal_tool.py +482 -0
- kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +189 -0
- kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +445 -0
- kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +194 -0
- kolega_code/agent/tool_backend/agent_tool.py +414 -0
- kolega_code/agent/tool_backend/apply_edit_tool.py +98 -0
- kolega_code/agent/tool_backend/apply_patch_tool.py +514 -0
- kolega_code/agent/tool_backend/base_tool.py +217 -0
- kolega_code/agent/tool_backend/browser_tool.py +271 -0
- kolega_code/agent/tool_backend/build_tool.py +93 -0
- kolega_code/agent/tool_backend/create_file_tool.py +52 -0
- kolega_code/agent/tool_backend/glob_tool.py +323 -0
- kolega_code/agent/tool_backend/list_directory_tool.py +300 -0
- kolega_code/agent/tool_backend/memory_tool.py +79 -0
- kolega_code/agent/tool_backend/read_file_tool.py +119 -0
- kolega_code/agent/tool_backend/replace_entire_file_tool.py +40 -0
- kolega_code/agent/tool_backend/replace_lines_tool.py +97 -0
- kolega_code/agent/tool_backend/search_and_replace_tool.py +146 -0
- kolega_code/agent/tool_backend/search_codebase_tool.py +377 -0
- kolega_code/agent/tool_backend/streaming_tool.py +47 -0
- kolega_code/agent/tool_backend/terminal_tool.py +643 -0
- kolega_code/agent/tool_backend/think_hard_tool.py +211 -0
- kolega_code/agent/tool_backend/web_fetch_tool.py +205 -0
- kolega_code/agent/tools.py +1704 -0
- kolega_code/agent/utils/commands.py +94 -0
- kolega_code/cli/__init__.py +1 -0
- kolega_code/cli/app.py +2756 -0
- kolega_code/cli/config.py +280 -0
- kolega_code/cli/connection.py +49 -0
- kolega_code/cli/file_index.py +147 -0
- kolega_code/cli/main.py +564 -0
- kolega_code/cli/mentions.py +155 -0
- kolega_code/cli/messages.py +89 -0
- kolega_code/cli/provider_registry.py +96 -0
- kolega_code/cli/session_store.py +207 -0
- kolega_code/cli/settings.py +87 -0
- kolega_code/cli/skills.py +409 -0
- kolega_code/cli/slash_commands.py +108 -0
- kolega_code/cli/tests/__init__.py +1 -0
- kolega_code/cli/tests/test_app.py +4251 -0
- kolega_code/cli/tests/test_cli_config.py +171 -0
- kolega_code/cli/tests/test_connection.py +26 -0
- kolega_code/cli/tests/test_file_index.py +103 -0
- kolega_code/cli/tests/test_main.py +455 -0
- kolega_code/cli/tests/test_mentions.py +108 -0
- kolega_code/cli/tests/test_session_store.py +67 -0
- kolega_code/cli/tests/test_settings.py +62 -0
- kolega_code/cli/tests/test_skills.py +157 -0
- kolega_code/cli/tests/test_slash_commands.py +88 -0
- kolega_code/cli/theme.py +180 -0
- kolega_code/config.py +154 -0
- kolega_code/events.py +202 -0
- kolega_code/llm/client.py +300 -0
- kolega_code/llm/exceptions.py +285 -0
- kolega_code/llm/instrumented_client.py +520 -0
- kolega_code/llm/models.py +1368 -0
- kolega_code/llm/providers/__init__.py +0 -0
- kolega_code/llm/providers/anthropic.py +387 -0
- kolega_code/llm/providers/base.py +71 -0
- kolega_code/llm/providers/google.py +157 -0
- kolega_code/llm/providers/models.py +37 -0
- kolega_code/llm/providers/openai.py +363 -0
- kolega_code/llm/ratelimit.py +40 -0
- kolega_code/llm/specs.py +67 -0
- kolega_code/llm/tool_execution_ids.py +18 -0
- kolega_code/models/__init__.py +9 -0
- kolega_code/models/sandbox_terminal_state.py +47 -0
- kolega_code/runtime.py +50 -0
- kolega_code/sandbox/README.md +200 -0
- kolega_code/sandbox/__init__.py +21 -0
- kolega_code/sandbox/async_filesystem.py +475 -0
- kolega_code/sandbox/base.py +297 -0
- kolega_code/sandbox/browser.py +25 -0
- kolega_code/sandbox/event_loop.py +43 -0
- kolega_code/sandbox/filesystem.py +341 -0
- kolega_code/sandbox/local.py +118 -0
- kolega_code/sandbox/serializer.py +175 -0
- kolega_code/sandbox/terminal.py +868 -0
- kolega_code/sandbox/utils.py +216 -0
- kolega_code/services/base.py +255 -0
- kolega_code/services/browser.py +444 -0
- kolega_code/services/file_system.py +749 -0
- kolega_code/services/html.py +221 -0
- kolega_code/services/terminal.py +903 -0
- kolega_code/tools/__init__.py +22 -0
- kolega_code/tools/core.py +33 -0
- kolega_code/tools/definitions.py +81 -0
- kolega_code/tools/registry.py +73 -0
- kolega_code-0.1.0.dist-info/METADATA +157 -0
- kolega_code-0.1.0.dist-info/RECORD +171 -0
- kolega_code-0.1.0.dist-info/WHEEL +4 -0
- kolega_code-0.1.0.dist-info/entry_points.txt +2 -0
- kolega_code-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import pytest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import patch, mock_open
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from kolega_code.services.file_system import LocalFileSystem, FileSystemPath
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestLocalFileSystem:
|
|
12
|
+
"""Comprehensive tests for LocalFileSystem implementation."""
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def temp_dir(self):
|
|
16
|
+
"""Create a temporary directory for testing."""
|
|
17
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
18
|
+
yield Path(temp_dir)
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def filesystem(self, temp_dir):
|
|
22
|
+
"""Create a LocalFileSystem instance with a temporary root."""
|
|
23
|
+
return LocalFileSystem(root_path=temp_dir)
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def filesystem_no_root(self):
|
|
27
|
+
"""Create a LocalFileSystem instance without a root path."""
|
|
28
|
+
return LocalFileSystem()
|
|
29
|
+
|
|
30
|
+
def test_init_with_root_path_string(self, temp_dir):
|
|
31
|
+
"""Test initialization with root path as string."""
|
|
32
|
+
fs = LocalFileSystem(root_path=str(temp_dir))
|
|
33
|
+
assert fs.root_path == temp_dir
|
|
34
|
+
|
|
35
|
+
def test_init_with_root_path_pathlib(self, temp_dir):
|
|
36
|
+
"""Test initialization with root path as Path object."""
|
|
37
|
+
fs = LocalFileSystem(root_path=temp_dir)
|
|
38
|
+
assert fs.root_path == temp_dir
|
|
39
|
+
|
|
40
|
+
def test_init_without_root_path(self):
|
|
41
|
+
"""Test initialization without root path."""
|
|
42
|
+
fs = LocalFileSystem()
|
|
43
|
+
assert fs.root_path is None
|
|
44
|
+
|
|
45
|
+
def test_resolve_path_with_root(self, filesystem, temp_dir):
|
|
46
|
+
"""Test path resolution with root path."""
|
|
47
|
+
resolved = filesystem._resolve_path("test.txt")
|
|
48
|
+
expected = temp_dir / "test.txt"
|
|
49
|
+
assert resolved == expected
|
|
50
|
+
|
|
51
|
+
def test_resolve_path_without_root(self, filesystem_no_root):
|
|
52
|
+
"""Test path resolution without root path."""
|
|
53
|
+
resolved = filesystem_no_root._resolve_path("test.txt")
|
|
54
|
+
expected = Path("test.txt")
|
|
55
|
+
assert resolved == expected
|
|
56
|
+
|
|
57
|
+
def test_write_and_read_text(self, filesystem):
|
|
58
|
+
"""Test writing and reading text files."""
|
|
59
|
+
content = "Hello, World!\nThis is a test file."
|
|
60
|
+
filesystem.write_text("test.txt", content)
|
|
61
|
+
|
|
62
|
+
read_content = filesystem.read_text("test.txt")
|
|
63
|
+
assert read_content == content
|
|
64
|
+
|
|
65
|
+
def test_write_and_read_text_with_encoding(self, filesystem):
|
|
66
|
+
"""Test writing and reading text files with specific encoding."""
|
|
67
|
+
content = "Hello, 世界! 🌍"
|
|
68
|
+
filesystem.write_text("test_utf8.txt", content, encoding="utf-8")
|
|
69
|
+
|
|
70
|
+
read_content = filesystem.read_text("test_utf8.txt", encoding="utf-8")
|
|
71
|
+
assert read_content == content
|
|
72
|
+
|
|
73
|
+
def test_write_and_read_bytes(self, filesystem):
|
|
74
|
+
"""Test writing and reading binary files."""
|
|
75
|
+
content = b"\x00\x01\x02\x03\xff\xfe\xfd"
|
|
76
|
+
filesystem.write_bytes("test.bin", content)
|
|
77
|
+
|
|
78
|
+
read_content = filesystem.read_bytes("test.bin")
|
|
79
|
+
assert read_content == content
|
|
80
|
+
|
|
81
|
+
def test_exists(self, filesystem):
|
|
82
|
+
"""Test checking if files and directories exist."""
|
|
83
|
+
# File doesn't exist initially
|
|
84
|
+
assert not filesystem.exists("nonexistent.txt")
|
|
85
|
+
|
|
86
|
+
# Create file and check it exists
|
|
87
|
+
filesystem.write_text("exists_test.txt", "content")
|
|
88
|
+
assert filesystem.exists("exists_test.txt")
|
|
89
|
+
|
|
90
|
+
# Create directory and check it exists
|
|
91
|
+
filesystem.mkdir("test_dir")
|
|
92
|
+
assert filesystem.exists("test_dir")
|
|
93
|
+
|
|
94
|
+
def test_is_file(self, filesystem):
|
|
95
|
+
"""Test checking if path is a file."""
|
|
96
|
+
# Create file
|
|
97
|
+
filesystem.write_text("test_file.txt", "content")
|
|
98
|
+
assert filesystem.is_file("test_file.txt")
|
|
99
|
+
|
|
100
|
+
# Create directory
|
|
101
|
+
filesystem.mkdir("test_dir")
|
|
102
|
+
assert not filesystem.is_file("test_dir")
|
|
103
|
+
|
|
104
|
+
# Non-existent path
|
|
105
|
+
assert not filesystem.is_file("nonexistent.txt")
|
|
106
|
+
|
|
107
|
+
def test_is_dir(self, filesystem):
|
|
108
|
+
"""Test checking if path is a directory."""
|
|
109
|
+
# Create directory
|
|
110
|
+
filesystem.mkdir("test_dir")
|
|
111
|
+
assert filesystem.is_dir("test_dir")
|
|
112
|
+
|
|
113
|
+
# Create file
|
|
114
|
+
filesystem.write_text("test_file.txt", "content")
|
|
115
|
+
assert not filesystem.is_dir("test_file.txt")
|
|
116
|
+
|
|
117
|
+
# Non-existent path
|
|
118
|
+
assert not filesystem.is_dir("nonexistent_dir")
|
|
119
|
+
|
|
120
|
+
def test_stat(self, filesystem):
|
|
121
|
+
"""Test getting file statistics."""
|
|
122
|
+
content = "Test content for stat"
|
|
123
|
+
filesystem.write_text("stat_test.txt", content)
|
|
124
|
+
|
|
125
|
+
stat_info = filesystem.stat("stat_test.txt")
|
|
126
|
+
|
|
127
|
+
assert "size" in stat_info
|
|
128
|
+
assert "modified_time" in stat_info
|
|
129
|
+
assert "created_time" in stat_info
|
|
130
|
+
assert "accessed_time" in stat_info
|
|
131
|
+
assert "is_directory" in stat_info
|
|
132
|
+
assert "is_file" in stat_info
|
|
133
|
+
assert "stat_result" in stat_info
|
|
134
|
+
|
|
135
|
+
assert stat_info["size"] == len(content.encode())
|
|
136
|
+
assert stat_info["is_file"] is True
|
|
137
|
+
assert stat_info["is_directory"] is False
|
|
138
|
+
|
|
139
|
+
def test_mkdir(self, filesystem):
|
|
140
|
+
"""Test creating directories."""
|
|
141
|
+
# Simple directory creation
|
|
142
|
+
filesystem.mkdir("simple_dir")
|
|
143
|
+
assert filesystem.is_dir("simple_dir")
|
|
144
|
+
|
|
145
|
+
# Directory creation with parents
|
|
146
|
+
filesystem.mkdir("parent/child/grandchild", parents=True)
|
|
147
|
+
assert filesystem.is_dir("parent/child/grandchild")
|
|
148
|
+
|
|
149
|
+
# Directory creation with exist_ok
|
|
150
|
+
filesystem.mkdir("simple_dir", exist_ok=True) # Should not raise
|
|
151
|
+
|
|
152
|
+
# Directory creation without exist_ok should raise
|
|
153
|
+
with pytest.raises(FileExistsError):
|
|
154
|
+
filesystem.mkdir("simple_dir", exist_ok=False)
|
|
155
|
+
|
|
156
|
+
def test_remove(self, filesystem):
|
|
157
|
+
"""Test removing files."""
|
|
158
|
+
# Create and remove file
|
|
159
|
+
filesystem.write_text("remove_test.txt", "content")
|
|
160
|
+
assert filesystem.exists("remove_test.txt")
|
|
161
|
+
|
|
162
|
+
filesystem.remove("remove_test.txt")
|
|
163
|
+
assert not filesystem.exists("remove_test.txt")
|
|
164
|
+
|
|
165
|
+
# Test missing_ok=True
|
|
166
|
+
filesystem.remove("nonexistent.txt", missing_ok=True) # Should not raise
|
|
167
|
+
|
|
168
|
+
# Test missing_ok=False (default)
|
|
169
|
+
with pytest.raises(FileNotFoundError):
|
|
170
|
+
filesystem.remove("nonexistent.txt", missing_ok=False)
|
|
171
|
+
|
|
172
|
+
def test_rmdir(self, filesystem):
|
|
173
|
+
"""Test removing empty directories."""
|
|
174
|
+
# Create and remove empty directory
|
|
175
|
+
filesystem.mkdir("empty_dir")
|
|
176
|
+
assert filesystem.is_dir("empty_dir")
|
|
177
|
+
|
|
178
|
+
filesystem.rmdir("empty_dir")
|
|
179
|
+
assert not filesystem.exists("empty_dir")
|
|
180
|
+
|
|
181
|
+
# Test removing non-empty directory should raise
|
|
182
|
+
filesystem.mkdir("non_empty_dir")
|
|
183
|
+
filesystem.write_text("non_empty_dir/file.txt", "content")
|
|
184
|
+
|
|
185
|
+
with pytest.raises(OSError):
|
|
186
|
+
filesystem.rmdir("non_empty_dir")
|
|
187
|
+
|
|
188
|
+
def test_rmtree(self, filesystem):
|
|
189
|
+
"""Test removing directories recursively."""
|
|
190
|
+
# Create directory structure
|
|
191
|
+
filesystem.mkdir("tree/branch1/leaf1", parents=True)
|
|
192
|
+
filesystem.mkdir("tree/branch2/leaf2", parents=True)
|
|
193
|
+
filesystem.write_text("tree/file.txt", "content")
|
|
194
|
+
filesystem.write_text("tree/branch1/file1.txt", "content1")
|
|
195
|
+
|
|
196
|
+
assert filesystem.is_dir("tree")
|
|
197
|
+
|
|
198
|
+
# Remove entire tree
|
|
199
|
+
filesystem.rmtree("tree")
|
|
200
|
+
assert not filesystem.exists("tree")
|
|
201
|
+
|
|
202
|
+
def test_listdir(self, filesystem):
|
|
203
|
+
"""Test listing directory contents."""
|
|
204
|
+
# Create test structure
|
|
205
|
+
filesystem.mkdir("list_test")
|
|
206
|
+
filesystem.write_text("list_test/file1.txt", "content1")
|
|
207
|
+
filesystem.write_text("list_test/file2.txt", "content2")
|
|
208
|
+
filesystem.mkdir("list_test/subdir")
|
|
209
|
+
|
|
210
|
+
contents = filesystem.listdir("list_test")
|
|
211
|
+
|
|
212
|
+
assert len(contents) == 3
|
|
213
|
+
assert "file1.txt" in contents
|
|
214
|
+
assert "file2.txt" in contents
|
|
215
|
+
assert "subdir" in contents
|
|
216
|
+
|
|
217
|
+
def test_iterdir(self, filesystem):
|
|
218
|
+
"""Test iterating over directory contents."""
|
|
219
|
+
# Create test structure
|
|
220
|
+
filesystem.mkdir("iter_test")
|
|
221
|
+
filesystem.write_text("iter_test/file1.txt", "content1")
|
|
222
|
+
filesystem.write_text("iter_test/file2.txt", "content2")
|
|
223
|
+
filesystem.mkdir("iter_test/subdir")
|
|
224
|
+
|
|
225
|
+
contents = list(filesystem.iterdir("iter_test"))
|
|
226
|
+
|
|
227
|
+
assert len(contents) == 3
|
|
228
|
+
assert "iter_test/file1.txt" in contents
|
|
229
|
+
assert "iter_test/file2.txt" in contents
|
|
230
|
+
assert "iter_test/subdir" in contents
|
|
231
|
+
|
|
232
|
+
def test_glob(self, filesystem):
|
|
233
|
+
"""Test glob pattern matching."""
|
|
234
|
+
# Create test files
|
|
235
|
+
filesystem.write_text("test1.txt", "content")
|
|
236
|
+
filesystem.write_text("test2.txt", "content")
|
|
237
|
+
filesystem.write_text("test.py", "content")
|
|
238
|
+
filesystem.mkdir("subdir")
|
|
239
|
+
filesystem.write_text("subdir/test3.txt", "content")
|
|
240
|
+
|
|
241
|
+
# Test simple pattern
|
|
242
|
+
txt_files = filesystem.glob("*.txt")
|
|
243
|
+
assert "test1.txt" in txt_files
|
|
244
|
+
assert "test2.txt" in txt_files
|
|
245
|
+
assert "test.py" not in txt_files
|
|
246
|
+
|
|
247
|
+
# Test recursive pattern
|
|
248
|
+
all_txt = filesystem.glob("**/*.txt")
|
|
249
|
+
assert "test1.txt" in all_txt
|
|
250
|
+
assert "test2.txt" in all_txt
|
|
251
|
+
assert "subdir/test3.txt" in all_txt
|
|
252
|
+
|
|
253
|
+
def test_is_binary_file(self, filesystem):
|
|
254
|
+
"""Test binary file detection."""
|
|
255
|
+
# Text file
|
|
256
|
+
filesystem.write_text("text.txt", "This is text content")
|
|
257
|
+
assert not filesystem.is_binary_file("text.txt")
|
|
258
|
+
|
|
259
|
+
# Binary file by extension
|
|
260
|
+
filesystem.write_bytes("binary.jpg", b"fake image data")
|
|
261
|
+
assert filesystem.is_binary_file("binary.jpg")
|
|
262
|
+
|
|
263
|
+
# Binary file by content (contains null bytes)
|
|
264
|
+
filesystem.write_bytes("binary_content.dat", b"text\x00with\x00nulls")
|
|
265
|
+
assert filesystem.is_binary_file("binary_content.dat")
|
|
266
|
+
|
|
267
|
+
def test_get_name(self, filesystem):
|
|
268
|
+
"""Test getting filename from path."""
|
|
269
|
+
assert filesystem.get_name("test.txt") == "test.txt"
|
|
270
|
+
assert filesystem.get_name("dir/subdir/file.py") == "file.py"
|
|
271
|
+
assert filesystem.get_name("dir/") == "dir"
|
|
272
|
+
assert filesystem.get_name("dir") == "dir"
|
|
273
|
+
|
|
274
|
+
def test_get_suffix(self, filesystem):
|
|
275
|
+
"""Test getting file extension."""
|
|
276
|
+
assert filesystem.get_suffix("test.txt") == ".txt"
|
|
277
|
+
assert filesystem.get_suffix("file.tar.gz") == ".gz"
|
|
278
|
+
assert filesystem.get_suffix("no_extension") == ""
|
|
279
|
+
assert filesystem.get_suffix("dir/file.py") == ".py"
|
|
280
|
+
|
|
281
|
+
def test_get_parent(self, filesystem):
|
|
282
|
+
"""Test getting parent directory."""
|
|
283
|
+
# With root path, should return relative parent
|
|
284
|
+
parent = filesystem.get_parent("dir/subdir/file.txt")
|
|
285
|
+
assert parent == "dir/subdir"
|
|
286
|
+
|
|
287
|
+
parent = filesystem.get_parent("file.txt")
|
|
288
|
+
assert parent == "."
|
|
289
|
+
|
|
290
|
+
def test_get_parents(self, filesystem):
|
|
291
|
+
"""Test getting all parent directories."""
|
|
292
|
+
parents = filesystem.get_parents("dir/subdir/file.txt")
|
|
293
|
+
assert "dir/subdir" in parents
|
|
294
|
+
assert "dir" in parents
|
|
295
|
+
assert "." in parents
|
|
296
|
+
|
|
297
|
+
def test_relative_to(self, filesystem):
|
|
298
|
+
"""Test getting relative path."""
|
|
299
|
+
# Create some structure
|
|
300
|
+
filesystem.mkdir("base/sub", parents=True)
|
|
301
|
+
|
|
302
|
+
relative = filesystem.relative_to("base/sub/file.txt", "base")
|
|
303
|
+
assert relative == "sub/file.txt"
|
|
304
|
+
|
|
305
|
+
def test_join_path(self, filesystem):
|
|
306
|
+
"""Test joining path components."""
|
|
307
|
+
joined = filesystem.join_path("dir", "subdir", "file.txt")
|
|
308
|
+
assert joined == "dir/subdir/file.txt"
|
|
309
|
+
|
|
310
|
+
def test_is_absolute(self, filesystem):
|
|
311
|
+
"""Test checking if path is absolute."""
|
|
312
|
+
assert not filesystem.is_absolute("relative/path")
|
|
313
|
+
assert filesystem.is_absolute("/absolute/path")
|
|
314
|
+
|
|
315
|
+
# Windows absolute path
|
|
316
|
+
if os.name == "nt":
|
|
317
|
+
assert filesystem.is_absolute("C:\\absolute\\path")
|
|
318
|
+
|
|
319
|
+
def test_path_join(self, filesystem):
|
|
320
|
+
"""Test os.path.join compatibility method."""
|
|
321
|
+
joined = filesystem.path_join("dir", "subdir", "file.txt")
|
|
322
|
+
expected = os.path.join("dir", "subdir", "file.txt")
|
|
323
|
+
assert joined == expected
|
|
324
|
+
|
|
325
|
+
def test_path_exists(self, filesystem):
|
|
326
|
+
"""Test os.path.exists compatibility method."""
|
|
327
|
+
# This tests the raw os.path.exists, not the filesystem abstraction
|
|
328
|
+
with tempfile.NamedTemporaryFile() as tmp:
|
|
329
|
+
assert filesystem.path_exists(tmp.name)
|
|
330
|
+
assert not filesystem.path_exists("/nonexistent/path")
|
|
331
|
+
|
|
332
|
+
def test_path_isdir(self, filesystem):
|
|
333
|
+
"""Test os.path.isdir compatibility method."""
|
|
334
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
335
|
+
assert filesystem.path_isdir(tmp_dir)
|
|
336
|
+
assert not filesystem.path_isdir("/nonexistent/dir")
|
|
337
|
+
|
|
338
|
+
def test_path_isfile(self, filesystem):
|
|
339
|
+
"""Test os.path.isfile compatibility method."""
|
|
340
|
+
with tempfile.NamedTemporaryFile() as tmp:
|
|
341
|
+
assert filesystem.path_isfile(tmp.name)
|
|
342
|
+
assert not filesystem.path_isfile("/nonexistent/file")
|
|
343
|
+
|
|
344
|
+
def test_format_datetime(self, filesystem):
|
|
345
|
+
"""Test datetime formatting utility."""
|
|
346
|
+
timestamp = 1640995200.0 # 2022-01-01 00:00:00 UTC
|
|
347
|
+
formatted = filesystem.format_datetime(timestamp)
|
|
348
|
+
# The exact format depends on timezone, but should contain date components
|
|
349
|
+
assert "2022" in formatted or "2021" in formatted # Account for timezone differences
|
|
350
|
+
assert "-" in formatted
|
|
351
|
+
assert ":" in formatted
|
|
352
|
+
|
|
353
|
+
def test_open_context_manager(self, filesystem):
|
|
354
|
+
"""Test file opening as context manager."""
|
|
355
|
+
content = "Test content for context manager"
|
|
356
|
+
|
|
357
|
+
# Write using context manager
|
|
358
|
+
with filesystem.open("context_test.txt", "w") as f:
|
|
359
|
+
f.write(content)
|
|
360
|
+
|
|
361
|
+
# Read using context manager
|
|
362
|
+
with filesystem.open("context_test.txt", "r") as f:
|
|
363
|
+
read_content = f.read()
|
|
364
|
+
|
|
365
|
+
assert read_content == content
|
|
366
|
+
|
|
367
|
+
def test_error_handling(self, filesystem):
|
|
368
|
+
"""Test various error conditions."""
|
|
369
|
+
# FileNotFoundError for reading non-existent file
|
|
370
|
+
with pytest.raises(FileNotFoundError):
|
|
371
|
+
filesystem.read_text("nonexistent.txt")
|
|
372
|
+
|
|
373
|
+
# FileNotFoundError for stat on non-existent file
|
|
374
|
+
with pytest.raises(FileNotFoundError):
|
|
375
|
+
filesystem.stat("nonexistent.txt")
|
|
376
|
+
|
|
377
|
+
# NotADirectoryError for listing non-directory
|
|
378
|
+
filesystem.write_text("not_a_dir.txt", "content")
|
|
379
|
+
with pytest.raises(NotADirectoryError):
|
|
380
|
+
filesystem.listdir("not_a_dir.txt")
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class TestFileSystemPath:
|
|
384
|
+
"""Tests for the FileSystemPath wrapper class."""
|
|
385
|
+
|
|
386
|
+
@pytest.fixture
|
|
387
|
+
def temp_dir(self):
|
|
388
|
+
"""Create a temporary directory for testing."""
|
|
389
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
390
|
+
yield Path(temp_dir)
|
|
391
|
+
|
|
392
|
+
@pytest.fixture
|
|
393
|
+
def filesystem(self, temp_dir):
|
|
394
|
+
"""Create a LocalFileSystem instance with a temporary root."""
|
|
395
|
+
return LocalFileSystem(root_path=temp_dir)
|
|
396
|
+
|
|
397
|
+
@pytest.fixture
|
|
398
|
+
def fs_path(self, filesystem):
|
|
399
|
+
"""Create a FileSystemPath instance."""
|
|
400
|
+
return filesystem.path("test_path")
|
|
401
|
+
|
|
402
|
+
def test_init_and_str(self, filesystem):
|
|
403
|
+
"""Test FileSystemPath initialization and string representation."""
|
|
404
|
+
path = FileSystemPath(filesystem, "test/path")
|
|
405
|
+
assert str(path) == "test/path"
|
|
406
|
+
assert path.path == "test/path"
|
|
407
|
+
|
|
408
|
+
def test_truediv_operator(self, filesystem):
|
|
409
|
+
"""Test path / 'subpath' syntax."""
|
|
410
|
+
path = FileSystemPath(filesystem, "base")
|
|
411
|
+
new_path = path / "subdir" / "file.txt"
|
|
412
|
+
|
|
413
|
+
assert str(new_path) == "base/subdir/file.txt"
|
|
414
|
+
assert isinstance(new_path, FileSystemPath)
|
|
415
|
+
|
|
416
|
+
def test_truediv_with_root_path(self, filesystem):
|
|
417
|
+
"""Test path / 'subpath' syntax with root path."""
|
|
418
|
+
path = FileSystemPath(filesystem, ".")
|
|
419
|
+
new_path = path / "file.txt"
|
|
420
|
+
|
|
421
|
+
assert str(new_path) == "file.txt"
|
|
422
|
+
|
|
423
|
+
def test_name_property(self, filesystem):
|
|
424
|
+
"""Test name property."""
|
|
425
|
+
path = FileSystemPath(filesystem, "dir/file.txt")
|
|
426
|
+
assert path.name == "file.txt"
|
|
427
|
+
|
|
428
|
+
def test_suffix_property(self, filesystem):
|
|
429
|
+
"""Test suffix property."""
|
|
430
|
+
path = FileSystemPath(filesystem, "dir/file.txt")
|
|
431
|
+
assert path.suffix == ".txt"
|
|
432
|
+
|
|
433
|
+
def test_parent_property(self, filesystem):
|
|
434
|
+
"""Test parent property."""
|
|
435
|
+
path = FileSystemPath(filesystem, "dir/subdir/file.txt")
|
|
436
|
+
parent = path.parent
|
|
437
|
+
|
|
438
|
+
assert isinstance(parent, FileSystemPath)
|
|
439
|
+
assert str(parent) == "dir/subdir"
|
|
440
|
+
|
|
441
|
+
def test_parents_property(self, filesystem):
|
|
442
|
+
"""Test parents property."""
|
|
443
|
+
path = FileSystemPath(filesystem, "dir/subdir/file.txt")
|
|
444
|
+
parents = path.parents
|
|
445
|
+
|
|
446
|
+
assert all(isinstance(p, FileSystemPath) for p in parents)
|
|
447
|
+
parent_strs = [str(p) for p in parents]
|
|
448
|
+
assert "dir/subdir" in parent_strs
|
|
449
|
+
assert "dir" in parent_strs
|
|
450
|
+
|
|
451
|
+
def test_file_operations(self, filesystem):
|
|
452
|
+
"""Test file operations through FileSystemPath."""
|
|
453
|
+
path = filesystem.path("test_file.txt")
|
|
454
|
+
|
|
455
|
+
# Write and read
|
|
456
|
+
path.write_text("Hello, World!")
|
|
457
|
+
content = path.read_text()
|
|
458
|
+
assert content == "Hello, World!"
|
|
459
|
+
|
|
460
|
+
# Check existence and type
|
|
461
|
+
assert path.exists()
|
|
462
|
+
assert path.is_file()
|
|
463
|
+
assert not path.is_dir()
|
|
464
|
+
|
|
465
|
+
# Get stats
|
|
466
|
+
stat_info = path.stat()
|
|
467
|
+
assert stat_info["is_file"] is True
|
|
468
|
+
|
|
469
|
+
def test_directory_operations(self, filesystem):
|
|
470
|
+
"""Test directory operations through FileSystemPath."""
|
|
471
|
+
path = filesystem.path("test_dir")
|
|
472
|
+
|
|
473
|
+
# Create directory
|
|
474
|
+
path.mkdir()
|
|
475
|
+
assert path.exists()
|
|
476
|
+
assert path.is_dir()
|
|
477
|
+
assert not path.is_file()
|
|
478
|
+
|
|
479
|
+
# Create subdirectory with parents
|
|
480
|
+
subpath = path / "subdir" / "deep"
|
|
481
|
+
subpath.mkdir(parents=True)
|
|
482
|
+
assert subpath.exists()
|
|
483
|
+
|
|
484
|
+
def test_iterdir(self, filesystem):
|
|
485
|
+
"""Test iterating over directory contents."""
|
|
486
|
+
# Create test structure
|
|
487
|
+
base_path = filesystem.path("iter_base")
|
|
488
|
+
base_path.mkdir()
|
|
489
|
+
|
|
490
|
+
(base_path / "file1.txt").write_text("content1")
|
|
491
|
+
(base_path / "file2.txt").write_text("content2")
|
|
492
|
+
(base_path / "subdir").mkdir()
|
|
493
|
+
|
|
494
|
+
# Iterate and collect
|
|
495
|
+
contents = list(base_path.iterdir())
|
|
496
|
+
content_names = [item.name for item in contents]
|
|
497
|
+
|
|
498
|
+
assert len(contents) == 3
|
|
499
|
+
assert "file1.txt" in content_names
|
|
500
|
+
assert "file2.txt" in content_names
|
|
501
|
+
assert "subdir" in content_names
|
|
502
|
+
assert all(isinstance(item, FileSystemPath) for item in contents)
|
|
503
|
+
|
|
504
|
+
def test_glob(self, filesystem):
|
|
505
|
+
"""Test glob pattern matching through FileSystemPath."""
|
|
506
|
+
# Create test structure
|
|
507
|
+
base_path = filesystem.path("glob_base")
|
|
508
|
+
base_path.mkdir()
|
|
509
|
+
|
|
510
|
+
(base_path / "test1.txt").write_text("content")
|
|
511
|
+
(base_path / "test2.txt").write_text("content")
|
|
512
|
+
(base_path / "test.py").write_text("content")
|
|
513
|
+
|
|
514
|
+
# Test glob
|
|
515
|
+
txt_files = list(base_path.glob("*.txt"))
|
|
516
|
+
txt_names = [f.name for f in txt_files]
|
|
517
|
+
|
|
518
|
+
assert len(txt_files) == 2
|
|
519
|
+
assert "test1.txt" in txt_names
|
|
520
|
+
assert "test2.txt" in txt_names
|
|
521
|
+
assert all(isinstance(f, FileSystemPath) for f in txt_files)
|
|
522
|
+
|
|
523
|
+
def test_relative_to(self, filesystem):
|
|
524
|
+
"""Test relative_to method."""
|
|
525
|
+
base_path = filesystem.path("base")
|
|
526
|
+
file_path = filesystem.path("base/sub/file.txt")
|
|
527
|
+
|
|
528
|
+
relative = file_path.relative_to(base_path)
|
|
529
|
+
assert relative == "sub/file.txt"
|
|
530
|
+
|
|
531
|
+
# Test with string
|
|
532
|
+
relative_str = file_path.relative_to("base")
|
|
533
|
+
assert relative_str == "sub/file.txt"
|
|
534
|
+
|
|
535
|
+
def test_unlink(self, filesystem):
|
|
536
|
+
"""Test file removal through FileSystemPath."""
|
|
537
|
+
path = filesystem.path("remove_me.txt")
|
|
538
|
+
path.write_text("content")
|
|
539
|
+
assert path.exists()
|
|
540
|
+
|
|
541
|
+
path.unlink()
|
|
542
|
+
assert not path.exists()
|
|
543
|
+
|
|
544
|
+
# Test missing_ok
|
|
545
|
+
path.unlink(missing_ok=True) # Should not raise
|
|
546
|
+
|
|
547
|
+
def test_rmdir(self, filesystem):
|
|
548
|
+
"""Test directory removal through FileSystemPath."""
|
|
549
|
+
path = filesystem.path("remove_dir")
|
|
550
|
+
path.mkdir()
|
|
551
|
+
assert path.exists()
|
|
552
|
+
|
|
553
|
+
path.rmdir()
|
|
554
|
+
assert not path.exists()
|
|
555
|
+
|
|
556
|
+
def test_binary_operations(self, filesystem):
|
|
557
|
+
"""Test binary file operations through FileSystemPath."""
|
|
558
|
+
path = filesystem.path("binary_test.bin")
|
|
559
|
+
binary_data = b"\x00\x01\x02\x03\xff"
|
|
560
|
+
|
|
561
|
+
path.write_bytes(binary_data)
|
|
562
|
+
read_data = path.read_bytes()
|
|
563
|
+
|
|
564
|
+
assert read_data == binary_data
|
|
565
|
+
|
|
566
|
+
def test_open_context_manager(self, filesystem):
|
|
567
|
+
"""Test opening files as context manager through FileSystemPath."""
|
|
568
|
+
path = filesystem.path("context_test.txt")
|
|
569
|
+
content = "Context manager test"
|
|
570
|
+
|
|
571
|
+
# Write using context manager
|
|
572
|
+
with path.open("w") as f:
|
|
573
|
+
f.write(content)
|
|
574
|
+
|
|
575
|
+
# Read using context manager
|
|
576
|
+
with path.open("r") as f:
|
|
577
|
+
read_content = f.read()
|
|
578
|
+
|
|
579
|
+
assert read_content == content
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class TestFileSystemIntegration:
|
|
583
|
+
"""Integration tests for filesystem operations."""
|
|
584
|
+
|
|
585
|
+
@pytest.fixture
|
|
586
|
+
def temp_dir(self):
|
|
587
|
+
"""Create a temporary directory for testing."""
|
|
588
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
589
|
+
yield Path(temp_dir)
|
|
590
|
+
|
|
591
|
+
@pytest.fixture
|
|
592
|
+
def filesystem(self, temp_dir):
|
|
593
|
+
"""Create a LocalFileSystem instance with a temporary root."""
|
|
594
|
+
return LocalFileSystem(root_path=temp_dir)
|
|
595
|
+
|
|
596
|
+
def test_complex_directory_structure(self, filesystem):
|
|
597
|
+
"""Test creating and manipulating complex directory structures."""
|
|
598
|
+
# Create complex structure
|
|
599
|
+
structure = [
|
|
600
|
+
"project/src/main.py",
|
|
601
|
+
"project/src/utils/helper.py",
|
|
602
|
+
"project/tests/test_main.py",
|
|
603
|
+
"project/docs/readme.md",
|
|
604
|
+
"project/config.json",
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
for file_path in structure:
|
|
608
|
+
path = filesystem.path(file_path)
|
|
609
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
610
|
+
path.write_text(f"Content of {file_path}")
|
|
611
|
+
|
|
612
|
+
# Verify structure
|
|
613
|
+
assert filesystem.exists("project")
|
|
614
|
+
assert filesystem.is_dir("project")
|
|
615
|
+
assert filesystem.exists("project/src/main.py")
|
|
616
|
+
assert filesystem.is_file("project/src/main.py")
|
|
617
|
+
|
|
618
|
+
# Test glob patterns
|
|
619
|
+
py_files = filesystem.glob("**/*.py")
|
|
620
|
+
assert len(py_files) == 3
|
|
621
|
+
assert "project/src/main.py" in py_files
|
|
622
|
+
assert "project/src/utils/helper.py" in py_files
|
|
623
|
+
assert "project/tests/test_main.py" in py_files
|
|
624
|
+
|
|
625
|
+
# Test directory listing
|
|
626
|
+
src_contents = list(filesystem.iterdir("project/src"))
|
|
627
|
+
assert "project/src/main.py" in src_contents
|
|
628
|
+
assert "project/src/utils" in src_contents
|
|
629
|
+
|
|
630
|
+
def test_file_operations_chain(self, filesystem):
|
|
631
|
+
"""Test chaining multiple file operations."""
|
|
632
|
+
# Create initial file
|
|
633
|
+
path = filesystem.path("chain_test.txt")
|
|
634
|
+
path.write_text("Initial content")
|
|
635
|
+
|
|
636
|
+
# Read, modify, write back
|
|
637
|
+
content = path.read_text()
|
|
638
|
+
modified_content = content + "\nAdditional line"
|
|
639
|
+
path.write_text(modified_content)
|
|
640
|
+
|
|
641
|
+
# Verify modification
|
|
642
|
+
final_content = path.read_text()
|
|
643
|
+
assert "Initial content" in final_content
|
|
644
|
+
assert "Additional line" in final_content
|
|
645
|
+
|
|
646
|
+
# Get file stats
|
|
647
|
+
stats = path.stat()
|
|
648
|
+
assert stats["size"] > len("Initial content")
|
|
649
|
+
|
|
650
|
+
def test_error_recovery(self, filesystem):
|
|
651
|
+
"""Test error handling and recovery scenarios."""
|
|
652
|
+
# Try to read non-existent file
|
|
653
|
+
with pytest.raises(FileNotFoundError):
|
|
654
|
+
filesystem.read_text("nonexistent.txt")
|
|
655
|
+
|
|
656
|
+
# Create file after error
|
|
657
|
+
filesystem.write_text("recovery_test.txt", "Recovered!")
|
|
658
|
+
content = filesystem.read_text("recovery_test.txt")
|
|
659
|
+
assert content == "Recovered!"
|
|
660
|
+
|
|
661
|
+
# Try to create directory that already exists
|
|
662
|
+
filesystem.mkdir("existing_dir")
|
|
663
|
+
filesystem.mkdir("existing_dir", exist_ok=True) # Should not raise
|
|
664
|
+
|
|
665
|
+
with pytest.raises(FileExistsError):
|
|
666
|
+
filesystem.mkdir("existing_dir", exist_ok=False)
|
|
667
|
+
|
|
668
|
+
def test_path_object_consistency(self, filesystem):
|
|
669
|
+
"""Test that FileSystemPath objects maintain consistency."""
|
|
670
|
+
# Create path objects for same path
|
|
671
|
+
path1 = filesystem.path("consistency_test.txt")
|
|
672
|
+
path2 = filesystem.path("consistency_test.txt")
|
|
673
|
+
|
|
674
|
+
# Operations on one should be visible through the other
|
|
675
|
+
path1.write_text("Consistent content")
|
|
676
|
+
assert path2.exists()
|
|
677
|
+
assert path2.read_text() == "Consistent content"
|
|
678
|
+
|
|
679
|
+
# Path operations should work consistently
|
|
680
|
+
assert path1.name == path2.name
|
|
681
|
+
assert path1.suffix == path2.suffix
|
|
682
|
+
assert str(path1.parent) == str(path2.parent)
|
|
683
|
+
|
|
684
|
+
def test_mixed_operations(self, filesystem):
|
|
685
|
+
"""Test mixing direct filesystem calls with FileSystemPath operations."""
|
|
686
|
+
# Create file using direct filesystem
|
|
687
|
+
filesystem.write_text("mixed_test.txt", "Direct content")
|
|
688
|
+
|
|
689
|
+
# Access using FileSystemPath
|
|
690
|
+
path = filesystem.path("mixed_test.txt")
|
|
691
|
+
assert path.exists()
|
|
692
|
+
assert path.read_text() == "Direct content"
|
|
693
|
+
|
|
694
|
+
# Modify using FileSystemPath
|
|
695
|
+
path.write_text("Modified via path")
|
|
696
|
+
|
|
697
|
+
# Read using direct filesystem
|
|
698
|
+
content = filesystem.read_text("mixed_test.txt")
|
|
699
|
+
assert content == "Modified via path"
|