2980b14e7b
Add ~60 new behave scenarios across output rendering, tool router, file ops, and skill search features targeting uncovered lines and branches. Key additions: - ElementHandle validation guards (empty id, type, negative index, None args) - Handle close/context-manager edge cases - Table list-row and batch-row coverage - Color/box-draw/JSON/YAML materializer edge cases - Format selection paths (detect capabilities, explicit flag, empty format) - Session ColumnDef, double-close guard, force-close open handles - Tool router schema export and provider format scenarios - File ops edge cases and search stat-failure/glob-include tests All nox checks pass: lint, typecheck (0 errors), unit_tests (4730 scenarios), coverage_report (97.0% >= 97% threshold).
278 lines
12 KiB
Gherkin
278 lines
12 KiB
Gherkin
Feature: Skill File Operation Tools
|
|
As a developer
|
|
I want skill-level file operation tools
|
|
So that agents can safely read, write, edit, and delete files with
|
|
enhanced safety features like binary detection, atomic writes, and
|
|
newline normalization
|
|
|
|
# ---- ReadFile ----
|
|
|
|
Scenario: ReadFile reads text file content
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "greeting.txt" with content "Hello, World!"
|
|
When I execute the "skill/file-read" tool with path "greeting.txt"
|
|
Then the skill tool result should be successful
|
|
And the skill output "content" should be "Hello, World!"
|
|
And the skill output "encoding" should be "utf-8"
|
|
And the skill output "size" should be greater than 0
|
|
|
|
Scenario: ReadFile with offset and limit
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "lines.txt" with lines "line1,line2,line3,line4"
|
|
When I execute the "skill/file-read" tool with path "lines.txt" offset 1 limit 2
|
|
Then the skill tool result should be successful
|
|
And the skill output "content" should contain "line2"
|
|
And the skill output "content" should contain "line3"
|
|
And the skill output "content" should not contain "line1"
|
|
|
|
Scenario: ReadFile detects binary files and returns error
|
|
Given a skill file ops sandbox directory
|
|
And a binary file "image.png" in the sandbox
|
|
When I execute the "skill/file-read" tool with path "image.png"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "Binary file detected"
|
|
|
|
Scenario: ReadFile rejects missing file
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-read" tool with path "ghost.txt"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "not found"
|
|
|
|
Scenario: ReadFile enforces content size limit
|
|
Given a skill file ops sandbox directory
|
|
And an oversized file "huge.txt" in the sandbox
|
|
When I execute the "skill/file-read" tool with path "huge.txt"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "too large"
|
|
|
|
Scenario: ReadFile normalizes UTF-8 encoding
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "utf8.txt" with content "caf\u00e9"
|
|
When I execute the "skill/file-read" tool with path "utf8.txt"
|
|
Then the skill tool result should be successful
|
|
And the skill output "content" should contain "caf"
|
|
|
|
Scenario: ReadFile rejects non-file path
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox subdirectory "mydir"
|
|
When I execute the "skill/file-read" tool with path "mydir"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "Not a regular file"
|
|
|
|
# ---- WriteFile ----
|
|
|
|
Scenario: WriteFile creates a new file atomically
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-write" tool with path "new.txt" and content "created"
|
|
Then the skill tool result should be successful
|
|
And the skill output "created" should be boolean true
|
|
And the skill output "size" should be greater than 0
|
|
And the sandbox file "new.txt" should exist
|
|
|
|
Scenario: WriteFile overwrites an existing file
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "existing.txt" with content "old"
|
|
When I execute the "skill/file-write" tool with path "existing.txt" and content "new"
|
|
Then the skill tool result should be successful
|
|
And the skill output "created" should be boolean false
|
|
|
|
Scenario: WriteFile creates parent directories
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-write" tool with path "sub/dir/file.txt" and content "nested"
|
|
Then the skill tool result should be successful
|
|
And the sandbox file "sub/dir/file.txt" should exist
|
|
|
|
Scenario: WriteFile with LF newline normalization
|
|
Given a skill file ops sandbox directory
|
|
When I write "skill/file-write" with path "lf.txt" content "a\r\nb\r\n" and newline_mode "lf"
|
|
Then the skill tool result should be successful
|
|
And the sandbox file "lf.txt" should not contain carriage return
|
|
|
|
Scenario: WriteFile with CRLF newline normalization
|
|
Given a skill file ops sandbox directory
|
|
When I write "skill/file-write" with path "crlf.txt" content "a\nb\n" and newline_mode "crlf"
|
|
Then the skill tool result should be successful
|
|
And the sandbox file "crlf.txt" should contain carriage return
|
|
|
|
# ---- EditFile ----
|
|
|
|
Scenario: EditFile with single replacement
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "edit.txt" with content "foo bar foo"
|
|
When I execute the "skill/file-edit" tool replacing "foo" with "baz" in "edit.txt"
|
|
Then the skill tool result should be successful
|
|
And the skill output "replacements" should equal 1
|
|
|
|
Scenario: EditFile with replace_all
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "edit.txt" with content "foo bar foo"
|
|
When I execute the "skill/file-edit" tool replacing all "foo" with "baz" in "edit.txt"
|
|
Then the skill tool result should be successful
|
|
And the skill output "replacements" should equal 2
|
|
|
|
Scenario: EditFile preserves file permissions
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "perm.txt" with content "original" and mode 0o755
|
|
When I execute the "skill/file-edit" tool replacing "original" with "modified" in "perm.txt"
|
|
Then the skill tool result should be successful
|
|
And the sandbox file "perm.txt" should have mode 0o755
|
|
|
|
Scenario: EditFile with text not found raises error
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "edit.txt" with content "foo bar"
|
|
When I execute the "skill/file-edit" tool replacing "notfound" with "x" in "edit.txt"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "not found"
|
|
|
|
Scenario: EditFile rejects missing file
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-edit" tool replacing "a" with "b" in "ghost.txt"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "not found"
|
|
|
|
# ---- DeleteFile ----
|
|
|
|
Scenario: DeleteFile removes an existing file
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "doomed.txt" with content "goodbye"
|
|
When I execute the "skill/file-delete" tool with path "doomed.txt"
|
|
Then the skill tool result should be successful
|
|
And the skill output "existed" should be boolean true
|
|
And the sandbox file "doomed.txt" should not exist
|
|
|
|
Scenario: DeleteFile on nonexistent file reports not existed
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-delete" tool with path "ghost.txt"
|
|
Then the skill tool result should be successful
|
|
And the skill output "existed" should be boolean false
|
|
|
|
Scenario: DeleteFile rejects directory targets
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox subdirectory "mydir"
|
|
When I execute the "skill/file-delete" tool with path "mydir"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "non-file"
|
|
|
|
# ---- Path Traversal Guards ----
|
|
|
|
Scenario: Path traversal with dotdot in read is rejected
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-read" tool with path "../../etc/passwd"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "traversal"
|
|
And the skill tool error should mention "../../etc/passwd"
|
|
|
|
Scenario: Path traversal in write is rejected
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-write" tool with path "../escape.txt" and content "evil"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "traversal"
|
|
And the skill tool error should mention "../escape.txt"
|
|
|
|
Scenario: Path traversal in edit is rejected
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "safe.txt" with content "safe"
|
|
When I execute the "skill/file-edit" tool replacing "safe" with "x" in "../evil.txt"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "traversal"
|
|
|
|
Scenario: Path traversal in delete is rejected
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-delete" tool with path "../../etc/important"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "traversal"
|
|
|
|
# ---- Read-Only Enforcement ----
|
|
|
|
Scenario: ReadFile tool has read_only capability
|
|
Given a skill file tools registry
|
|
Then the "skill/file-read" tool should have read_only capability
|
|
|
|
Scenario: WriteFile tool has writes capability
|
|
Given a skill file tools registry
|
|
Then the "skill/file-write" tool should have writes capability
|
|
|
|
Scenario: EditFile tool has writes capability
|
|
Given a skill file tools registry
|
|
Then the "skill/file-edit" tool should have writes capability
|
|
|
|
Scenario: DeleteFile tool has writes capability
|
|
Given a skill file tools registry
|
|
Then the "skill/file-delete" tool should have writes capability
|
|
|
|
# ---- Registration ----
|
|
|
|
Scenario: Register all skill file tools
|
|
Given a skill file tools registry
|
|
Then the skill registry should contain 4 tools
|
|
And the skill registry should contain tool "skill/file-read"
|
|
And the skill registry should contain tool "skill/file-write"
|
|
And the skill registry should contain tool "skill/file-edit"
|
|
And the skill registry should contain tool "skill/file-delete"
|
|
|
|
# ---- Newline normalization helpers ----
|
|
|
|
Scenario: Newline normalization with invalid mode raises error
|
|
Given a skill file ops sandbox directory
|
|
When I write "skill/file-write" with path "bad.txt" content "test" and newline_mode "invalid"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "Invalid newline mode"
|
|
|
|
# ---- Empty path validation ----
|
|
|
|
Scenario: Empty path is rejected in read
|
|
Given a skill file ops sandbox directory
|
|
When I execute the "skill/file-read" tool with an empty path
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "must not be empty"
|
|
|
|
# ---- Binary detection heuristic ----
|
|
|
|
Scenario: ReadFile detects ELF binary
|
|
Given a skill file ops sandbox directory
|
|
And an ELF binary file "program" in the sandbox
|
|
When I execute the "skill/file-read" tool with path "program"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "Binary file detected"
|
|
|
|
Scenario: ReadFile detects high control character content
|
|
Given a skill file ops sandbox directory
|
|
And a control-heavy file "binary.dat" in the sandbox
|
|
When I execute the "skill/file-read" tool with path "binary.dat"
|
|
Then the skill tool result should not be successful
|
|
And the skill tool error should mention "Binary file detected"
|
|
|
|
# ---- Coverage: _is_binary OSError, create_dirs, atomic write cleanup ----
|
|
|
|
Scenario: _is_binary returns false for unreadable path
|
|
Given a skill file ops sandbox directory
|
|
When I check _is_binary on a nonexistent path
|
|
Then _is_binary should return false
|
|
|
|
Scenario: WriteFile creates parent directories with create_dirs
|
|
Given a skill file ops sandbox directory
|
|
When I execute write_file with create_dirs to "deep/nested/dir/file.txt" and content "hello dirs"
|
|
Then the skill tool result should be successful
|
|
And the written file "deep/nested/dir/file.txt" should contain "hello dirs"
|
|
|
|
Scenario: _is_fd_closed returns false for open descriptor
|
|
When I check _is_fd_closed with an open file descriptor
|
|
Then _is_fd_closed should return false
|
|
|
|
Scenario: _is_fd_closed returns true for closed descriptor
|
|
When I check _is_fd_closed with a closed file descriptor
|
|
Then _is_fd_closed should return true
|
|
|
|
Scenario: WriteFile atomic write cleanup on os.replace failure
|
|
Given a skill file ops sandbox directory
|
|
When I simulate atomic write failure during replace
|
|
Then the temporary file should be cleaned up
|
|
And the original exception should propagate
|
|
|
|
Scenario: EditFile atomic write cleanup on os.chmod failure
|
|
Given a skill file ops sandbox directory
|
|
And a sandbox file "editable.txt" with content "original content"
|
|
When I simulate edit atomic write failure during chmod
|
|
Then the temporary file should be cleaned up after edit
|
|
And the original edit exception should propagate
|