7f078f75a5
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 28s
CI / security (pull_request) Successful in 1m8s
CI / build (pull_request) Successful in 20s
CI / helm (pull_request) Successful in 34s
CI / quality (pull_request) Successful in 3m53s
CI / integration_tests (pull_request) Successful in 4m2s
CI / typecheck (pull_request) Successful in 4m9s
CI / unit_tests (pull_request) Successful in 4m48s
CI / docker (pull_request) Successful in 1m19s
CI / e2e_tests (pull_request) Successful in 9m47s
CI / coverage (pull_request) Successful in 11m41s
CI / status-check (pull_request) Successful in 1s
CI / quality (push) Successful in 52s
CI / lint (push) Successful in 3m18s
CI / build (push) Successful in 20s
CI / typecheck (push) Successful in 3m56s
CI / helm (push) Successful in 22s
CI / security (push) Successful in 4m15s
CI / unit_tests (push) Successful in 4m39s
CI / docker (push) Successful in 18s
CI / integration_tests (push) Successful in 6m56s
CI / e2e_tests (push) Successful in 12m34s
CI / coverage (push) Successful in 11m39s
CI / status-check (push) Successful in 1s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 30m10s
CI / benchmark-regression (pull_request) Successful in 57m33s
Changed SandboxManager.commit_all() from partial-commit semantics to all-or-nothing atomic operation per specification requirement. On partial failure, already-committed sandboxes are rolled back. Error reporting indicates which sandbox failed and what was rolled back. Added Behave scenarios verifying atomicity guarantee. Hardened atomicity guarantees after code review: - commit_all and _rollback_committed catch Exception (not just SandboxError) so unexpected errors cannot bypass rollback. Non-SandboxError exceptions are wrapped in a new AtomicCommitError (chaining the original as __cause__) that carries rolled_back_ids and failed_rollback_ids attributes so callers can programmatically determine rollback outcomes. - _rollback_committed returns both rolled_back_ids and failed_rollback_ids; the error result metadata now carries both "rolled_back" and "rollback_failed" keys. - _rollback_committed iterates in reverse (LIFO) order following the standard transaction-log undo pattern. Clarified in docstring that this is distinct from the specification DAG-based "top-down" rollback ordering (line 24632). - TransactionSandbox.rollback() from COMMITTED now raises SandboxRollbackError (database commits are irreversible) instead of silently transitioning to ROLLED_BACK. - TransactionSandbox is now classified as non-rollbackable in commit_all alongside NoSandbox and committed last in the batch, since database COMMIT is irreversible. Docstring corrected to match the raising behavior. - CopyOnWriteSandbox and OverlaySandbox rollback-from-COMMITTED uses rename-based safe_restore() to prevent data loss when the copytree step fails after the original was removed. - CopyOnWriteSandbox and OverlaySandbox rollback() now catches Exception (not just OSError), matching the broader catch used in _rollback_committed, so non-OSError exceptions from safe_restore set the status to ERRORED correctly. - Extracted shared _fs_utils module (backup_directory, safe_restore, compute_diff) with symlink, permission, and timestamp preservation (including directory timestamps), replacing duplicated per-class _backup_directory and _compute_diff methods. - backup_directory defers directory permissions and timestamps to a bottom-up post-walk pass, fixing incorrect mtime preservation (POSIX file creation inside a directory overwrites its mtime) and preventing restrictive source permissions from blocking backup writes. - backup_directory skips non-regular files (FIFOs, sockets, device files) with a warning to prevent hangs on special files. - CopyOnWriteSandbox and OverlaySandbox commit() now attempts to restore the original from the pre-commit backup when the file-copy phase fails midway, preventing partial corruption. If the restore itself fails the backup is preserved for manual recovery (cleanup() still removes it). - CopyOnWriteSandbox and OverlaySandbox commit() error handler now catches Exception (not just OSError) so that unexpected errors during the file-copy phase also trigger pre-commit backup restoration, preventing partial corruption of the original directory. - Pre-commit backup exception handler catches Exception (not just OSError) preventing temp directory leaks on non-OSError failures from backup_directory. - Fixed _pre_commit_backup assignment timing: the backup reference is now assigned only AFTER backup_directory() succeeds, preventing safe_restore() from corrupting an intact original with a partial backup when backup_directory() fails (e.g. disk full). - Rollback from COMMITTED with no pre-commit backup (no changes were applied) is now a no-op instead of raising SandboxRollbackError, preventing false rollback-failure reports in commit_all error metadata. - commit_all logs a warning when NoSandbox or TransactionSandbox instances are present in the batch since their changes cannot be rolled back, which breaks the atomicity guarantee. - Pre-commit backup is skipped when compute_diff returns no changes, avoiding a full directory copy for no-op commits. - GitWorktreeSandbox clears _pre_merge_commit on commit failure so the stale value cannot be used by future code. - commit_all docstring documents Raises clause for AtomicCommitError exception wrapping behavior. - GitWorktreeSandbox.rollback() docstring warns about multi-worktree safety when rolling back from COMMITTED. - Updated SandboxStatus transition diagram in protocol.py to clearly show the COMMITTED -> ROLLED_BACK path. - Added spec-contradiction note (line 45938 vs 19193) in commit_all docstring. - OverlaySandbox rollback from COMMITTED now properly remounts OverlayFS for real overlay (unmount, clean upper/work dirs, remount) and uses dirs_exist_ok=True for userspace fallback to prevent FileExistsError if rmtree silently fails. The merged directory is reset from the restored original, preventing stale pre-rollback data from being exposed on re-activation via get_path() (which allows ROLLED_BACK status). - OverlaySandbox rollback from COMMITTED now raises SandboxRollbackError if the OverlayFS unmount fails, preventing a double-mount attempt that would leave the sandbox in an inconsistent state. - OverlaySandbox rollback from ACTIVE now uses dirs_exist_ok=True for userspace fallback to prevent FileExistsError when rmtree with ignore_errors=True silently fails. - CopyOnWriteSandbox rollback from ACTIVE now uses dirs_exist_ok=True in copytree to prevent FileExistsError when rmtree with ignore_errors=True silently fails, matching the fix already applied to OverlaySandbox. - Non-rollbackable sandboxes (NoSandbox, TransactionSandbox) are committed last in the batch so that all rollbackable sandboxes commit first; if any rollbackable sandbox fails, none of the non-rollbackable sandboxes will have committed yet. - Moved NoSandbox and TransactionSandbox imports to module level in manager.py (no circular dependency exists). - Pre-commit backups are now created on the same filesystem as the original directory (using dir= argument to mkdtemp), avoiding cross-device copy overhead and ensuring os.rename compatibility. - safe_restore now renames the target into the mkdtemp directory instead of removing the mkdtemp dir first, eliminating the residual TOCTOU window between rmdir and rename. - safe_restore catches BaseException (not just OSError) to ensure the original directory is always renamed back on unexpected errors, preventing the original from being left in the renamed-aside state. - Added AtomicCommitError exception class to protocol.py carrying rolled_back_ids and failed_rollback_ids attributes. - Exported AtomicCommitError from sandbox package __init__.py so callers can import it from the public API. - Added BDD scenarios: LIFO rollback order, AtomicCommitError wrapping with RuntimeError cause and rollback metadata, _fs_utils backup/restore coverage, no-change commit rollback success, directory timestamp preservation, OverlaySandbox merged dir reset after COMMITTED rollback, CopyOnWriteSandbox rollback from COMMITTED restores original, GitWorktreeSandbox rollback from COMMITTED undoes merge, TransactionSandbox rollback from COMMITTED raises SandboxRollbackError about irreversible commit. ISSUES CLOSED: #925 Post-review hardening (PR #1146 review findings): - OverlaySandbox rollback from COMMITTED with no backup (no-op) now skips the merged directory reset entirely, preventing unnecessary unmount/remount or re-copy that could fail and turn a harmless no-op rollback into a SandboxRollbackError during commit_all atomic recovery. - OverlaySandbox rollback no longer double-wraps SandboxRollbackError: the outer except Exception handler now has a preceding except SandboxRollbackError clause that re-raises directly, avoiding a confusing double-wrapped error chain. - CopyOnWriteSandbox.get_path() now accepts ROLLED_BACK status for consistency with OverlaySandbox and the protocol status transition table (ROLLED_BACK -> ACTIVE). - CopyOnWriteSandbox rollback from COMMITTED now resets the sandbox copy from the restored original via rmtree+copytree, preventing stale pre-rollback modifications from being exposed on re-activation. - rollback_all now catches Exception (not just SandboxError) so that unexpected rollback errors do not prevent remaining sandboxes from being rolled back, consistent with the pattern already used in _rollback_committed. - commit_all docstring now documents a thread-safety warning: the method is not safe for concurrent calls on the same plan_id since sandbox commit/rollback runs outside the lock. - Fixed CHANGELOG.md whitespace inconsistencies (double leading spaces on two lines). Post-review hardening round 2 (PR #1146 automated review): - safe_restore now uses os.rename (O(1) atomic rename) instead of shutil.copytree (O(n) recursive copy) for the main restore path, since backup and target are always on the same filesystem. This eliminates the ENOTEMPTY bug where a partial copytree failure left target_path partially populated, causing the recovery os.rename to fail and strand the original in the stale temp directory. - OverlaySandbox.get_path() now transitions ROLLED_BACK to ACTIVE, matching CopyOnWriteSandbox and the protocol transition table (ROLLED_BACK -> ACTIVE). - GitWorktreeSandbox.get_path() now accepts ROLLED_BACK status for consistency with all other sandbox implementations and the protocol transition table (ROLLED_BACK -> ACTIVE). - rollback_all now also handles sandboxes in COMMITTED status (not just ACTIVE), consistent with the state machine allowing COMMITTED -> ROLLED_BACK. - cleanup_all now catches Exception (not just SandboxError) so a single unexpected error does not abort cleanup of remaining sandboxes, consistent with _rollback_committed and rollback_all. - Restructured CHANGELOG entry from a single ~90-line paragraph into structured sub-bullets for readability. - Added BDD scenarios: no-op rollback from COMMITTED for CopyOnWriteSandbox and OverlaySandbox (zero-change commit), commit ordering verification (rollbackable before non-rollbackable). Post-review hardening round 3 (PR #1146 deep automated review): - cleanup_abandoned now catches Exception (not just SandboxError) so that unexpected errors (e.g. raw OSError, PermissionError) do not crash the loop and prevent remaining abandoned sandboxes from being cleaned up, consistent with cleanup_all, rollback_all, and _rollback_committed. - OverlaySandbox._mount_overlay() now catches subprocess.TimeoutExpired (in addition to CalledProcessError and OSError), preventing create() from leaving the sandbox in PENDING status when mount hangs beyond the timeout. - OverlaySandbox._unmount_overlay() now catches subprocess.TimeoutExpired (in addition to CalledProcessError and OSError), preventing cleanup() from leaving the sandbox in a zombie state when umount hangs beyond the timeout. - OverlaySandbox._mount_overlay() validates that overlay paths do not contain commas, which would corrupt the OverlayFS mount options string (comma is the mount option delimiter). - GitWorktreeSandbox.commit() now checks git diff return code so that a failed diff command raises CalledProcessError instead of silently concluding there are no changes and skipping the merge. - safe_restore cleanup of the temporary rollback container now runs in a finally block, preventing a temp directory leak when the rename fails and the exception is re-raised. - Fixed misleading BDD step name: "backup path that will cause copytree to fail" renamed to "backup path that will cause rename to fail" since safe_restore now uses os.rename.
863 lines
36 KiB
Gherkin
863 lines
36 KiB
Gherkin
Feature: Consolidated Sandbox
|
|
Combined scenarios from: copy_on_write_sandbox, cow_sandbox_coverage_boost, no_sandbox_coverage, sandbox_checkpoints, sandbox_copy_on_write_coverage, sandbox_factory_coverage
|
|
|
|
# ============================================================
|
|
# Originally from: copy_on_write_sandbox.feature
|
|
# Feature: Copy-on-write sandbox lifecycle
|
|
# ============================================================
|
|
|
|
Scenario: Create a copy-on-write sandbox
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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 test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
|
|
|
|
Scenario: Rollback from COMMITTED restores original via pre-commit backup
|
|
Given a cow test directory is initialised
|
|
When a cow sandbox is created for plan "plan-rb-committed"
|
|
And the cow existing file "existing.txt" is modified in the sandbox with content "modified-for-commit"
|
|
And the cow sandbox is committed
|
|
Then the cow sandbox should be in the "committed" state
|
|
And the cow file "existing.txt" in the original should have content "modified-for-commit"
|
|
When the cow sandbox is rolled back
|
|
Then the cow sandbox should be in the "rolled_back" state
|
|
And the cow file "existing.txt" in the original should have content "original content"
|
|
|
|
Scenario: Rollback from COMMITTED with no changes is a no-op
|
|
Given a cow test directory is initialised
|
|
When a cow sandbox is created for plan "plan-rb-noop"
|
|
And the cow sandbox is committed
|
|
Then the cow sandbox should be in the "committed" state
|
|
When the cow sandbox is rolled back
|
|
Then the cow sandbox should be in the "rolled_back" state
|
|
And the cow file "existing.txt" in the original should have content "original content"
|
|
|
|
# --- Cleanup ---
|
|
|
|
|
|
Scenario: Cleanup removes the sandbox directory
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
Given a cow test directory is initialised
|
|
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
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: cow_sandbox_coverage_boost.feature
|
|
# Feature: Copy-on-write sandbox coverage boost
|
|
# ============================================================
|
|
|
|
Scenario: Creating a sandbox on a file (not directory) raises SandboxCreationError
|
|
Given a cowcb temporary file instead of a directory
|
|
When a cowcb sandbox is created on that file for plan "plan-err"
|
|
Then a cowcb SandboxCreationError should be raised
|
|
And the cowcb sandbox status should be "errored"
|
|
|
|
# --- get_path with _sandbox_path manually set to None ---
|
|
|
|
|
|
Scenario: get_path raises SandboxStateError when sandbox_path is None
|
|
Given a cowcb sandbox with status CREATED and sandbox_path None
|
|
When cowcb get_path is called with "file.txt"
|
|
Then a cowcb SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# --- commit with _sandbox_path manually set to None ---
|
|
|
|
|
|
Scenario: commit raises SandboxStateError when sandbox_path is None
|
|
Given a cowcb sandbox with status ACTIVE and sandbox_path None
|
|
When cowcb commit is called
|
|
Then a cowcb SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# --- commit triggers OSError during sync ---
|
|
|
|
|
|
Scenario: commit wraps OSError into SandboxCommitError
|
|
Given a cowcb test directory is initialised
|
|
And a cowcb sandbox is created and activated for plan "plan-oserr"
|
|
When the cowcb original directory is removed before commit
|
|
And cowcb commit is called
|
|
Then a cowcb SandboxCommitError should be raised
|
|
And the cowcb sandbox status should be "errored"
|
|
|
|
# --- rollback with _sandbox_path manually set to None ---
|
|
|
|
|
|
Scenario: rollback raises SandboxStateError when sandbox_path is None
|
|
Given a cowcb sandbox with status ACTIVE and sandbox_path None
|
|
When cowcb rollback is called
|
|
Then a cowcb SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# --- rollback triggers OSError during re-copy ---
|
|
|
|
|
|
Scenario: rollback wraps OSError into SandboxRollbackError
|
|
Given a cowcb test directory is initialised
|
|
And a cowcb sandbox is created and activated for plan "plan-rbfail"
|
|
When the cowcb original directory is removed before rollback
|
|
And cowcb rollback is called
|
|
Then a cowcb SandboxRollbackError should be raised
|
|
And the cowcb sandbox status should be "errored"
|
|
|
|
# --- cleanup when _sandbox_path is None ---
|
|
|
|
|
|
Scenario: cleanup succeeds when sandbox_path is None
|
|
Given a cowcb sandbox with status PENDING and sandbox_path None
|
|
When cowcb cleanup is called
|
|
Then the cowcb sandbox status should be "cleaned_up"
|
|
|
|
# --- cleanup when sandbox parent directory does not exist ---
|
|
|
|
|
|
Scenario: cleanup succeeds when sandbox parent directory is already gone
|
|
Given a cowcb test directory is initialised
|
|
And a cowcb sandbox is created for plan "plan-cleanparent"
|
|
When the cowcb sandbox parent directory is removed
|
|
And cowcb cleanup is called
|
|
Then the cowcb sandbox status should be "cleaned_up"
|
|
|
|
# --- commit with dst_dir that needs creation (added file in subdirectory) ---
|
|
|
|
|
|
Scenario: commit creates missing destination subdirectories for added files
|
|
Given a cowcb test directory is initialised
|
|
And a cowcb sandbox is created and activated for plan "plan-subdir"
|
|
When a cowcb file "newdir/nested/added.txt" is created in the sandbox
|
|
And cowcb commit is called
|
|
Then the cowcb commit should succeed
|
|
And the cowcb file "newdir/nested/added.txt" should exist in the original directory
|
|
|
|
# --- commit with file deleted from sandbox that is already missing from original ---
|
|
|
|
|
|
Scenario: commit handles deleted file not present in original
|
|
Given a cowcb test directory is initialised
|
|
And a cowcb sandbox is created and activated for plan "plan-delmissing"
|
|
When the cowcb file "existing.txt" is deleted from both sandbox and original
|
|
And cowcb commit is called
|
|
Then the cowcb commit should succeed
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: no_sandbox_coverage.feature
|
|
# Feature: Non-sandboxed resource behaviour
|
|
# ============================================================
|
|
|
|
Scenario: A non-sandboxed resource rejects an empty resource identifier during setup
|
|
When a non-sandboxed resource is prepared with an empty identifier
|
|
Then the non-sandboxed setup should fail with a resource identifier error
|
|
|
|
|
|
Scenario: A non-sandboxed resource rejects an empty path during setup
|
|
When a non-sandboxed resource is prepared with an empty path
|
|
Then the non-sandboxed setup should fail with a path error
|
|
|
|
# --- Context before creation ---
|
|
|
|
|
|
Scenario: A non-sandboxed resource has no context before it is created
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
Then the non-sandboxed resource should have no context
|
|
|
|
# --- Creation validation ---
|
|
|
|
|
|
Scenario: Creating a non-sandboxed resource with an empty plan identifier is rejected
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
When the non-sandboxed resource is created with an empty plan identifier
|
|
Then the non-sandboxed setup should fail with a plan identifier error
|
|
|
|
|
|
Scenario: Creating a non-sandboxed resource records the correct context
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
When the non-sandboxed resource is created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
Then the non-sandboxed context should reference plan "01JKXYZ1234567890ABCDEFGH"
|
|
And the non-sandboxed context should reference resource "my-api"
|
|
And the non-sandboxed context should use the original path as the sandbox path
|
|
And the non-sandboxed context should have strategy metadata "none"
|
|
|
|
# --- Path resolution ---
|
|
|
|
|
|
Scenario: Resolving a path before the resource is created is rejected
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
When a file path is resolved against the non-sandboxed resource
|
|
Then the non-sandboxed path resolution should be rejected as premature
|
|
|
|
|
|
Scenario: Resolving a path after cleanup is rejected
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created and committed and cleaned up
|
|
When a file path is resolved against the non-sandboxed resource
|
|
Then the non-sandboxed path resolution should be rejected as premature
|
|
|
|
|
|
Scenario: Resolving a path with directory traversal is rejected
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
When a file path containing directory traversal is resolved
|
|
Then the non-sandboxed path resolution should be rejected as unsafe
|
|
|
|
|
|
Scenario: The first path resolution activates a non-sandboxed resource
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
When the file "src/main.py" is resolved against the non-sandboxed resource
|
|
Then the non-sandboxed resource should be in the "active" state
|
|
And the resolved non-sandboxed path should combine the original path and file
|
|
|
|
|
|
Scenario: Subsequent path resolutions keep the resource active
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
And the file "first.py" has been resolved against the non-sandboxed resource
|
|
When the file "second.py" is resolved against the non-sandboxed resource
|
|
Then the non-sandboxed resource should be in the "active" state
|
|
|
|
# --- Commit ---
|
|
|
|
|
|
Scenario: Committing from a pending state is rejected
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
When the non-sandboxed resource is committed
|
|
Then the non-sandboxed commit should be rejected as premature
|
|
|
|
|
|
Scenario: Committing after cleanup is rejected
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created and committed and cleaned up
|
|
When the non-sandboxed resource is committed
|
|
Then the non-sandboxed commit should be rejected as premature
|
|
|
|
|
|
Scenario: A successful commit returns a positive result
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
When the non-sandboxed resource is committed
|
|
Then the non-sandboxed commit result should indicate success
|
|
And the non-sandboxed resource should be in the "committed" state
|
|
|
|
|
|
Scenario: A commit from the active state also succeeds
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
And the file "src/main.py" has been resolved against the non-sandboxed resource
|
|
When the non-sandboxed resource is committed
|
|
Then the non-sandboxed commit result should indicate success
|
|
|
|
# --- Rollback ---
|
|
|
|
|
|
Scenario: Rollback is always rejected for non-sandboxed resources
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
When the non-sandboxed resource is rolled back
|
|
Then the non-sandboxed rollback should be rejected as impossible
|
|
|
|
|
|
Scenario: Rollback is rejected even from the active state
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
And the file "src/main.py" has been resolved against the non-sandboxed resource
|
|
When the non-sandboxed resource is rolled back
|
|
Then the non-sandboxed rollback should be rejected as impossible
|
|
|
|
# --- Cleanup ---
|
|
|
|
|
|
Scenario: Cleaning up an already cleaned resource is harmless
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created and committed and cleaned up
|
|
When the non-sandboxed resource is cleaned up again
|
|
Then the non-sandboxed resource should remain in the "cleaned_up" state
|
|
|
|
|
|
Scenario: Cleanup after commit transitions to cleaned up
|
|
Given a non-sandboxed resource for "my-api" at "/srv/api"
|
|
And the non-sandboxed resource has been created for plan "01JKXYZ1234567890ABCDEFGH"
|
|
And the non-sandboxed resource has been committed
|
|
When the non-sandboxed resource is cleaned up
|
|
Then the non-sandboxed resource should be in the "cleaned_up" state
|
|
|
|
# --- Identity ---
|
|
|
|
|
|
Scenario: Each non-sandboxed resource receives a unique identifier
|
|
Given a non-sandboxed resource for "api-1" at "/srv/api1"
|
|
And another non-sandboxed resource for "api-2" at "/srv/api2"
|
|
Then the two non-sandboxed resources should have different identifiers
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: sandbox_checkpoints.feature
|
|
# Feature: Sandbox Checkpoint and Rollback Hooks
|
|
# ============================================================
|
|
|
|
Scenario: Checkpoint created before plan execution
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
When I create a checkpoint with phase "pre_execute" and plan "plan-001"
|
|
Then a checkpoint should be returned
|
|
And the checkpoint phase should be "pre_execute"
|
|
And the checkpoint plan_id should be "plan-001"
|
|
And the checkpoint sandbox_id should match the sandbox
|
|
|
|
|
|
Scenario: Checkpoint created after successful execution
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
When I create a checkpoint with phase "post_execute" and plan "plan-001"
|
|
And I attach metadata "status" as "success"
|
|
Then a checkpoint should be returned
|
|
And the checkpoint phase should be "post_execute"
|
|
And the checkpoint metadata should contain key "status" with value "success"
|
|
|
|
# Rollback
|
|
|
|
|
|
Scenario: Rollback restores sandbox state on failure
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
And a checkpoint exists for the sandbox with phase "pre_execute" and plan "plan-001"
|
|
When I modify the sandbox files
|
|
And I rollback to the checkpoint
|
|
Then the rollback should succeed
|
|
And the sandbox files should match the original state
|
|
|
|
|
|
Scenario: Rollback returns false when snapshot path is missing
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
And a checkpoint with an invalid snapshot path
|
|
When I attempt to rollback to the invalid checkpoint
|
|
Then the rollback should fail
|
|
|
|
# Listing
|
|
|
|
|
|
Scenario: Multiple checkpoints maintain order
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
When I create a checkpoint with phase "pre_execute" and plan "plan-001"
|
|
And I create a checkpoint with phase "post_execute" and plan "plan-001"
|
|
And I create a checkpoint with phase "pre_apply" and plan "plan-001"
|
|
Then listing checkpoints for the sandbox should return 3 checkpoints
|
|
And the checkpoints should be in creation order
|
|
|
|
# Metadata
|
|
|
|
|
|
Scenario: Checkpoint metadata captures phase and plan information
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
When I create a checkpoint with phase "pre_execute" and plan "plan-002"
|
|
Then the checkpoint should have a valid ULID checkpoint_id
|
|
And the checkpoint should have a created_at timestamp
|
|
And the checkpoint should have a non-empty snapshot_path
|
|
|
|
# Deletion
|
|
|
|
|
|
Scenario: Deleting a checkpoint removes it from the list
|
|
Given a checkpoint manager
|
|
Given a sandbox with files in a temporary directory
|
|
And a checkpoint exists for the sandbox with phase "pre_execute" and plan "plan-001"
|
|
When I delete the checkpoint
|
|
Then the deletion should succeed
|
|
And listing checkpoints for the sandbox should return 0 checkpoints
|
|
|
|
|
|
Scenario: Deleting a non-existent checkpoint returns false
|
|
Given a checkpoint manager
|
|
When I delete a checkpoint with id "nonexistent-id"
|
|
Then the deletion should fail
|
|
|
|
# Plan executor integration (optional checkpoint manager)
|
|
|
|
|
|
Scenario: PlanExecutor works without checkpoint manager
|
|
Given a checkpoint manager
|
|
Given a plan lifecycle service
|
|
And a plan executor without checkpoint manager
|
|
Then the plan executor checkpoint_manager should be None
|
|
|
|
|
|
Scenario: PlanExecutor accepts optional checkpoint manager
|
|
Given a checkpoint manager
|
|
Given a plan lifecycle service
|
|
And a plan executor with a checkpoint manager
|
|
Then the plan executor checkpoint_manager should not be None
|
|
|
|
# PlanApplyService integration
|
|
|
|
|
|
Scenario: PlanApplyService works without checkpoint manager
|
|
Given a checkpoint manager
|
|
Given a plan lifecycle service
|
|
And a plan apply service without checkpoint manager
|
|
Then the plan apply service checkpoint_manager should be None
|
|
|
|
|
|
Scenario: PlanApplyService accepts optional checkpoint manager
|
|
Given a checkpoint manager
|
|
Given a plan lifecycle service
|
|
And a plan apply service with a checkpoint manager
|
|
Then the plan apply service checkpoint_manager should not be None
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: sandbox_copy_on_write_coverage.feature
|
|
# Feature: CopyOnWriteSandbox uncovered lines and branches
|
|
# ============================================================
|
|
|
|
Scenario: create wraps OSError from copytree into SandboxCreationError
|
|
Given a cowcov test directory is initialised
|
|
And shutil.copytree is patched to raise OSError in cowcov
|
|
When a cowcov sandbox create is attempted for plan "plan-oserr"
|
|
Then a cowcov SandboxCreationError should be raised
|
|
And the cowcov error message should contain "Failed to copy directory"
|
|
And the cowcov sandbox status should be "errored"
|
|
|
|
# ------------------------------------------------------------------
|
|
# get_path() — second call when already ACTIVE (line 196 False branch)
|
|
# Covers missing branch 196 → 199
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: get_path called twice stays in ACTIVE status
|
|
Given a cowcov test directory is initialised
|
|
And a cowcov sandbox is created for plan "plan-active"
|
|
When cowcov get_path is called with "file1.txt"
|
|
And cowcov get_path is called with "file2.txt"
|
|
Then the cowcov sandbox status should be "active"
|
|
And both cowcov resolved paths should be valid
|
|
|
|
# ------------------------------------------------------------------
|
|
# get_path() — _sandbox_path is None with ACTIVE status
|
|
# Covers line 200 and branch 196 False + 199 True
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: get_path raises SandboxStateError when ACTIVE but sandbox_path is None
|
|
Given a cowcov sandbox forced to ACTIVE with sandbox_path None
|
|
When cowcov get_path is attempted with "file.txt"
|
|
Then a cowcov SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# ------------------------------------------------------------------
|
|
# get_path() — _sandbox_path is None with CREATED status
|
|
# Covers line 200 and branch 199 True
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: get_path raises SandboxStateError when CREATED but sandbox_path is None
|
|
Given a cowcov sandbox forced to CREATED with sandbox_path None
|
|
When cowcov get_path is attempted with "data.csv"
|
|
Then a cowcov SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# ------------------------------------------------------------------
|
|
# commit() — _sandbox_path is None
|
|
# Covers line 230
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: commit raises SandboxStateError when sandbox_path is None
|
|
Given a cowcov sandbox forced to ACTIVE with sandbox_path None
|
|
When cowcov commit is attempted
|
|
Then a cowcov SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# ------------------------------------------------------------------
|
|
# commit() — dst_dir is empty (branch 242 False → 244)
|
|
# Covers missing branch at line 242
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: commit skips makedirs when dst_dir is empty string
|
|
Given a cowcov test directory is initialised
|
|
And a cowcov sandbox is created for plan "plan-dstdir"
|
|
And cowcov _compute_diff is patched to return a changed root file
|
|
And cowcov os.path.dirname is patched to return empty string
|
|
And cowcov shutil.copy2 is patched to no-op
|
|
When cowcov commit is attempted
|
|
Then the cowcov commit should succeed
|
|
And cowcov os.makedirs should not have been called
|
|
|
|
# ------------------------------------------------------------------
|
|
# commit() — deleted file not present in original (branch 249 False)
|
|
# Covers missing branch at line 249 → back to 247
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: commit handles deletion when file already missing from original
|
|
Given a cowcov test directory is initialised
|
|
And a cowcov sandbox is created and activated for plan "plan-delmiss"
|
|
When the cowcov file "existing.txt" is deleted from both sandbox and original
|
|
And cowcov commit is attempted
|
|
Then the cowcov commit should succeed
|
|
|
|
# ------------------------------------------------------------------
|
|
# commit() — OSError during sync wraps into SandboxCommitError
|
|
# Covers lines 252-254
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: commit wraps OSError during file sync into SandboxCommitError
|
|
Given a cowcov test directory is initialised
|
|
And a cowcov sandbox is created and activated for plan "plan-syncerr"
|
|
And a cowcov file "existing.txt" is modified in the sandbox
|
|
And cowcov shutil.copy2 is patched to raise OSError
|
|
When cowcov commit is attempted
|
|
Then a cowcov SandboxCommitError should be raised
|
|
And the cowcov sandbox status should be "errored"
|
|
|
|
# ------------------------------------------------------------------
|
|
# rollback() — _sandbox_path is None
|
|
# Covers line 297
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: rollback raises SandboxStateError when sandbox_path is None
|
|
Given a cowcov sandbox forced to ACTIVE with sandbox_path None
|
|
When cowcov rollback is attempted
|
|
Then a cowcov SandboxStateError should be raised with message "Sandbox path not set"
|
|
|
|
# ------------------------------------------------------------------
|
|
# rollback() — OSError during re-copy wraps into SandboxRollbackError
|
|
# Covers lines 311-313
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: rollback wraps OSError from copytree into SandboxRollbackError
|
|
Given a cowcov test directory is initialised
|
|
And a cowcov sandbox is created and activated for plan "plan-rbfail"
|
|
And cowcov shutil.copytree is patched to raise OSError for rollback
|
|
When cowcov rollback is attempted
|
|
Then a cowcov SandboxRollbackError should be raised
|
|
And the cowcov sandbox status should be "errored"
|
|
|
|
# ------------------------------------------------------------------
|
|
# cleanup() — _sandbox_path is None (branch 341 False → 347)
|
|
# Covers missing branch at line 341
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: cleanup succeeds when sandbox_path was never set
|
|
Given a cowcov sandbox in PENDING state with sandbox_path None
|
|
When cowcov cleanup is called
|
|
Then the cowcov sandbox status should be "cleaned_up"
|
|
|
|
# ------------------------------------------------------------------
|
|
# cleanup() — parent directory already gone (branch 344 False → 347)
|
|
# Covers missing branch at line 344
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
Scenario: cleanup succeeds when parent directory is already removed
|
|
Given a cowcov test directory is initialised
|
|
And a cowcov sandbox is created for plan "plan-cleanmissing"
|
|
And the cowcov sandbox parent directory is manually removed
|
|
When cowcov cleanup is called
|
|
Then the cowcov sandbox status should be "cleaned_up"
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: sandbox_factory_coverage.feature
|
|
# Feature: Sandbox factory strategy routing
|
|
# ============================================================
|
|
|
|
Scenario: The factory rejects an empty resource identifier
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested with an empty resource identifier
|
|
Then the factory should reject the request due to a missing resource identifier
|
|
|
|
|
|
Scenario: The factory rejects an empty resource path
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested with an empty resource path
|
|
Then the factory should reject the request due to a missing resource path
|
|
|
|
# --- None strategy (passthrough) ---
|
|
|
|
|
|
Scenario: The none strategy produces a passthrough sandbox
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "web-api" at "/srv/api" using the none strategy
|
|
Then the factory should produce a passthrough sandbox
|
|
And the produced sandbox should be in the pending state
|
|
|
|
# --- Implemented strategies ---
|
|
|
|
|
|
Scenario: The git worktree strategy produces a git worktree sandbox
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "repo-1" at "/src/repo" using the git worktree strategy
|
|
Then the factory should produce a git worktree sandbox
|
|
And the produced git worktree sandbox should be in the pending state
|
|
|
|
|
|
Scenario: The copy-on-write strategy produces a copy-on-write sandbox
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "docs" at "/srv/docs" using the copy-on-write strategy
|
|
Then the factory should produce a copy-on-write sandbox
|
|
And the produced copy-on-write sandbox should be in the pending state
|
|
|
|
# --- Unimplemented strategies ---
|
|
|
|
|
|
Scenario: The transaction rollback strategy creates a TransactionSandbox
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "db-main" at "postgres://db/main" using the transaction rollback strategy
|
|
Then the factory should produce a transaction sandbox in the pending state
|
|
|
|
|
|
Scenario: The snapshot strategy is not yet available
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "corpus" at "/srv/corpus" using the snapshot strategy
|
|
Then the factory should indicate the strategy is not yet implemented
|
|
|
|
# --- Unknown strategy ---
|
|
|
|
|
|
Scenario: An unrecognised strategy name is rejected
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "misc" at "/tmp/misc" using an unrecognised strategy
|
|
Then the factory should reject the request due to an unknown strategy
|
|
|
|
|
|
Scenario: The overlay strategy is supported
|
|
Given the sandbox factory is available
|
|
When the factory is asked whether the overlay strategy is supported
|
|
Then the factory should confirm the strategy is supported
|
|
|
|
|
|
Scenario: The versioning strategy is now unknown and rejected
|
|
Given the sandbox factory is available
|
|
When a sandbox is requested for resource "corpus" at "/srv/corpus" using the versioning strategy
|
|
Then the factory should reject the request due to an unknown strategy
|
|
|
|
# --- Support checks ---
|
|
|
|
|
|
Scenario: The none strategy is reported as supported
|
|
Given the sandbox factory is available
|
|
When the factory is asked whether the none strategy is supported
|
|
Then the factory should confirm the strategy is supported
|
|
|
|
|
|
Scenario: The git worktree strategy is reported as supported
|
|
Given the sandbox factory is available
|
|
When the factory is asked whether the git worktree strategy is supported
|
|
Then the factory should confirm the strategy is supported
|
|
|
|
|
|
Scenario: The copy-on-write strategy is reported as supported
|
|
Given the sandbox factory is available
|
|
When the factory is asked whether the copy-on-write strategy is supported
|
|
Then the factory should confirm the strategy is supported
|
|
|
|
|
|
Scenario: An unrecognised strategy is reported as unsupported
|
|
Given the sandbox factory is available
|
|
When the factory is asked whether an unrecognised strategy is supported
|
|
Then the factory should indicate the strategy is not supported
|
|
|
|
# --- Resource type strategy lookup (spec-aligned types) ---
|
|
|
|
|
|
Scenario: A git-checkout resource supports worktree, copy-on-write, and none
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for a "git-checkout" resource are queried
|
|
Then the factory should return git worktree, copy-on-write, and none as compatible
|
|
|
|
|
|
Scenario: A git resource supports only the none strategy
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for a "git" resource are queried
|
|
Then the factory should return only the none strategy as compatible
|
|
|
|
|
|
Scenario: A fs-mount resource supports copy-on-write, overlay, and none
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for a "fs-mount" resource are queried
|
|
Then the factory should return copy-on-write, overlay, and none as compatible
|
|
|
|
|
|
Scenario: A fs-directory resource supports copy-on-write, overlay, and none
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for a "fs-directory" resource are queried
|
|
Then the factory should return copy-on-write, overlay, and none as compatible
|
|
|
|
|
|
Scenario: A fs-file resource supports copy-on-write, overlay, and none
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for a "fs-file" resource are queried
|
|
Then the factory should return copy-on-write, overlay, and none as compatible
|
|
|
|
|
|
Scenario: An api_endpoint resource supports only the none strategy
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for an "api_endpoint" resource are queried
|
|
Then the factory should return only the none strategy as compatible
|
|
|
|
|
|
Scenario: An unknown resource type falls back to the none strategy
|
|
Given the sandbox factory is available
|
|
When the compatible strategies for an unknown resource type are queried
|
|
Then the factory should return only the none strategy as compatible
|
|
|