c9abb45adf
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 18s
CI / security (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 55s
CI / integration_tests (pull_request) Successful in 3m28s
CI / unit_tests (pull_request) Successful in 10m38s
CI / docker (pull_request) Successful in 16s
CI / benchmark-regression (pull_request) Successful in 20m39s
CI / coverage (pull_request) Successful in 41m22s
CI / lint (push) Successful in 13s
CI / quality (push) Successful in 17s
CI / build (push) Successful in 22s
CI / typecheck (push) Successful in 29s
CI / security (push) Successful in 29s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 2m40s
CI / unit_tests (push) Successful in 11m4s
CI / docker (push) Successful in 1m5s
CI / benchmark-publish (push) Successful in 12m10s
CI / coverage (push) Successful in 1h7m46s
Added 246 new BDD scenarios across 9 feature files to improve unit test coverage for modules that were either entirely untested or had significant coverage gaps: - lock_service_coverage.feature (27 scenarios): validation branches, TTL boundaries, re-entrant acquisition, rollback on exceptions - plan_apply_service_coverage.feature (54 scenarios): operation labels, diff rendering (plain/rich/json), artifact building, validation gate, changeset resolution and cleanup - plan_executor_coverage.feature (51 scenarios): step parsing, execute actor integration, strategize/execute guards, stub retry/recovery, decision tree construction - skill_cli_coverage_r3.feature (22 scenarios): tools refresh, list/show JSON fallback, capability summary errors, remove confirmation - changeset_repository_coverage.feature (39 scenarios): entry/tool repos validation, database error wrapping, domain conversion, SQLite store CRUD operations - repositories_coverage.feature (20 scenarios): get_by_name/namespace errors, list_available filters, delete with ActionInUseError, plan update with invariants/processing_state/error_details - sandbox_copy_on_write_coverage.feature (12 scenarios): create OSError wrapping, get_path state transitions, commit edge cases, rollback errors, cleanup with missing paths - bridge_coverage.feature (8 scenarios): __del__ suppression, async task cancellation, execute_graph message type handling, stream config, state checkpointer - plan_cli_coverage.feature (13 scenarios): legacy apply/list/cd paths, use-action with estimation/invariant actors, lifecycle-apply guards, status errors, error recovery display All 246 scenarios (1105 steps) pass. Step definitions use unique prefixes to prevent ambiguous step conflicts with existing tests. ISSUES CLOSED: #467
242 lines
13 KiB
Gherkin
242 lines
13 KiB
Gherkin
@unit
|
|
Feature: ChangeSet repository edge cases and error coverage
|
|
As a developer maintaining changeset persistence
|
|
I want all error paths, validation, and edge cases covered
|
|
So that the changeset repository is reliable and well-tested
|
|
|
|
# ------ ChangeSetEntryRepository validation ------
|
|
|
|
Scenario: ChangeSetEntryRepository save_entry rejects empty changeset_id
|
|
Given I have a coverage ChangeSetEntryRepository
|
|
When I try to coverage-save an entry with empty changeset_id
|
|
Then a coverage ValueError should be raised with message "changeset_id must not be empty"
|
|
|
|
Scenario: ChangeSetEntryRepository save_entry rejects non-ChangeEntry
|
|
Given I have a coverage ChangeSetEntryRepository
|
|
When I try to coverage-save a non-ChangeEntry object
|
|
Then a coverage TypeError should be raised with message "entry must be a ChangeEntry instance"
|
|
|
|
Scenario: ChangeSetEntryRepository get_entries_for_changeset rejects empty changeset_id
|
|
Given I have a coverage ChangeSetEntryRepository
|
|
When I try to coverage-get entries for empty changeset_id
|
|
Then a coverage ValueError should be raised with message "changeset_id must not be empty"
|
|
|
|
Scenario: ChangeSetEntryRepository get_entries_for_plan rejects empty plan_id
|
|
Given I have a coverage ChangeSetEntryRepository
|
|
When I try to coverage-get entries for empty plan_id
|
|
Then a coverage ValueError should be raised with message "plan_id must not be empty"
|
|
|
|
Scenario: ChangeSetEntryRepository delete_for_changeset rejects empty changeset_id
|
|
Given I have a coverage ChangeSetEntryRepository
|
|
When I try to coverage-delete entries for empty changeset_id
|
|
Then a coverage ValueError should be raised with message "changeset_id must not be empty"
|
|
|
|
Scenario: ChangeSetEntryRepository delete_for_plan rejects empty plan_id
|
|
Given I have a coverage ChangeSetEntryRepository
|
|
When I try to coverage-delete entries for empty plan_id
|
|
Then a coverage ValueError should be raised with message "plan_id must not be empty"
|
|
|
|
# ------ ChangeSetEntryRepository database errors ------
|
|
|
|
Scenario: ChangeSetEntryRepository save_entry raises DatabaseError on OperationalError
|
|
Given I have a coverage ChangeSetEntryRepository with a broken session
|
|
When I try to coverage-save an entry that triggers an OperationalError
|
|
Then a coverage DatabaseError should be raised with message "Failed to save changeset entry"
|
|
|
|
Scenario: ChangeSetEntryRepository get_entries_for_changeset raises DatabaseError
|
|
Given I have a coverage ChangeSetEntryRepository with a broken query session
|
|
When I try to coverage-get entries for changeset "cs-broken"
|
|
Then a coverage DatabaseError should be raised with message "Failed to get entries"
|
|
|
|
Scenario: ChangeSetEntryRepository get_entries_for_plan raises DatabaseError
|
|
Given I have a coverage ChangeSetEntryRepository with a broken query session
|
|
When I try to coverage-get entries for plan "plan-broken"
|
|
Then a coverage DatabaseError should be raised with message "Failed to get entries for plan"
|
|
|
|
Scenario: ChangeSetEntryRepository delete_for_changeset raises DatabaseError
|
|
Given I have a coverage ChangeSetEntryRepository with a broken delete session
|
|
When I try to coverage-delete entries for changeset "cs-broken"
|
|
Then a coverage DatabaseError should be raised with message "Failed to delete entries"
|
|
|
|
Scenario: ChangeSetEntryRepository delete_for_plan raises DatabaseError
|
|
Given I have a coverage ChangeSetEntryRepository with a broken delete session
|
|
When I try to coverage-delete entries for plan "plan-broken"
|
|
Then a coverage DatabaseError should be raised with message "Failed to delete entries"
|
|
|
|
# ------ ChangeSetEntryRepository _to_domain edge cases ------
|
|
|
|
Scenario: ChangeSetEntryRepository _to_domain with null hashes and modes
|
|
Given I have a coverage ChangeSetEntryModel row with null hashes and modes
|
|
When I convert the coverage row to a domain ChangeEntry
|
|
Then the domain entry before_hash should be None
|
|
And the domain entry after_hash should be None
|
|
And the domain entry before_mode should be None
|
|
And the domain entry after_mode should be None
|
|
|
|
Scenario: ChangeSetEntryRepository _to_domain with populated hashes and modes
|
|
Given I have a coverage ChangeSetEntryModel row with hashes and modes
|
|
When I convert the coverage row to a domain ChangeEntry
|
|
Then the domain entry before_hash should be "abc123"
|
|
And the domain entry after_hash should be "def456"
|
|
And the domain entry before_mode should be 33188
|
|
And the domain entry after_mode should be 33261
|
|
|
|
Scenario: ChangeSetEntryRepository _to_domain with empty string hashes
|
|
Given I have a coverage ChangeSetEntryModel row with empty string hashes
|
|
When I convert the coverage row to a domain ChangeEntry
|
|
Then the domain entry before_hash should be None
|
|
And the domain entry after_hash should be None
|
|
|
|
# ------ ToolInvocationRepository validation ------
|
|
|
|
Scenario: ToolInvocationRepository save_invocation rejects non-ToolInvocation
|
|
Given I have a coverage ToolInvocationRepository
|
|
When I try to coverage-save a non-ToolInvocation object
|
|
Then a coverage TypeError should be raised with message "invocation must be a ToolInvocation instance"
|
|
|
|
Scenario: ToolInvocationRepository get_invocations_for_plan rejects empty plan_id
|
|
Given I have a coverage ToolInvocationRepository
|
|
When I try to coverage-get invocations for empty plan_id
|
|
Then a coverage ValueError should be raised with message "plan_id must not be empty"
|
|
|
|
Scenario: ToolInvocationRepository delete_for_plan rejects empty plan_id
|
|
Given I have a coverage ToolInvocationRepository
|
|
When I try to coverage-delete invocations for empty plan_id
|
|
Then a coverage ValueError should be raised with message "plan_id must not be empty"
|
|
|
|
# ------ ToolInvocationRepository database errors ------
|
|
|
|
Scenario: ToolInvocationRepository save_invocation raises DatabaseError on OperationalError
|
|
Given I have a coverage ToolInvocationRepository with a broken session
|
|
When I try to coverage-save an invocation that triggers an OperationalError
|
|
Then a coverage DatabaseError should be raised with message "Failed to save tool invocation"
|
|
|
|
Scenario: ToolInvocationRepository get_invocations_for_plan raises DatabaseError
|
|
Given I have a coverage ToolInvocationRepository with a broken query session
|
|
When I try to coverage-get invocations for plan "plan-broken"
|
|
Then a coverage DatabaseError should be raised with message "Failed to get invocations"
|
|
|
|
Scenario: ToolInvocationRepository delete_for_plan raises DatabaseError
|
|
Given I have a coverage ToolInvocationRepository with a broken delete session
|
|
When I try to coverage-delete invocations for plan "plan-broken"
|
|
Then a coverage DatabaseError should be raised with message "Failed to delete invocations"
|
|
|
|
# ------ ToolInvocationRepository save_invocation optional fields ------
|
|
|
|
Scenario: ToolInvocationRepository saves invocation with all optional fields populated
|
|
Given I have a coverage ToolInvocationRepository with real session
|
|
When I coverage-save an invocation with result, completed_at, and provider_metadata
|
|
Then the coverage-saved invocation should round-trip with all fields intact
|
|
|
|
Scenario: ToolInvocationRepository saves invocation with all optional fields as None
|
|
Given I have a coverage ToolInvocationRepository with real session
|
|
When I coverage-save an invocation with no result, no completed_at, and no provider_metadata
|
|
Then the coverage-saved invocation should round-trip with None optional fields
|
|
|
|
Scenario: ToolInvocationRepository saves invocation with changeset_id as None
|
|
Given I have a coverage ToolInvocationRepository with real session
|
|
When I coverage-save an invocation without changeset_id
|
|
Then the coverage-saved invocation for plan should be retrievable
|
|
|
|
# ------ ToolInvocationRepository _to_domain edge cases ------
|
|
|
|
Scenario: ToolInvocationRepository _to_domain with null JSON fields
|
|
Given I have a coverage ToolInvocationModel row with null JSON fields
|
|
When I convert the coverage row to a domain ToolInvocation
|
|
Then the domain invocation arguments should be an empty dict
|
|
And the domain invocation result should be None
|
|
And the domain invocation change_ids should be an empty list
|
|
And the domain invocation resource_refs should be an empty list
|
|
And the domain invocation provider_metadata should be None
|
|
|
|
Scenario: ToolInvocationRepository _to_domain with populated JSON fields
|
|
Given I have a coverage ToolInvocationModel row with populated JSON fields
|
|
When I convert the coverage row to a domain ToolInvocation
|
|
Then the domain invocation arguments should have key "path"
|
|
And the domain invocation result should have key "status"
|
|
And the domain invocation change_ids should have 2 entries
|
|
And the domain invocation resource_refs should have 1 entry
|
|
And the domain invocation provider_metadata should have key "model"
|
|
|
|
Scenario: ToolInvocationRepository _to_domain with null duration_ms and sequence_number
|
|
Given I have a coverage ToolInvocationModel row with null numeric fields
|
|
When I convert the coverage row to a domain ToolInvocation
|
|
Then the domain invocation duration_ms should be 0.0
|
|
And the domain invocation sequence_number should be 0
|
|
|
|
Scenario: ToolInvocationRepository _to_domain with null completed_at
|
|
Given I have a coverage ToolInvocationModel row with null completed_at
|
|
When I convert the coverage row to a domain ToolInvocation
|
|
Then the domain invocation completed_at should be None
|
|
|
|
Scenario: ToolInvocationRepository _to_domain with populated completed_at
|
|
Given I have a coverage ToolInvocationModel row with populated completed_at
|
|
When I convert the coverage row to a domain ToolInvocation
|
|
Then the domain invocation completed_at should be a datetime
|
|
|
|
# ------ SqliteChangeSetStore edge cases ------
|
|
|
|
Scenario: SqliteChangeSetStore get returns None for empty changeset_id
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-get a changeset with empty string
|
|
Then the coverage-get result should be None
|
|
|
|
Scenario: SqliteChangeSetStore get returns empty SpecChangeSet for known ID without entries
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-start a changeset for plan "plan-known"
|
|
And I coverage-get the started changeset
|
|
Then the coverage-get result should be a SpecChangeSet with 0 entries
|
|
And the coverage-get result plan_id should be "plan-known"
|
|
|
|
Scenario: SqliteChangeSetStore get returns None for unknown ID not in plan_map
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-get a changeset with id "totally-unknown-id"
|
|
Then the coverage-get result should be None
|
|
|
|
Scenario: SqliteChangeSetStore get returns SpecChangeSet with entries
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-start a changeset for plan "plan-with-entries"
|
|
And I coverage-record a create entry in the started changeset
|
|
And I coverage-get the started changeset
|
|
Then the coverage-get result should be a SpecChangeSet with 1 entries
|
|
And the coverage-get result plan_id should be "plan-with-entries"
|
|
|
|
Scenario: SqliteChangeSetStore record rejects empty changeset_id
|
|
Given I have a coverage sqlite changeset store
|
|
When I try to coverage-record an entry with empty changeset_id
|
|
Then a coverage ValueError should be raised with message "changeset_id must not be empty"
|
|
|
|
Scenario: SqliteChangeSetStore get_for_plan returns empty list for empty plan_id
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-get_for_plan with empty plan_id
|
|
Then the coverage-get_for_plan result should be an empty list
|
|
|
|
Scenario: SqliteChangeSetStore get_for_plan returns empty list when no entries exist
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-get_for_plan with plan_id "nonexistent-plan"
|
|
Then the coverage-get_for_plan result should be an empty list
|
|
|
|
Scenario: SqliteChangeSetStore summarize returns empty dict for empty changeset_id
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-summarize changeset with empty string
|
|
Then the coverage-summarize result should be an empty dict
|
|
|
|
Scenario: SqliteChangeSetStore summarize returns summary for valid changeset
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-start a changeset for plan "plan-sum"
|
|
And I coverage-record a create entry in the started changeset
|
|
And I coverage-summarize the started changeset
|
|
Then the coverage-summarize result total should be 1
|
|
|
|
Scenario: SqliteChangeSetStore delete_for_plan rejects empty plan_id
|
|
Given I have a coverage sqlite changeset store
|
|
When I try to coverage-delete_for_plan with empty plan_id
|
|
Then a coverage ValueError should be raised with message "plan_id must not be empty"
|
|
|
|
Scenario: SqliteChangeSetStore delete_for_plan returns count
|
|
Given I have a coverage sqlite changeset store
|
|
When I coverage-start a changeset for plan "plan-del-cov"
|
|
And I coverage-record a create entry in the started changeset
|
|
And I coverage-delete_for_plan "plan-del-cov"
|
|
Then the coverage-delete count should be 1
|