Python · 3404 bytes Raw Blame History
1 """Tests for file and shell hardening introduced in Sprint 03."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 import pytest
8
9 from loader.runtime.permissions import PermissionMode
10 from loader.tools.file_tools import ReadTool, WriteTool
11 from loader.tools.fs_safety import MAX_WRITE_SIZE
12 from loader.tools.shell_tools import BashTool
13
14
15 @pytest.mark.asyncio
16 async def test_read_tool_blocks_binary_file(temp_dir: Path) -> None:
17 tool = ReadTool(workspace_root=temp_dir)
18 binary_path = temp_dir / "binary.bin"
19 binary_path.write_bytes(b"\x00\x01\x02")
20
21 result = await tool.execute(file_path=str(binary_path))
22
23 assert result.is_error
24 assert "binary" in result.output.lower()
25
26
27 @pytest.mark.asyncio
28 async def test_read_tool_allows_outside_workspace(temp_dir: Path) -> None:
29 """Reads are safe and should not enforce workspace boundaries."""
30 outside = temp_dir.parent / "outside.txt"
31 outside.write_text("outside\n")
32 tool = ReadTool(workspace_root=temp_dir)
33
34 result = await tool.execute(file_path=str(outside))
35
36 assert not result.is_error
37 assert "outside" in result.output
38 outside.unlink()
39
40
41 @pytest.mark.asyncio
42 async def test_write_tool_prompts_for_workspace_escape(temp_dir: Path) -> None:
43 from loader.tools.base import ConfirmationRequired
44
45 outside = temp_dir.parent / "outside-write.txt"
46 tool = WriteTool(workspace_root=temp_dir)
47
48 # First attempt raises ConfirmationRequired
49 with pytest.raises(ConfirmationRequired):
50 await tool.execute(file_path=str(outside), content="outside\n")
51
52 assert not outside.exists()
53
54 # Second attempt (simulating retry after user approval) succeeds
55 result = await tool.execute(file_path=str(outside), content="outside\n")
56 assert not result.is_error
57 assert outside.exists()
58 outside.unlink()
59
60
61 @pytest.mark.asyncio
62 async def test_write_tool_blocks_oversize_content(temp_dir: Path) -> None:
63 tool = WriteTool(workspace_root=temp_dir)
64 target = temp_dir / "too-large.txt"
65
66 result = await tool.execute(file_path=str(target), content="a" * (MAX_WRITE_SIZE + 1))
67
68 assert result.is_error
69 assert "too large" in result.output.lower()
70
71
72 @pytest.mark.asyncio
73 async def test_write_tool_returns_structured_patch_metadata(temp_dir: Path) -> None:
74 tool = WriteTool(workspace_root=temp_dir)
75 target = temp_dir / "patch.txt"
76
77 result = await tool.execute(file_path=str(target), content="line one\nline two\n")
78
79 assert not result.is_error
80 assert result.metadata["file_path"] == str(target.resolve())
81 assert result.metadata["kind"] == "create"
82 assert result.metadata["structured_patch"]
83
84
85 @pytest.mark.asyncio
86 async def test_bash_tool_reports_truncation_metadata() -> None:
87 tool = BashTool()
88
89 result = await tool.execute(command='python -c "print(\'a\' * 60000)"')
90
91 assert not result.is_error
92 assert result.metadata["truncated"] is True
93 assert result.metadata["output_limit"] == tool.OUTPUT_LIMIT
94 assert "output truncated" in result.output.lower()
95
96
97 def test_bash_tool_classifies_permissions() -> None:
98 tool = BashTool()
99
100 assert tool.classify_command_permission("ls -la") == PermissionMode.READ_ONLY
101 assert tool.classify_command_permission("touch notes.txt") == PermissionMode.WORKSPACE_WRITE
102 assert tool.classify_command_permission("sudo rm -rf /tmp/test") == PermissionMode.DANGER_FULL_ACCESS