Files
cleveragents-core/features/changeset_repository_coverage.feature
freemo 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
test(coverage): add Behave BDD scenarios for 9 under-covered modules
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
2026-02-27 13:47:42 -05:00

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