@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
