cc45b5ff2c
Implement GitWorktreeSandbox (worktree creation, commit+merge, rollback, cleanup, branch sanitization, git timeouts) and CopyOnWriteSandbox (directory copy, diff-based commit, rollback, cleanup). Update SandboxFactory to wire new strategies, remove stale overlay/ versioning constants, add snapshot placeholder, align resource type mapping to spec types (git-checkout, fs-mount, fs-directory, fs-file). Add 40 Behave scenarios (19 git_worktree + 21 copy_on_write) with gwt/cow prefixed steps. Update existing sandbox_factory_coverage feature (21 scenarios) to reflect new factory behavior. All quality gates pass: ruff, pyright, 2325 scenarios, 97% coverage. TASK-006 / Stage B4
142 lines
6.2 KiB
Gherkin
142 lines
6.2 KiB
Gherkin
Feature: Copy-on-write sandbox lifecycle
|
|
As a developer
|
|
I want a sandbox that isolates plan changes in a directory copy
|
|
So that the original directory is unmodified until changes are committed
|
|
|
|
Background:
|
|
Given a cow test directory is initialised
|
|
|
|
# --- Creation ---
|
|
|
|
Scenario: Create a copy-on-write sandbox
|
|
When a cow sandbox is created for plan "plan-001"
|
|
Then the cow sandbox should be in the "created" state
|
|
And the cow sandbox context should reference plan "plan-001"
|
|
And the cow sandbox context should have strategy metadata "copy_on_write"
|
|
And the cow sandbox path should exist
|
|
|
|
Scenario: Creating a sandbox with empty plan_id raises ValueError
|
|
When a cow sandbox is created with empty plan_id
|
|
Then a cow ValueError should be raised with message "plan_id cannot be empty"
|
|
|
|
Scenario: Creating a sandbox with empty resource_id raises ValueError
|
|
When a cow sandbox is prepared with empty resource_id
|
|
Then a cow ValueError should be raised with message "resource_id cannot be empty"
|
|
|
|
Scenario: Creating a sandbox with empty original_path raises ValueError
|
|
When a cow sandbox is prepared with empty original_path
|
|
Then a cow ValueError should be raised with message "original_path cannot be empty"
|
|
|
|
Scenario: Creating a sandbox on a non-existent directory raises SandboxCreationError
|
|
Given a cow non-existent directory
|
|
When a cow sandbox is created on the non-existent directory for plan "plan-001"
|
|
Then a cow SandboxCreationError should be raised
|
|
|
|
# --- Path resolution ---
|
|
|
|
Scenario: Resolve a path in the sandbox copy
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow path "data/file.txt" is resolved
|
|
Then the cow resolved path should be inside the sandbox copy
|
|
And the cow sandbox should be in the "active" state
|
|
|
|
Scenario: Path traversal is rejected
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow path "../etc/passwd" is resolved
|
|
Then a cow ValueError should be raised with message "Path traversal not allowed"
|
|
|
|
Scenario: Resolving a path on a cleaned-up sandbox raises SandboxStateError
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow sandbox is cleaned up
|
|
And the cow path "file.txt" is resolved on a cleaned-up sandbox
|
|
Then a cow SandboxStateError should be raised
|
|
|
|
# --- Commit ---
|
|
|
|
Scenario: Commit with no changes produces empty result
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow sandbox is committed
|
|
Then the cow commit result should indicate success
|
|
And the cow commit result should have 0 changed files
|
|
And the cow commit result should have 0 added files
|
|
And the cow commit result should have 0 deleted files
|
|
|
|
Scenario: Commit with a new file syncs it to original
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And a cow file "new_file.txt" is created in the sandbox with content "hello world"
|
|
And the cow sandbox is committed
|
|
Then the cow commit result should indicate success
|
|
And the cow commit result should have 1 added files
|
|
And the cow file "new_file.txt" should exist in the original directory with content "hello world"
|
|
|
|
Scenario: Commit with a modified file syncs the change
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow existing file "existing.txt" is modified in the sandbox with content "updated"
|
|
And the cow sandbox is committed
|
|
Then the cow commit result should indicate success
|
|
And the cow commit result should have 1 changed files
|
|
And the cow file "existing.txt" in the original should have content "updated"
|
|
|
|
Scenario: Commit with a deleted file removes it from original
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow existing file "existing.txt" is deleted from the sandbox
|
|
And the cow sandbox is committed
|
|
Then the cow commit result should indicate success
|
|
And the cow commit result should have 1 deleted files
|
|
And the cow file "existing.txt" should not exist in the original directory
|
|
|
|
Scenario: Commit on a cleaned-up sandbox raises SandboxStateError
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow sandbox is cleaned up
|
|
And the cow sandbox commit is attempted on cleaned-up sandbox
|
|
Then a cow SandboxStateError should be raised
|
|
|
|
# --- Rollback ---
|
|
|
|
Scenario: Rollback restores the sandbox to original state
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And a cow file "temp.txt" is created in the sandbox with content "temporary"
|
|
And the cow path "temp.txt" is resolved
|
|
And the cow sandbox is rolled back
|
|
Then the cow sandbox should be in the "rolled_back" state
|
|
And the cow file "temp.txt" should not exist in the sandbox
|
|
|
|
Scenario: Rollback on a non-active sandbox raises SandboxStateError
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow sandbox rollback is attempted on created sandbox
|
|
Then a cow SandboxStateError should be raised
|
|
|
|
# --- Cleanup ---
|
|
|
|
Scenario: Cleanup removes the sandbox directory
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow sandbox is cleaned up
|
|
Then the cow sandbox should be in the "cleaned_up" state
|
|
And the cow sandbox path should not exist
|
|
|
|
Scenario: Cleanup is idempotent
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And the cow sandbox is cleaned up
|
|
And the cow sandbox is cleaned up again
|
|
Then the cow sandbox should be in the "cleaned_up" state
|
|
|
|
# --- Protocol properties ---
|
|
|
|
Scenario: Sandbox has a unique ULID identifier
|
|
When a cow sandbox is instantiated
|
|
Then the cow sandbox_id should be a valid ULID
|
|
And the cow sandbox status should be "pending"
|
|
And the cow sandbox context should be None
|
|
|
|
# --- Diff computation ---
|
|
|
|
Scenario: Diff detects added, modified, and deleted files
|
|
When a cow sandbox is created for plan "plan-001"
|
|
And a cow file "added.txt" is created in the sandbox with content "new"
|
|
And the cow existing file "existing.txt" is modified in the sandbox with content "changed"
|
|
And the cow existing file "to_delete.txt" is deleted from the sandbox
|
|
And the cow sandbox is committed
|
|
Then the cow commit result should have 1 added files
|
|
And the cow commit result should have 1 changed files
|
|
And the cow commit result should have 1 deleted files
|