Files
cleveragents-core/features/actor_service_coverage.feature
T
HAL9000 4e37da2471 fix(concurrency): make ensure_default_mock_actor atomic to prevent TOCTOU race
Merge the two separate database transactions in ensure_default_mock_actor()
into a single atomic transaction to prevent Time-of-Check-Time-of-Use (TOCTOU)
race conditions in concurrent environments (e.g., parallel test workers).

The original implementation had a gap between checking for an existing default
actor and creating a new mock actor. In concurrent scenarios, multiple threads
could both observe no default and attempt to create one, leading to potential
constraint violations or inconsistent state.

The fix consolidates both operations into a single transaction, ensuring that
the check and creation are atomic and safe for concurrent callers.

Added BDD test scenarios to verify idempotency and concurrent safety:
- Idempotency: calling ensure_default_mock_actor multiple times produces same result
- Existing default safety: respects existing default actors
- Single actor creation: ensures only one mock actor is created

Closes #8448
2026-04-27 08:57:27 +00:00

116 lines
5.4 KiB
Gherkin

Feature: Actor service coverage
As a developer
I want actor service behaviors covered
So that custom and built-in actors are validated
Scenario: Normalizes missing prefix to local
Given an actor service with stubbed dependencies
When I normalize actor name "custom"
Then the normalized actor name should be "local/custom"
Scenario Outline: Invalid actor names are rejected
Given an actor service with stubbed dependencies
When I try to normalize actor name "<name>" without allowing built-ins
Then a ValidationError should be raised containing "<message>"
Examples:
| name | message |
| | cannot be empty |
| local/extra/slash/name | exactly one '/' separator |
| /missing-id | include both prefix and identifier |
| remote/model | 'local/<id>' naming pattern |
Scenario: Listing actors returns stored entries
Given an actor service with stubbed dependencies
And existing actors named "local/first" and "local/second"
When I list all actors
Then I should receive actors ["local/first", "local/second"]
Scenario: Getting an unknown actor raises not found
Given an actor service with stubbed dependencies
When I attempt to fetch actor "vendor/model"
Then a NotFoundError should be raised for the actor
Scenario: Upserting cannot overwrite built-in actors
Given an actor service with stubbed dependencies
And a built-in actor named "vendor/model" already exists
When I attempt to upsert "vendor/model" as custom
Then a BusinessRuleViolation should be raised
Scenario: Upserting with set_default marks actor as default
Given an actor service with stubbed dependencies
And an actor named "local/defaultable" exists
When I upsert "local/defaultable" with set_default
Then the stored actor "local/defaultable" should be default
Scenario: Removing missing actors raises not found
Given an actor service with stubbed dependencies
When I try to remove actor "local/missing"
Then a NotFoundError should be raised for the actor
Scenario: Removing protected actors surfaces business rule violation
Given an actor service with stubbed dependencies
And a built-in actor named "local/protected" already exists
When I try to remove actor "local/protected"
Then a BusinessRuleViolation should be raised
Scenario: Setting default actor requires existing entry
Given an actor service with stubbed dependencies
When I set default actor "local/absent"
Then a NotFoundError should be raised for the actor
Scenario: Setting default actor updates repository default
Given an actor service with stubbed dependencies
And an actor named "local/current" exists
When I set default actor "local/current"
Then the stored actor "local/current" should be default
Scenario: Ensuring default mock actor returns existing default
Given an actor service with stubbed dependencies
And an actor named "local/existing-default" exists as default
When I ensure the default mock actor with env value "true"
Then the returned actor name should be "local/existing-default"
And no additional actors should be created
Scenario: Ensuring default mock actor promotes existing mock
Given an actor service with stubbed dependencies
And a mock actor named "local/mock-default" exists without default flag
When I ensure the default mock actor with env value "1"
Then the returned actor name should be "local/mock-default"
And the stored actor "local/mock-default" should be default
Scenario: Ensuring default mock actor creates a new default when missing
Given an actor service with stubbed dependencies
When I ensure the default mock actor with env value "yes"
Then the returned actor name should be "local/mock-default"
And the stored actor "local/mock-default" should be default
Scenario: Ensuring default mock actor is a no-op when env disabled
Given an actor service with stubbed dependencies
And any existing actors are cleared
When I ensure the default mock actor with env value "0"
Then the result should be None
Scenario: Ensuring default mock actor is idempotent
Given an actor service with stubbed dependencies
When I ensure the default mock actor with env value "true"
Then the returned actor name should be "local/mock-default"
And the stored actor "local/mock-default" should be default
When I ensure the default mock actor with env value "true" again
Then the returned actor name should be "local/mock-default"
And only one "local/mock-default" actor should exist
Scenario: Ensuring default mock actor with existing default is safe
Given an actor service with stubbed dependencies
And an actor named "local/existing-default" exists as default
When I ensure the default mock actor with env value "true"
Then the returned actor name should be "local/existing-default"
And the stored actor "local/existing-default" should be default
And only one default actor should exist
Scenario: Ensuring default mock actor creates exactly one actor
Given an actor service with stubbed dependencies
When I ensure the default mock actor with env value "true"
Then exactly one actor should exist
And the stored actor "local/mock-default" should be default