Files
cleveragents-core/features/checkpoint_manager_coverage.feature
HAL9000 beceb183d0 fix(sandbox): store sandbox_path in checkpoint metadata to enable rollback
CheckpointManager.create_checkpoint() computed sandbox_path from
sandbox.context.sandbox_path but never stored it in the metadata dict.
This caused rollback_to() to always find metadata.get('sandbox_path')
returning None, silently skip the rollback, and return False.

The fix adds sandbox_path to the metadata dict immediately after it is
resolved from the sandbox context, before the SandboxCheckpoint is
constructed. rollback_to() can now retrieve the path and correctly
restore the sandbox filesystem state.

Added two new BDD scenarios to checkpoint_manager_coverage.feature:
- Verifies sandbox_path is automatically stored in metadata on create
- Verifies rollback succeeds without manually supplying sandbox_path

ISSUES CLOSED: #7488
2026-04-17 18:33:39 +00:00

213 lines
11 KiB
Gherkin
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Feature: CheckpointManager full coverage
As a developer
I want thorough tests for every method, branch, and edge case in checkpoint.py
So that line-rate and branch-rate reach 1.0
# ---------------------------------------------------------------------------
# SandboxCheckpoint Pydantic model
# ---------------------------------------------------------------------------
Scenario: SandboxCheckpoint model can be constructed with all fields
Given I build a SandboxCheckpoint with valid fields
Then the constructed checkpoint model should expose all field values
And the constructed checkpoint model should be frozen
Scenario: SandboxCheckpoint model uses empty dict as default metadata
Given I build a SandboxCheckpoint without explicit metadata
Then the constructed checkpoint metadata should be an empty dict
# ---------------------------------------------------------------------------
# Checkpointable protocol
# ---------------------------------------------------------------------------
Scenario: An object satisfying Checkpointable passes isinstance check
Given a mock object with sandbox_id and context properties
Then the mock object should satisfy the Checkpointable protocol
Scenario: An object missing sandbox_id fails Checkpointable isinstance check
Given a mock object without a sandbox_id property
Then the mock object should not satisfy the Checkpointable protocol
# ---------------------------------------------------------------------------
# CheckpointManager.__init__
# ---------------------------------------------------------------------------
Scenario: CheckpointManager initialises with empty state
Given a freshly created CheckpointManager
Then the manager internal checkpoints dict should be empty
And the manager should have a threading lock
# ---------------------------------------------------------------------------
# create_checkpoint context is None (metadata-only)
# ---------------------------------------------------------------------------
Scenario: Creating a checkpoint when sandbox context is None produces a metadata-only snapshot
Given a freshly created CheckpointManager
And a mock sandbox whose context is None
When I invoke create_checkpoint with plan "plan-m1" phase "pre_execute" and no metadata
Then the returned checkpoint should have a non-empty snapshot_path directory
And the returned checkpoint sandbox_id should equal the mock sandbox id
Scenario: Creating a checkpoint with explicit metadata converts values to strings
Given a freshly created CheckpointManager
And a mock sandbox whose context is None
When I invoke create_checkpoint with plan "plan-m2" phase "post_execute" and numeric metadata
Then the returned checkpoint metadata values should all be strings
Scenario: Creating a checkpoint with None metadata defaults to empty dict
Given a freshly created CheckpointManager
And a mock sandbox whose context is None
When I invoke create_checkpoint with plan "plan-m3" phase "pre_apply" and None metadata
Then the returned checkpoint metadata should be an empty dict
# ---------------------------------------------------------------------------
# create_checkpoint context with sandbox_path
# ---------------------------------------------------------------------------
Scenario: Creating a checkpoint with a real sandbox path snapshots the directory
Given a freshly created CheckpointManager
And a temporary sandbox directory with sample files
And a mock sandbox whose context points to the temporary directory
When I invoke create_checkpoint with plan "plan-s1" phase "pre_execute" and no metadata
Then the snapshot directory should contain copies of the sample files
Scenario: Creating a checkpoint with a real sandbox path stores sandbox_path in metadata
Given a freshly created CheckpointManager
And a temporary sandbox directory with sample files
And a mock sandbox whose context points to the temporary directory
When I invoke create_checkpoint with plan "plan-s2" phase "pre_execute" and no metadata
Then the returned checkpoint metadata should contain the sandbox path
Scenario: Rollback succeeds without manually supplying sandbox_path in metadata
Given a freshly created CheckpointManager
And a temporary sandbox directory with sample files
And a mock sandbox whose context points to the temporary directory
And a checkpoint was created for the temporary sandbox without explicit sandbox_path metadata
And the temporary sandbox files are then modified
When I invoke rollback_to on the created checkpoint
Then the rollback result should be true
And the temporary sandbox should contain the original files
# ---------------------------------------------------------------------------
# _snapshot_directory branches
# ---------------------------------------------------------------------------
Scenario: _snapshot_directory with None path creates an empty snapshot dir
Given a freshly created CheckpointManager
When I call _snapshot_directory with a None sandbox_path
Then the returned snapshot should be an existing empty directory
Scenario: _snapshot_directory with non-existent path creates an empty snapshot dir
Given a freshly created CheckpointManager
When I call _snapshot_directory with a non-existent sandbox_path
Then the returned snapshot should be an existing empty directory
Scenario: _snapshot_directory with a valid path copies the tree
Given a freshly created CheckpointManager
And a temporary sandbox directory with sample files
When I call _snapshot_directory with the temporary sandbox_path
Then the returned snapshot should contain the sample files
Scenario: _snapshot_directory raises SandboxError when copytree fails
Given a freshly created CheckpointManager
And a temporary sandbox directory with sample files
When I call _snapshot_directory with a path that triggers OSError
Then a SandboxError should have been raised
# ---------------------------------------------------------------------------
# _cleanup_snapshot branches
# ---------------------------------------------------------------------------
Scenario: _cleanup_snapshot with empty string is a no-op
Given a freshly created CheckpointManager
When I call _cleanup_snapshot with an empty string
Then no error should occur from cleanup_snapshot
Scenario: _cleanup_snapshot removes parent directory when it exists
Given a freshly created CheckpointManager
And a temporary snapshot directory tree for cleanup
When I call _cleanup_snapshot with the temporary snapshot path
Then the temporary snapshot parent should no longer exist
Scenario: _cleanup_snapshot with non-existent parent is a no-op
Given a freshly created CheckpointManager
When I call _cleanup_snapshot with a non-existent path
Then no error should occur from cleanup_snapshot
# ---------------------------------------------------------------------------
# rollback_to all branches
# ---------------------------------------------------------------------------
Scenario: Rollback returns false when snapshot_path is empty string
Given a freshly created CheckpointManager
And a SandboxCheckpoint with empty snapshot_path
When I invoke rollback_to on the crafted checkpoint
Then the rollback result should be false
Scenario: Rollback returns false when snapshot_path does not exist on disk
Given a freshly created CheckpointManager
And a SandboxCheckpoint with a non-existent snapshot_path
When I invoke rollback_to on the crafted checkpoint
Then the rollback result should be false
Scenario: Rollback returns false when sandbox_path is missing from metadata
Given a freshly created CheckpointManager
And a SandboxCheckpoint whose snapshot exists but metadata has no sandbox_path
When I invoke rollback_to on the crafted checkpoint
Then the rollback result should be false
Scenario: Rollback returns false when sandbox_path directory does not exist
Given a freshly created CheckpointManager
And a SandboxCheckpoint whose snapshot exists but sandbox_path points nowhere
When I invoke rollback_to on the crafted checkpoint
Then the rollback result should be false
Scenario: Rollback succeeds and restores files and subdirectories
Given a freshly created CheckpointManager
And a temporary sandbox directory with sample files
And a mock sandbox whose context points to the temporary directory
And a checkpoint was created for the temporary sandbox
And the temporary sandbox files are then modified
When I invoke rollback_to on the created checkpoint
Then the rollback result should be true
And the temporary sandbox should contain the original files
Scenario: Rollback handles OSError during restore gracefully
Given a freshly created CheckpointManager
And a SandboxCheckpoint whose snapshot and sandbox exist but restore will fail
When I invoke rollback_to on the crafted checkpoint
Then the rollback result should be false
# ---------------------------------------------------------------------------
# list_checkpoints branches
# ---------------------------------------------------------------------------
Scenario: Listing checkpoints for unknown sandbox returns empty list
Given a freshly created CheckpointManager
When I invoke list_checkpoints for sandbox "unknown-sb-99"
Then the checkpoint list should be empty
Scenario: Listing checkpoints returns all created checkpoints in order
Given a freshly created CheckpointManager
And a mock sandbox whose context is None
And I create two checkpoints for the mock sandbox
When I invoke list_checkpoints for the mock sandbox
Then the checkpoint list should have 2 entries in creation order
# ---------------------------------------------------------------------------
# delete_checkpoint branches
# ---------------------------------------------------------------------------
Scenario: Deleting a known checkpoint returns true and removes it
Given a freshly created CheckpointManager
And a mock sandbox whose context is None
And a single checkpoint is created for the mock sandbox
When I invoke delete_checkpoint with the created checkpoint id
Then the checkpoint delete result should be true
And listing checkpoints for the mock sandbox should return 0 entries
Scenario: Deleting an unknown checkpoint id returns false
Given a freshly created CheckpointManager
When I invoke delete_checkpoint with id "no-such-cp-id"
Then the checkpoint delete result should be false