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
176 lines
8.6 KiB
Gherkin
176 lines
8.6 KiB
Gherkin
Feature: LockService coverage – uncovered branches and edge cases
|
||
As a developer
|
||
I want full coverage of every branch in lock_service.py
|
||
So that no edge case is left untested
|
||
|
||
Background:
|
||
Given I have a fresh lock service with in-memory database
|
||
|
||
# ------------------------------------------------------------------
|
||
# Validation: release method
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Release rejects empty resource_type
|
||
When I try to release with an empty resource_type
|
||
Then a lock_service validation error should be raised with message containing "resource_type must not be empty"
|
||
|
||
Scenario: Release rejects invalid resource_type
|
||
When I try to release with resource_type "widget" owner "o" resource_id "r1"
|
||
Then a lock_service validation error should be raised with message containing "resource_type must be one of"
|
||
|
||
Scenario: Release rejects empty resource_id
|
||
When I try to release with an empty resource_id
|
||
Then a lock_service validation error should be raised with message containing "resource_id must not be empty"
|
||
|
||
# ------------------------------------------------------------------
|
||
# Validation: renew method
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Renew rejects empty resource_type
|
||
When I try to renew with an empty resource_type
|
||
Then a lock_service validation error should be raised with message containing "resource_type must not be empty"
|
||
|
||
Scenario: Renew rejects invalid resource_type
|
||
When I try to renew with resource_type "widget" owner "o" resource_id "r1"
|
||
Then a lock_service validation error should be raised with message containing "resource_type must be one of"
|
||
|
||
Scenario: Renew rejects empty resource_id
|
||
When I try to renew with an empty resource_id
|
||
Then a lock_service validation error should be raised with message containing "resource_id must not be empty"
|
||
|
||
Scenario: Renew rejects TTL below minimum
|
||
When I try to renew with TTL 2 owner "o" resource_type "plan" resource_id "r1"
|
||
Then a lock_service validation error should be raised with message containing "ttl_seconds must be >="
|
||
|
||
Scenario: Renew rejects TTL above maximum
|
||
When I try to renew with TTL 7200 owner "o" resource_type "plan" resource_id "r1"
|
||
Then a lock_service validation error should be raised with message containing "ttl_seconds must be <="
|
||
|
||
# ------------------------------------------------------------------
|
||
# Validation: is_locked method
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: is_locked rejects empty resource_type
|
||
When I try to check is_locked with an empty resource_type
|
||
Then a lock_service validation error should be raised with message containing "resource_type must not be empty"
|
||
|
||
Scenario: is_locked rejects invalid resource_type
|
||
When I try to check is_locked with resource_type "widget" resource_id "r1"
|
||
Then a lock_service validation error should be raised with message containing "resource_type must be one of"
|
||
|
||
Scenario: is_locked rejects empty resource_id
|
||
When I try to check is_locked with an empty resource_id
|
||
Then a lock_service validation error should be raised with message containing "resource_id must not be empty"
|
||
|
||
# ------------------------------------------------------------------
|
||
# TTL boundary values on acquire
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Acquire with explicit minimum TTL succeeds
|
||
When I acquire a lock with TTL 5 owner "owner-min" resource_type "plan" resource_id "p-min"
|
||
Then the lock_service result should be true
|
||
|
||
Scenario: Acquire with explicit maximum TTL succeeds
|
||
When I acquire a lock with TTL 3600 owner "owner-max" resource_type "plan" resource_id "p-max"
|
||
Then the lock_service result should be true
|
||
|
||
Scenario: Acquire with custom valid TTL succeeds
|
||
When I acquire a lock with TTL 120 owner "owner-cust" resource_type "project" resource_id "proj-cust"
|
||
Then the lock_service result should be true
|
||
|
||
# ------------------------------------------------------------------
|
||
# Re-entrant acquisition on project resource
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Same owner re-acquires a project lock
|
||
Given owner "owner-a" already holds a project lock on "proj-re"
|
||
When I acquire a lock with TTL 300 owner "owner-a" resource_type "project" resource_id "proj-re"
|
||
Then the lock_service result should be true
|
||
|
||
# ------------------------------------------------------------------
|
||
# Release by wrong owner returns False
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Release returns false when owner does not match
|
||
Given owner "owner-a" already holds a plan lock on "plan-wrong"
|
||
When I try to release with resource_type "plan" owner "wrong-owner" resource_id "plan-wrong"
|
||
Then the lock_service result should be false
|
||
|
||
# ------------------------------------------------------------------
|
||
# is_locked on expired lock returns False
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: is_locked returns false for an expired lock
|
||
Given an expired lock exists for owner "old" on plan "plan-exp"
|
||
When I check is_locked for resource_type "plan" resource_id "plan-exp"
|
||
Then the lock_service result should be false
|
||
|
||
# ------------------------------------------------------------------
|
||
# Zero-count paths (no log emitted)
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: release_all_for_owner returns zero when owner has no locks
|
||
When I release all locks for owner_id "ghost"
|
||
Then the lock_service integer result should be 0
|
||
|
||
Scenario: cleanup_expired returns zero when no expired locks exist
|
||
When I run cleanup of expired locks
|
||
Then the lock_service integer result should be 0
|
||
|
||
Scenario: count_stale_locks returns zero when no stale locks exist
|
||
When I count stale locks via the service
|
||
Then the lock_service integer result should be 0
|
||
|
||
# ------------------------------------------------------------------
|
||
# Exception rollback paths (mocked session)
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Acquire rolls back on unexpected exception
|
||
Given a lock service whose session raises an unexpected error on execute
|
||
When I try to acquire and expect a RuntimeError
|
||
Then a RuntimeError should have been raised
|
||
And the session should have been rolled back
|
||
|
||
Scenario: Release rolls back on unexpected exception
|
||
Given a lock service whose session raises an unexpected error on execute
|
||
When I try to release and expect a RuntimeError
|
||
Then a RuntimeError should have been raised
|
||
And the session should have been rolled back
|
||
|
||
Scenario: Renew rolls back on unexpected exception
|
||
Given a lock service whose session raises an unexpected error on execute
|
||
When I try to renew and expect a RuntimeError
|
||
Then a RuntimeError should have been raised
|
||
And the session should have been rolled back
|
||
|
||
Scenario: release_all_for_owner rolls back on unexpected exception
|
||
Given a lock service whose session raises an unexpected error on execute
|
||
When I try to release_all_for_owner and expect a RuntimeError
|
||
Then a RuntimeError should have been raised
|
||
And the session should have been rolled back
|
||
|
||
Scenario: cleanup_expired rolls back on unexpected exception
|
||
Given a lock service whose session raises an unexpected error on execute
|
||
When I try to cleanup_expired and expect a RuntimeError
|
||
Then a RuntimeError should have been raised
|
||
And the session should have been rolled back
|
||
|
||
# ------------------------------------------------------------------
|
||
# Acquire conflict rollback path
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Acquire rolls back the session on LockConflictError
|
||
Given a lock service with a real database and owner "holder" holds plan "plan-conf"
|
||
When I try to acquire plan "plan-conf" as "intruder" and capture the conflict error
|
||
Then a LockConflictError should have been raised
|
||
And the conflict owner should be "holder"
|
||
|
||
# ------------------------------------------------------------------
|
||
# Renew LockExpiredError rollback path
|
||
# ------------------------------------------------------------------
|
||
|
||
Scenario: Renew rolls back the session on LockExpiredError
|
||
Given a lock service with a real database and an expired lock for "exp-owner" on plan "plan-exp-r"
|
||
When I try to renew plan "plan-exp-r" as "exp-owner" and capture the expired error
|
||
Then a LockExpiredError should have been raised
|