191482d0ef
The session_id validation guard added to _handle_session_close in A2aLocalFacade now raises ValueError when session_id is empty or missing. Update three pre-existing smoke scenarios that previously dispatched session.close with empty params to pass an explicit session_id, aligning the smoke contract with the security fix. The negative-path scenarios (@tdd_issue_9250) in a2a_facade_coverage.feature continue to verify the ValueError path with empty/missing session_id. ISSUES CLOSED: #9250
2033 lines
81 KiB
Gherkin
2033 lines
81 KiB
Gherkin
Feature: Consolidated Misc
|
||
Combined scenarios from: a2a_facade, agents_base_uncovered_lines, bridge_coverage, coverage_improvements, m6_autonomy_acceptance, scale_test, container_coverage_r2, lsp_registry, memory_service_coverage, migration_runner_coverage, system_coverage_boost, yaml_engine_direct_coverage
|
||
|
||
# ============================================================
|
||
# Originally from: a2a_facade.feature
|
||
# Feature: A2A Local Facade and Server Stubs
|
||
# ============================================================
|
||
|
||
Scenario: Create facade with no services
|
||
Given a new A2aLocalFacade with no services
|
||
Then the facade should be created successfully
|
||
|
||
|
||
Scenario: Create facade with services dict
|
||
Given a new A2aLocalFacade with services {"session": "mock_session"}
|
||
Then the facade should be created successfully
|
||
|
||
|
||
Scenario: Register a service on the facade
|
||
Given a new A2aLocalFacade with no services
|
||
When I register a service named "planner" on the facade
|
||
Then the service should be registered successfully
|
||
|
||
|
||
Scenario: Register service with empty name raises error
|
||
Given a new A2aLocalFacade with no services
|
||
When I try to register a service with an empty name
|
||
Then an A2A ValueError should be raised
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aLocalFacade — dispatch for each supported operation
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: Dispatch session.create returns session_id and status
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "session.create" with params {}
|
||
Then the response status should be "ok"
|
||
And response data includes key "session_id"
|
||
And response data key "status" equals "created"
|
||
|
||
|
||
Scenario: Dispatch session.close returns status closed
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "session.close" with params {"session_id": "01M6SM0KESESS0N00000000000"}
|
||
Then the response status should be "ok"
|
||
And response data key "status" equals "closed"
|
||
|
||
|
||
Scenario: Dispatch plan.create returns plan_id and status
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "plan.create" with params {}
|
||
Then the response status should be "ok"
|
||
And response data includes key "plan_id"
|
||
And response data key "status" equals "created"
|
||
|
||
|
||
Scenario: Dispatch plan.execute returns plan_id and status queued
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "plan.execute" with params {"plan_id": "PLAN001"}
|
||
Then the response status should be "ok"
|
||
And response data key "plan_id" equals "PLAN001"
|
||
And response data key "status" equals "queued"
|
||
|
||
|
||
Scenario: Dispatch plan.status returns plan_id and phase
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "plan.status" with params {"plan_id": "PLAN001"}
|
||
Then the response status should be "ok"
|
||
And response data key "plan_id" equals "PLAN001"
|
||
And response data key "phase" equals "unknown"
|
||
|
||
|
||
Scenario: Dispatch plan.diff returns plan_id and empty changes
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "plan.diff" with params {"plan_id": "PLAN001"}
|
||
Then the response status should be "ok"
|
||
And response data key "plan_id" equals "PLAN001"
|
||
And response data includes key "changes"
|
||
|
||
|
||
Scenario: Dispatch plan.apply returns plan_id and status applied
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "plan.apply" with params {"plan_id": "PLAN001"}
|
||
Then the response status should be "ok"
|
||
And response data key "plan_id" equals "PLAN001"
|
||
And response data key "status" equals "applied"
|
||
|
||
|
||
Scenario: Dispatch registry.list_tools returns empty tools
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "registry.list_tools" with params {}
|
||
Then the response status should be "ok"
|
||
And response data includes key "tools"
|
||
|
||
|
||
Scenario: Dispatch registry.list_resources returns empty resources
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "registry.list_resources" with params {}
|
||
Then the response status should be "ok"
|
||
And response data includes key "resources"
|
||
|
||
|
||
Scenario: Dispatch context.get returns empty context
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "context.get" with params {}
|
||
Then the response status should be "ok"
|
||
And response data includes key "context"
|
||
|
||
|
||
Scenario: Dispatch event.subscribe returns subscription_id and status
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch operation "event.subscribe" with params {}
|
||
Then the response status should be "ok"
|
||
And response data includes key "subscription_id"
|
||
And response data key "status" equals "subscribed"
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aLocalFacade — unknown operation
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: Dispatch unknown operation raises A2aOperationNotFoundError
|
||
Given a new A2aLocalFacade with no services
|
||
When I dispatch an unknown operation "does.not.exist"
|
||
Then an A2aOperationNotFoundError should be raised
|
||
And the error operation attribute should be "does.not.exist"
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aLocalFacade — list_operations
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: list_operations returns all supported operations
|
||
Given a new A2aLocalFacade with no services
|
||
When I call list_operations on the facade
|
||
Then the operations list should contain "session.create"
|
||
And the operations list should contain "plan.create"
|
||
And the operations list should contain "plan.execute"
|
||
And the operations list should contain "registry.list_tools"
|
||
And the operations list should contain "context.get"
|
||
And the operations list should contain "event.subscribe"
|
||
And the operations list should have 44 items
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aHttpTransport — server-mode HTTP transport
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario Outline: Transport connect accepts valid base URLs
|
||
Given a new A2aHttpTransport
|
||
When I try to connect via the transport to "<url>"
|
||
Then the transport should be connected when using url
|
||
Examples:
|
||
| url |
|
||
| http://localhost:8080 |
|
||
| https://localhost:8443 |
|
||
| http://server.example.com/ |
|
||
| https://a2a.cleverthis.com/api/|
|
||
|
||
|
||
Scenario Outline: Transport connect rejects invalid base URLs
|
||
Given a new A2aHttpTransport
|
||
When I try to connect via the transport to "<url>"
|
||
Then an A2aValueError should be raised with message containing "<reason>"
|
||
Examples:
|
||
| url | reason |
|
||
| "" | empty string |
|
||
| "ftp://localhost:8080" | invalid scheme |
|
||
| None | not a non-empty string|
|
||
|
||
|
||
Scenario: Transport send requires A2aRequest instance
|
||
Given a new A2aHttpTransport
|
||
When the transport is connected to "http://localhost:8080"
|
||
And I try to send a non-A2aRequest via the transport
|
||
Then an A2aTypeError should be raised for the transport
|
||
|
||
|
||
Scenario: Transport send while disconnected raises RuntimeError
|
||
Given a new A2aHttpTransport
|
||
When I try to send a request via the unconnected transport
|
||
Then an A2aRuntimeError should be raised for the transport
|
||
|
||
|
||
Scenario: Server response is parsed successfully (mocked)
|
||
Given a new A2aHttpTransport
|
||
When the transport is connected to "http://localhost:8080"
|
||
And HTTP responses are mocked with JSON-RPC success response
|
||
And I send a request <request_method> via the transport
|
||
Then the response has no error
|
||
|
||
|
||
Scenario: Server returns HTTP 500 error (mocked)
|
||
Given a new A2aHttpTransport
|
||
When the transport is connected to "http://localhost:8080"
|
||
And HTTP responses are mocked with HTTP 500 status
|
||
And I send a request <request_method> via the transport
|
||
Then the response has an error
|
||
|
||
|
||
Scenario: Transport disconnect after connect clears state
|
||
Given a new A2aHttpTransport
|
||
When the transport is connected to "http://localhost:8080"
|
||
Then the transport should be connected when using url
|
||
And I disconnect the transport
|
||
Then the transport should not be connected
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aEventQueue — local mode works
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: Publish event to local queue
|
||
Given a new A2aEventQueue
|
||
When I publish an event with type "plan.started"
|
||
Then the event queue should have 1 event
|
||
|
||
|
||
Scenario: Subscribe locally and receive event
|
||
Given a new A2aEventQueue
|
||
And I subscribe locally with a callback
|
||
When I publish an event with type "plan.completed"
|
||
Then the callback should have been called with event type "plan.completed"
|
||
|
||
|
||
Scenario: Unsubscribe removes subscription
|
||
Given a new A2aEventQueue
|
||
And I subscribe locally with a callback
|
||
When I unsubscribe using the subscription id
|
||
Then the unsubscribe should return True
|
||
|
||
|
||
Scenario: Unsubscribe non-existent returns False
|
||
Given a new A2aEventQueue
|
||
When I unsubscribe using a non-existent subscription id
|
||
Then the unsubscribe should return False
|
||
|
||
|
||
Scenario: Get events respects limit
|
||
Given a new A2aEventQueue
|
||
When I publish 5 events
|
||
And I get events with limit 3
|
||
Then I should receive 3 events
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aEventQueue — remote stub raises
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: Remote subscribe raises A2aNotAvailableError
|
||
Given a new A2aEventQueue
|
||
When I try to subscribe remotely to "http://remote:9090/events"
|
||
Then an A2aNotAvailableError should be raised
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aVersionNegotiator
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: Negotiate supported version succeeds
|
||
Given a new A2aVersionNegotiator
|
||
When I negotiate version "2.0"
|
||
Then the negotiated version should be "2.0"
|
||
|
||
|
||
Scenario: Negotiate unsupported version raises error
|
||
Given a new A2aVersionNegotiator
|
||
When I try to negotiate version "99.0"
|
||
Then an A2aVersionMismatchError should be raised
|
||
And the error requested_version should be "99.0"
|
||
|
||
|
||
Scenario: is_supported returns True for valid version
|
||
Given a new A2aVersionNegotiator
|
||
Then version "2.0" should be supported
|
||
|
||
|
||
Scenario: is_supported returns False for invalid version
|
||
Given a new A2aVersionNegotiator
|
||
Then version "99.0" should not be supported
|
||
|
||
|
||
Scenario: get_current returns current version
|
||
Given a new A2aVersionNegotiator
|
||
Then the current version should be "2.0"
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aRequest model validation
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: A2aRequest with valid operation succeeds
|
||
When I create an A2aRequest with operation "session.create"
|
||
Then the request should have a non-empty request_id
|
||
And the request a2a_version should be "2.0"
|
||
|
||
|
||
Scenario: A2aRequest with empty operation fails validation
|
||
When I try to create an A2aRequest with empty operation
|
||
Then a validation error should be raised
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aResponse model validation
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: A2aResponse with valid status ok succeeds
|
||
When I create an A2aResponse with status "ok" and request_id "REQ001"
|
||
Then the response should be valid
|
||
|
||
|
||
|
||
Scenario: A2aResponse with invalid status fails validation
|
||
When I try to create an A2aResponse with status "maybe"
|
||
Then a validation error should be raised
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aErrorDetail model validation
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: A2aErrorDetail with valid fields succeeds
|
||
When I create an A2aErrorDetail with code "NOT_FOUND" and message "gone"
|
||
Then the error detail should be valid
|
||
|
||
|
||
Scenario: A2aErrorDetail with empty code fails validation
|
||
When I try to create an A2aErrorDetail with empty code
|
||
Then a validation error should be raised
|
||
|
||
# -----------------------------------------------------------------------
|
||
# A2aEvent model construction
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: A2aEvent auto-generates event_id and timestamp
|
||
When I create an A2aEvent with type "plan.progress"
|
||
Then the event should have a non-empty event_id
|
||
And the event should have a non-empty timestamp
|
||
|
||
# -----------------------------------------------------------------------
|
||
# Error hierarchy
|
||
# -----------------------------------------------------------------------
|
||
|
||
|
||
Scenario: A2aError is a CleverAgentsError
|
||
Then A2aError should be a subclass of CleverAgentsError
|
||
|
||
|
||
Scenario: A2aNotAvailableError is an A2aError
|
||
Then A2aNotAvailableError should be a subclass of A2aError
|
||
|
||
|
||
Scenario: A2aVersionMismatchError is an A2aError
|
||
Then A2aVersionMismatchError should be a subclass of A2aError
|
||
|
||
|
||
Scenario: A2aOperationNotFoundError is an A2aError
|
||
Then A2aOperationNotFoundError should be a subclass of A2aError
|
||
|
||
|
||
Scenario: A2aNotAvailableError has default message
|
||
When I create an A2aNotAvailableError with default message
|
||
Then the error message should contain "not available in local mode"
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: agents_base_uncovered_lines.feature
|
||
# Feature: Agents base uncovered lines
|
||
# ============================================================
|
||
|
||
Scenario: Processing pipeline schedules the process wrapper
|
||
Given a basic test agent setup
|
||
When I patch asyncio create_task and send a message
|
||
Then the processing pipeline should schedule the process wrapper task
|
||
|
||
|
||
Scenario: Process wrapper handles tuple input and emits result
|
||
Given a basic test agent setup
|
||
When I process a tuple message through the wrapper
|
||
Then the tuple message and context should be passed and emitted
|
||
|
||
|
||
Scenario: Process wrapper handles non-tuple input with empty context
|
||
Given a basic test agent setup
|
||
When I process a non tuple message through the wrapper
|
||
Then the non tuple message should be processed with empty context
|
||
|
||
|
||
Scenario: Process wrapper wraps exceptions with ExecutionError
|
||
Given a basic test agent setup
|
||
When process_message raises an error inside the wrapper
|
||
Then an ExecutionError should be raised with the agent name
|
||
|
||
|
||
Scenario: process_message_sync runs coroutine on the event loop
|
||
Given a basic test agent setup
|
||
When I call process_message_sync on the agent
|
||
Then the synchronous result should match the async process output
|
||
|
||
|
||
Scenario: Dispose cleans up streams that expose dispose
|
||
Given a basic test agent setup
|
||
When I call dispose on an agent with disposable streams
|
||
Then both input and output streams should be disposed if available
|
||
|
||
|
||
Scenario: AgentWithMemory stores and recalls values with its lock
|
||
Given a basic test agent setup
|
||
When I remember a value and recall it later
|
||
Then the remembered value should be returned from memory
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: bridge_coverage.feature
|
||
# Feature: RxPyLangGraphBridge uncovered lines and branches
|
||
# ============================================================
|
||
|
||
Scenario: __del__ suppresses AttributeError when cleanup_tasks is unavailable
|
||
Given a bridge with its cleanup_tasks method removed
|
||
When __del__ is invoked on the crippled bridge
|
||
Then no exception should propagate from __del__
|
||
|
||
|
||
Scenario: cleanup_tasks_async cancels tasks that are still running
|
||
Given a bridge with a long-running async task
|
||
When I run cleanup_tasks_async on the bridge
|
||
Then the running task should be cancelled
|
||
And the bridge active tasks set should be empty
|
||
|
||
|
||
Scenario: Graph executor processes a string message through execute_graph
|
||
Given a bridge with a mock graph that returns messages
|
||
When I run the graph executor with a string content message
|
||
Then the executor result should contain the graph output
|
||
And the result metadata should include graph execution history
|
||
|
||
|
||
Scenario: Graph executor processes a dict message through execute_graph
|
||
Given a bridge with a mock graph that returns messages
|
||
When I run the graph executor with a dict content message
|
||
Then the executor result should contain the graph output
|
||
|
||
|
||
Scenario: Graph executor processes a non-str non-dict message through execute_graph
|
||
Given a bridge with a mock graph that returns messages
|
||
When I run the graph executor with a numeric content message
|
||
Then the executor result should contain the graph output
|
||
|
||
|
||
Scenario: Graph executor falls back to state dict when messages list is empty
|
||
Given a bridge with a mock graph that returns empty messages
|
||
When I run the graph executor with a string content message
|
||
Then the executor result should be the full state dict
|
||
|
||
|
||
Scenario: create_graph_stream returns StreamConfig for a known graph
|
||
Given a bridge that owns a registered graph named "demo"
|
||
When I call create_graph_stream with "demo"
|
||
Then the returned StreamConfig name should be "graph_demo"
|
||
|
||
|
||
Scenario: _create_state_checkpointer succeeds for a valid graph name
|
||
Given a bridge that owns a registered graph named "ckpt_graph"
|
||
When I build a state checkpointer for "ckpt_graph"
|
||
Then the checkpointer operator should be returned without error
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: coverage_improvements.feature
|
||
# Feature: Improve Code Coverage
|
||
# ============================================================
|
||
|
||
@coverage
|
||
Scenario: Test __main__ module direct execution
|
||
When I test the __main__ module if name equals main block
|
||
Then the sys.exit should be called with the run return value
|
||
|
||
|
||
@coverage
|
||
Scenario: Test platform module when module needs importing
|
||
Given I remove "cleveragents.cli" from sys.modules
|
||
When I test ensure_cli_importable with missing module
|
||
Then the module should be freshly imported
|
||
|
||
|
||
@coverage
|
||
@tdd_issue @tdd_issue_4240 @tdd_expected_fail
|
||
Scenario: Test CLI main function pass statement
|
||
When I call the CLI main function with no commands
|
||
Then the function should complete without error
|
||
|
||
|
||
@coverage
|
||
Scenario: Test exit code normalization with negative code
|
||
Given the CLI returns a negative exit code -5
|
||
When I normalize the exit code
|
||
Then the normalized code should be -5
|
||
|
||
|
||
@coverage
|
||
Scenario: Test exit code normalization with code over 255
|
||
Given the CLI returns an exit code 300
|
||
When I normalize the exit code
|
||
Then the normalized code should be 255
|
||
|
||
|
||
@coverage
|
||
Scenario: Test exit code normalization with None returning 0
|
||
Given the CLI returns None as exit code
|
||
When I normalize the exit code
|
||
Then the normalized code should be 0
|
||
|
||
|
||
@coverage
|
||
Scenario: Test exit code normalization with invalid string
|
||
Given the CLI returns "invalid" as exit code
|
||
When I normalize the exit code
|
||
Then the normalized code should be 1
|
||
|
||
# ============================================================
|
||
# Originally from: m6_autonomy_acceptance.feature
|
||
# Feature: M6 autonomy acceptance smoke tests
|
||
# ============================================================
|
||
|
||
Scenario: M6 smoke load A2A facade flows fixture
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke load the A2A facade flows fixture
|
||
Then the m6 smoke facade fixture should have a session lifecycle entry
|
||
And the m6 smoke facade fixture should have a plan lifecycle entry
|
||
|
||
|
||
Scenario: M6 smoke load autonomy guardrails fixture
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke load the autonomy guardrails fixture
|
||
Then the m6 smoke guardrails fixture should have a denylist entry
|
||
And the m6 smoke guardrails fixture should have an allowlist entry
|
||
And the m6 smoke guardrails fixture should have a cost budget entry
|
||
|
||
|
||
Scenario: M6 smoke load automation profiles fixture
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke load the automation profiles fixture
|
||
Then the m6 smoke profiles fixture should list all 8 built-in names
|
||
And the m6 smoke profiles fixture should have a custom profile entry
|
||
|
||
# --- A2A facade session operations ---
|
||
|
||
|
||
Scenario: M6 smoke A2A session create returns session id
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "session.create" with params {}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "session_id"
|
||
And the m6 smoke response data should contain key "status"
|
||
|
||
|
||
Scenario: M6 smoke A2A session close returns closed status
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "session.close" with params {"session_id": "01M6SM0KESESS0N00000000000"}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data "status" should equal "closed"
|
||
|
||
# --- A2A facade plan operations ---
|
||
|
||
|
||
Scenario: M6 smoke A2A plan create returns plan id
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "plan.create" with params {}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "plan_id"
|
||
|
||
|
||
Scenario: M6 smoke A2A plan execute queues plan
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "plan.execute" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data "status" should equal "queued"
|
||
|
||
|
||
Scenario: M6 smoke A2A plan status returns phase
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "plan.status" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "phase"
|
||
|
||
|
||
Scenario: M6 smoke A2A plan diff returns changes list
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "plan.diff" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "changes"
|
||
|
||
|
||
Scenario: M6 smoke A2A plan apply returns applied status
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "plan.apply" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data "status" should equal "applied"
|
||
|
||
# --- A2A facade registry and context operations ---
|
||
|
||
|
||
Scenario: M6 smoke A2A registry list tools returns empty list
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "registry.list_tools" with params {}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "tools"
|
||
|
||
|
||
Scenario: M6 smoke A2A registry list resources returns empty list
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "registry.list_resources" with params {}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "resources"
|
||
|
||
|
||
Scenario: M6 smoke A2A context get returns context dict
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "context.get" with params {}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "context"
|
||
|
||
|
||
Scenario: M6 smoke A2A event subscribe returns subscription id
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch "event.subscribe" with params {}
|
||
Then the m6 smoke response status should be "ok"
|
||
And the m6 smoke response data should contain key "subscription_id"
|
||
|
||
# --- A2A facade error handling ---
|
||
|
||
|
||
Scenario: M6 smoke A2A unknown operation raises error
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch unknown operation "nonexistent.op"
|
||
Then the m6 smoke facade should raise A2aOperationNotFoundError
|
||
|
||
|
||
Scenario: M6 smoke A2A dispatch with invalid request type raises TypeError
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke dispatch with a non-A2aRequest object
|
||
Then the m6 smoke facade should raise TypeError
|
||
|
||
# --- A2A facade service registration ---
|
||
|
||
|
||
Scenario: M6 smoke A2A register service stores service
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke register service "plan_service" on the facade
|
||
Then the m6 smoke facade should have service "plan_service"
|
||
|
||
|
||
Scenario: M6 smoke A2A list operations returns all supported
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke list facade operations
|
||
Then the m6 smoke operations should include "session.create"
|
||
And the m6 smoke operations should include "plan.execute"
|
||
And the m6 smoke operations should include "event.subscribe"
|
||
And the m6 smoke operations count should be 44
|
||
|
||
# --- A2A event queue ---
|
||
|
||
|
||
Scenario: M6 smoke A2A event queue publish and retrieve
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke A2A event queue
|
||
When I m6 smoke publish an event with type "plan.progress"
|
||
Then the m6 smoke event queue should have 1 event
|
||
And the m6 smoke last event type should be "plan.progress"
|
||
|
||
|
||
Scenario: M6 smoke A2A event queue subscribe local callback
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke A2A event queue
|
||
When I m6 smoke subscribe a local callback
|
||
And I m6 smoke publish an event with type "plan.complete"
|
||
Then the m6 smoke callback should have been called once
|
||
|
||
|
||
Scenario: M6 smoke A2A event queue unsubscribe
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke A2A event queue
|
||
When I m6 smoke subscribe a local callback
|
||
And I m6 smoke unsubscribe the callback
|
||
And I m6 smoke publish an event with type "plan.complete"
|
||
Then the m6 smoke callback should not have been called
|
||
|
||
|
||
Scenario: M6 smoke A2A event queue close prevents publish
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke A2A event queue
|
||
When I m6 smoke close the event queue
|
||
Then the m6 smoke publishing should raise RuntimeError
|
||
|
||
|
||
Scenario: M6 smoke A2A event queue remote subscribe raises error
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke A2A event queue
|
||
When I m6 smoke attempt remote subscribe to "https://example.com/events"
|
||
Then the m6 smoke facade should raise A2aNotAvailableError
|
||
|
||
# --- A2A HTTP transport — server-mode ---
|
||
|
||
|
||
Scenario: M6 smoke A2A transport send while disconnected raises RuntimeError
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke attempt transport send
|
||
Then the m6 smoke facade should raise RuntimeError
|
||
|
||
|
||
Scenario: M6 smoke A2A transport connect succeeds with valid URL
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke attempt transport connect to "https://example.com/a2a"
|
||
Then the m6 smoke transport should be connected
|
||
|
||
|
||
Scenario: M6 smoke A2A transport is_connected returns false before connect
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke check transport is_connected
|
||
Then the m6 smoke transport should not be connected
|
||
|
||
# --- A2A version negotiation ---
|
||
|
||
|
||
Scenario: M6 smoke A2A version negotiation accepts 2.0
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke negotiate A2A version "2.0"
|
||
Then the m6 smoke negotiated version should be "2.0"
|
||
|
||
|
||
Scenario: M6 smoke A2A version negotiation rejects unsupported
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke negotiate A2A version "99.0"
|
||
Then the m6 smoke facade should raise A2aVersionMismatchError
|
||
|
||
|
||
Scenario: M6 smoke A2A version is_supported returns correct result
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke check if version "2.0" is supported
|
||
Then the m6 smoke version support should be true
|
||
When I m6 smoke check if version "99.0" is supported
|
||
Then the m6 smoke version support should be false
|
||
|
||
# --- Automation profiles built-in ---
|
||
|
||
|
||
Scenario: M6 smoke all 8 built-in profiles exist
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke list all built-in profiles
|
||
Then the m6 smoke profile count should be 8
|
||
And the m6 smoke profiles should include "manual"
|
||
And the m6 smoke profiles should include "full-auto"
|
||
|
||
|
||
Scenario: M6 smoke manual profile has all thresholds at 1.0
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke load built-in profile "manual"
|
||
Then the m6 smoke profile decompose_task should be 1.0
|
||
And the m6 smoke profile create_tool should be 1.0
|
||
And the m6 smoke profile select_tool should be 1.0
|
||
And the m6 smoke profile require_sandbox should be true
|
||
|
||
|
||
Scenario: M6 smoke full-auto profile has no gates
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke load built-in profile "full-auto"
|
||
Then the m6 smoke profile decompose_task should be 0.0
|
||
And the m6 smoke profile create_tool should be 0.0
|
||
And the m6 smoke profile select_tool should be 0.0
|
||
And the m6 smoke profile require_sandbox should be false
|
||
And the m6 smoke profile allow_unsafe_tools should be true
|
||
|
||
# --- Automation profile creation and validation ---
|
||
|
||
|
||
Scenario: M6 smoke create custom namespaced profile
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create a profile named "acme/strict" with select_tool 1.0
|
||
Then the m6 smoke created profile name should be "acme/strict"
|
||
And the m6 smoke created profile select_tool should be 1.0
|
||
|
||
|
||
Scenario: M6 smoke profile name validation rejects invalid
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create a profile with invalid name "has spaces"
|
||
Then the m6 smoke creation should raise ValueError
|
||
|
||
|
||
Scenario: M6 smoke profile threshold validation rejects out of range
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create a profile with decompose_task 1.5
|
||
Then the m6 smoke creation should raise ValueError
|
||
|
||
|
||
Scenario: M6 smoke profile from_yaml loads correctly
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke temporary profile YAML file
|
||
When I m6 smoke load profile from the temp YAML
|
||
Then the m6 smoke loaded profile name should be "test-yaml-profile"
|
||
|
||
# --- Guard enforcement ---
|
||
|
||
|
||
Scenario: M6 smoke guard denylist blocks denied tool
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with denylist guard for "rm_rf"
|
||
When I m6 smoke check guard for tool "rm_rf"
|
||
Then the m6 smoke guard result should not be allowed
|
||
And the m6 smoke guard reason should contain "denylist"
|
||
|
||
|
||
Scenario: M6 smoke guard denylist allows non-denied tool
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with denylist guard for "rm_rf"
|
||
When I m6 smoke check guard for tool "read_file"
|
||
Then the m6 smoke guard result should be allowed
|
||
|
||
|
||
Scenario: M6 smoke guard allowlist blocks unlisted tool
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with allowlist guard for "read_file" and "search"
|
||
When I m6 smoke check guard for tool "write_file"
|
||
Then the m6 smoke guard result should not be allowed
|
||
And the m6 smoke guard reason should contain "allowlist"
|
||
|
||
|
||
Scenario: M6 smoke guard max tool calls blocks at limit
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with max 5 tool calls per step
|
||
When I m6 smoke check guard for tool "llm_call" with 5 calls so far
|
||
Then the m6 smoke guard result should not be allowed
|
||
And the m6 smoke guard reason should contain "limit"
|
||
|
||
|
||
Scenario: M6 smoke guard cost budget blocks at cap
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with max cost 10.0
|
||
When I m6 smoke check guard for tool "llm_call" with cost 10.0
|
||
Then the m6 smoke guard result should not be allowed
|
||
And the m6 smoke guard reason should contain "budget"
|
||
|
||
|
||
Scenario: M6 smoke guard write approval blocks write operations
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with write approval required
|
||
When I m6 smoke check guard for tool "write_file" as a write operation
|
||
Then the m6 smoke guard result should not be allowed
|
||
And the m6 smoke guard reason should contain "Write operations"
|
||
|
||
|
||
Scenario: M6 smoke guard apply approval blocks apply phase
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with apply approval required
|
||
When I m6 smoke check guard for tool "__apply__"
|
||
Then the m6 smoke guard result should not be allowed
|
||
And the m6 smoke guard reason should contain "Apply phase"
|
||
|
||
|
||
Scenario: M6 smoke guard with no guards allows everything
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke profile with no guards
|
||
When I m6 smoke check guard for tool "anything"
|
||
Then the m6 smoke guard result should be allowed
|
||
|
||
# --- Profile service resolution precedence ---
|
||
|
||
|
||
Scenario: M6 smoke profile resolution plan takes precedence
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke automation profile service
|
||
When I m6 smoke resolve profile with plan "ci" action "auto" project "manual"
|
||
Then the m6 smoke resolved profile name should be "ci"
|
||
|
||
|
||
Scenario: M6 smoke profile resolution action takes precedence over project
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke automation profile service
|
||
When I m6 smoke resolve profile with plan null action "auto" project "manual"
|
||
Then the m6 smoke resolved profile name should be "auto"
|
||
|
||
|
||
Scenario: M6 smoke profile resolution falls back to global default
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke automation profile service
|
||
When I m6 smoke resolve profile with plan null action null project null
|
||
Then the m6 smoke resolved profile name should be "manual"
|
||
|
||
# --- Profile service guard evaluation ---
|
||
|
||
|
||
Scenario: M6 smoke service evaluate guard delegates to profile
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
Given a m6 smoke automation profile service
|
||
When I m6 smoke evaluate guard for profile "manual" and tool "read_file"
|
||
Then the m6 smoke guard result should be allowed
|
||
|
||
# --- A2A model validation ---
|
||
|
||
|
||
Scenario: M6 smoke A2aRequest validates non-empty operation
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create A2aRequest with empty operation
|
||
Then the m6 smoke creation should raise ValueError
|
||
|
||
|
||
Scenario: M6 smoke A2aResponse validates status values
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create A2aResponse with invalid status "maybe"
|
||
Then the m6 smoke creation should raise ValueError
|
||
|
||
|
||
Scenario: M6 smoke A2aEvent validates non-empty event_type
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create A2aEvent with empty event_type
|
||
Then the m6 smoke creation should raise ValueError
|
||
|
||
|
||
Scenario: M6 smoke A2aErrorDetail validates non-empty fields
|
||
Given a m6 smoke test runner
|
||
And a m6 smoke A2A local facade
|
||
When I m6 smoke create A2aErrorDetail with empty code
|
||
Then the m6 smoke creation should raise ValueError
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: scale_test.feature
|
||
# Feature: Scale test fixtures and performance baselines
|
||
# ============================================================
|
||
|
||
Scenario: Load scale metadata fixture from disk
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then the scale test metadata should contain a schema version
|
||
And the scale test metadata should contain scale profiles
|
||
|
||
|
||
Scenario: Scale metadata contains all required profiles
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then the scale test metadata should have exactly 5 profiles
|
||
And the scale test profile names should be "small, medium, large, xlarge, xxlarge"
|
||
|
||
|
||
Scenario: Each scale profile has required fields
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then each scale test profile should have a file count
|
||
And each scale test profile should have a total size in megabytes
|
||
And each scale test profile should have a language mix
|
||
And each scale test profile should have expected index time
|
||
And each scale test profile should have expected decomposition time
|
||
|
||
|
||
Scenario: Scale metadata lists supported languages
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then the scale test metadata should list supported languages
|
||
And the scale test supported languages should include "python"
|
||
And the scale test supported languages should include "javascript"
|
||
|
||
# ──────────────────────────────────────────────────
|
||
# Section 2: Threshold matrix validation
|
||
# ──────────────────────────────────────────────────
|
||
|
||
|
||
Scenario: Load baseline thresholds fixture from disk
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then the scale test thresholds should contain a schema version
|
||
And the scale test thresholds should have indexing section
|
||
And the scale test thresholds should have decomposition section
|
||
|
||
|
||
Scenario: Indexing thresholds cover all file counts
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then the scale test indexing thresholds should cover "1000_files"
|
||
And the scale test indexing thresholds should cover "5000_files"
|
||
And the scale test indexing thresholds should cover "10000_files"
|
||
And the scale test indexing thresholds should cover "50000_files"
|
||
And the scale test indexing thresholds should cover "100000_files"
|
||
|
||
|
||
Scenario: Each indexing threshold has percentile fields
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then each scale test indexing threshold should have p50 p95 and p99
|
||
|
||
|
||
Scenario: Decomposition thresholds cover all file counts
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then the scale test decomposition thresholds should cover "1000_files"
|
||
And the scale test decomposition thresholds should cover "5000_files"
|
||
And the scale test decomposition thresholds should cover "10000_files"
|
||
And the scale test decomposition thresholds should cover "50000_files"
|
||
And the scale test decomposition thresholds should cover "100000_files"
|
||
|
||
|
||
Scenario: Each decomposition threshold has percentile fields
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then each scale test decomposition threshold should have p50 p95 and p99
|
||
|
||
|
||
Scenario: Thresholds are monotonically increasing across percentiles
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then scale test indexing p50 should be less than p95 for all file counts
|
||
And scale test indexing p95 should be less than p99 for all file counts
|
||
|
||
# ──────────────────────────────────────────────────
|
||
# Section 3: Language mix distribution validation
|
||
# ──────────────────────────────────────────────────
|
||
|
||
|
||
Scenario: Language mix sums to 1.0 for each profile
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then the scale test language mix should sum to 1.0 for each profile
|
||
|
||
|
||
Scenario: Language mix values are between 0 and 1
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then each scale test language mix value should be between 0 and 1
|
||
|
||
|
||
Scenario: All languages in mix are in supported languages list
|
||
Given a scale test environment is ready
|
||
When I scale test load the scale metadata fixture
|
||
Then all scale test language mix keys should be in supported languages
|
||
|
||
# ──────────────────────────────────────────────────
|
||
# Section 4: Scale profile simulation
|
||
# ──────────────────────────────────────────────────
|
||
|
||
|
||
Scenario: Generate file count distribution for small profile
|
||
Given a scale test environment is ready
|
||
When I scale test generate file distribution for the "small" profile
|
||
Then the scale test total generated file count should be 1000
|
||
And the scale test generated distribution should match the language mix
|
||
|
||
|
||
Scenario: Generate file count distribution for medium profile
|
||
Given a scale test environment is ready
|
||
When I scale test generate file distribution for the "medium" profile
|
||
Then the scale test total generated file count should be 5000
|
||
And the scale test generated distribution should match the language mix
|
||
|
||
|
||
Scenario: Generate file count distribution for large profile
|
||
Given a scale test environment is ready
|
||
When I scale test generate file distribution for the "large" profile
|
||
Then the scale test total generated file count should be 10000
|
||
And the scale test generated distribution should match the language mix
|
||
|
||
# ──────────────────────────────────────────────────
|
||
# Section 5: Baseline threshold realism checks
|
||
# ──────────────────────────────────────────────────
|
||
|
||
|
||
Scenario: Indexing thresholds scale sub-linearly with file count
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then scale test 10K indexing p50 should be less than 10x the 1K p50
|
||
|
||
|
||
Scenario: Decomposition thresholds exceed indexing thresholds
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then scale test decomposition p50 should be greater than indexing p50 for each file count
|
||
|
||
|
||
Scenario: Memory thresholds are present and realistic
|
||
Given a scale test environment is ready
|
||
When I scale test load the baseline thresholds fixture
|
||
Then the scale test thresholds should have memory usage section
|
||
And scale test memory peak should exceed steady state for each file count
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: container_coverage_r2.feature
|
||
# Feature: Container module coverage round 2
|
||
# ============================================================
|
||
|
||
Scenario: AI provider returns None when mock disabled and no providers configured
|
||
Given r2cont- the mock AI environment flag is disabled
|
||
And r2cont- a provider registry with no configured providers
|
||
When r2cont- I call get_ai_provider with the mocked registry
|
||
Then r2cont- the ai provider result should be None
|
||
|
||
|
||
Scenario: AI provider returns real provider when mock disabled and providers exist
|
||
Given r2cont- the mock AI environment flag is disabled
|
||
And r2cont- a provider registry with configured providers
|
||
When r2cont- I call get_ai_provider with the mocked registry
|
||
Then r2cont- the ai provider result should not be None
|
||
|
||
# -------------------------------------------------------------------
|
||
# get_ai_provider – features path already on sys.path (line 57 partial)
|
||
# -------------------------------------------------------------------
|
||
|
||
|
||
Scenario: AI provider with features path already on sys.path does not duplicate it
|
||
Given r2cont- the mock AI environment flag is enabled
|
||
And r2cont- the features path is already on sys.path
|
||
When r2cont- I call get_ai_provider without registry args
|
||
Then r2cont- the features path should appear exactly once on sys.path
|
||
|
||
# -------------------------------------------------------------------
|
||
# _build_project_resource_link_repo (lines 125-130)
|
||
# -------------------------------------------------------------------
|
||
|
||
|
||
Scenario: Build project resource link repo creates valid repository
|
||
When r2cont- I build a project resource link repo with an in-memory database
|
||
Then r2cont- the result should be a ProjectResourceLinkRepository instance
|
||
|
||
# -------------------------------------------------------------------
|
||
# get_database_url – env var branches (lines 82, 87)
|
||
# -------------------------------------------------------------------
|
||
|
||
|
||
Scenario: get_database_url returns fallback when no env vars are set
|
||
Given r2cont- no database URL environment variables are set
|
||
When r2cont- I call get_database_url
|
||
Then r2cont- the database URL should start with "sqlite:///"
|
||
|
||
|
||
Scenario: get_database_url returns CLEVERAGENTS_DATABASE_URL when set
|
||
Given r2cont- the env var "CLEVERAGENTS_DATABASE_URL" is set to "sqlite:///explicit.db"
|
||
When r2cont- I call get_database_url
|
||
Then r2cont- the database URL should be "sqlite:///explicit.db"
|
||
|
||
|
||
Scenario: get_database_url returns CLEVERAGENTS_TEST_DATABASE_URL when primary is unset
|
||
Given r2cont- the env var "CLEVERAGENTS_DATABASE_URL" is cleared
|
||
And r2cont- the env var "CLEVERAGENTS_TEST_DATABASE_URL" is set to "sqlite:///test.db"
|
||
When r2cont- I call get_database_url
|
||
Then r2cont- the database URL should be "sqlite:///test.db"
|
||
|
||
|
||
Scenario: get_database_url skips empty env var values
|
||
Given r2cont- the env var "CLEVERAGENTS_DATABASE_URL" is set to empty
|
||
And r2cont- the env var "CLEVERAGENTS_TEST_DATABASE_URL" is set to empty
|
||
When r2cont- I call get_database_url
|
||
Then r2cont- the database URL should start with "sqlite:///"
|
||
|
||
# -------------------------------------------------------------------
|
||
# get_container – cached instance (line 256 False branch)
|
||
# -------------------------------------------------------------------
|
||
|
||
|
||
Scenario: get_container returns the cached instance on subsequent calls
|
||
Given r2cont- the global container has been reset
|
||
When r2cont- I call get_container twice
|
||
Then r2cont- both calls should return the same container instance
|
||
|
||
# -------------------------------------------------------------------
|
||
# override_providers – non-existent attribute (line 285 False)
|
||
# -------------------------------------------------------------------
|
||
|
||
|
||
Scenario: override_providers ignores non-existent provider names
|
||
Given r2cont- the global container has been reset
|
||
When r2cont- I override a provider with a non-existent name
|
||
Then r2cont- no error should have been raised
|
||
|
||
# -------------------------------------------------------------------
|
||
# override_providers – non-database_url provider (line 288 else)
|
||
# -------------------------------------------------------------------
|
||
|
||
|
||
Scenario: override_providers handles non-database_url providers via Object
|
||
Given r2cont- the global container has been reset
|
||
When r2cont- I override the settings provider with a mock value
|
||
Then r2cont- the settings provider should return the mock value
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: lsp_registry.feature
|
||
# Feature: LSP Registry and Runtime Stubs
|
||
# ============================================================
|
||
|
||
@lsp_reg_create
|
||
Scenario: Register a new LSP server config
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
When the LSP server is registered
|
||
Then the LSP registry should contain "local/pyright"
|
||
And the LSP registry should have 1 server
|
||
|
||
|
||
@lsp_reg_create
|
||
Scenario: Register LSP server with multiple languages
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/tsserver" for languages "typescript,javascript" with command "typescript-language-server"
|
||
When the LSP server is registered
|
||
Then the LSP registry should contain "local/tsserver"
|
||
And the LSP server "local/tsserver" should support language "typescript"
|
||
And the LSP server "local/tsserver" should support language "javascript"
|
||
|
||
|
||
@lsp_reg_create
|
||
Scenario: Register LSP server with capabilities
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server config has capabilities "diagnostics,completions,hover"
|
||
When the LSP server is registered
|
||
Then the LSP server "local/pyright" should have 3 capabilities
|
||
|
||
|
||
@lsp_reg_create @error_handling
|
||
Scenario: Registering a duplicate server name is rejected
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server is registered
|
||
When a second LSP server with the same name "local/pyright" is registered
|
||
Then a ValueError should be raised mentioning "already registered"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Lookup by name
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_reg_get
|
||
Scenario: Lookup a registered server by name
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server is registered
|
||
When I look up LSP server "local/pyright"
|
||
Then the LSP lookup result should not be None
|
||
And the LSP lookup result name should be "local/pyright"
|
||
|
||
|
||
@lsp_reg_get
|
||
Scenario: Lookup a non-existent server returns None
|
||
Given a clean LSP registry
|
||
When I look up LSP server "local/nonexistent"
|
||
Then the LSP lookup result should be None
|
||
|
||
|
||
@lsp_reg_get @error_handling
|
||
Scenario: Lookup with get_or_raise for missing server raises error
|
||
Given a clean LSP registry
|
||
When I look up LSP server "local/missing" with get_or_raise
|
||
Then an LspServerNotFoundError should be raised for "local/missing"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# List with namespace filter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_reg_list
|
||
Scenario: List all registered servers
|
||
Given a clean LSP registry
|
||
Given the following LSP servers are registered:
|
||
| name | language | command |
|
||
| local/pyright | python | pyright-langserver |
|
||
| local/tsserver | typescript | typescript-language-server |
|
||
| devops/clangd | c | clangd |
|
||
When I list all LSP servers
|
||
Then the LSP server list should contain 3 servers
|
||
|
||
|
||
@lsp_reg_list
|
||
Scenario: List servers filtered by namespace
|
||
Given a clean LSP registry
|
||
Given the following LSP servers are registered:
|
||
| name | language | command |
|
||
| local/pyright | python | pyright-langserver |
|
||
| local/tsserver | typescript | typescript-language-server |
|
||
| devops/clangd | c | clangd |
|
||
When I list LSP servers with namespace "local"
|
||
Then the LSP server list should contain 2 servers
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# List with language filter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_reg_list
|
||
Scenario: List servers filtered by language
|
||
Given a clean LSP registry
|
||
Given the following LSP servers are registered:
|
||
| name | language | command |
|
||
| local/pyright | python | pyright-langserver |
|
||
| local/tsserver | typescript | typescript-language-server |
|
||
| devops/pylsp | python | pylsp |
|
||
When I list LSP servers with language "python"
|
||
Then the LSP server list should contain 2 servers
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Remove server
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_reg_remove
|
||
Scenario: Remove a registered server
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server is registered
|
||
When I remove LSP server "local/pyright"
|
||
Then the LSP removal result should be True
|
||
And the LSP registry should have 0 servers
|
||
|
||
|
||
@lsp_reg_remove
|
||
Scenario: Remove a non-existent server returns False
|
||
Given a clean LSP registry
|
||
When I remove LSP server "local/nonexistent"
|
||
Then the LSP removal result should be False
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Runtime raises LspServerNotFoundError for unregistered servers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_runtime
|
||
Scenario: Runtime start_server raises error for unregistered server
|
||
Given a clean LSP registry
|
||
Given a local-mode LSP runtime
|
||
When I call start_server on the runtime with name "local/pyright" and workspace "/tmp"
|
||
Then an LSP error should be raised
|
||
|
||
|
||
@lsp_runtime
|
||
Scenario: Runtime stop_server raises error for unregistered server
|
||
Given a clean LSP registry
|
||
Given a local-mode LSP runtime
|
||
When I call stop_server on the runtime with name "local/pyright"
|
||
Then an LSP error should be raised
|
||
|
||
|
||
@lsp_runtime
|
||
Scenario: Runtime get_diagnostics raises error for unregistered server
|
||
Given a clean LSP registry
|
||
Given a local-mode LSP runtime
|
||
When I call get_diagnostics on the runtime with name "local/pyright" and file "/tmp/test.py"
|
||
Then an LSP error should be raised
|
||
|
||
|
||
@lsp_runtime
|
||
Scenario: Runtime get_completions raises error for unregistered server
|
||
Given a clean LSP registry
|
||
Given a local-mode LSP runtime
|
||
When I call get_completions on the runtime with name "local/pyright" file "/tmp/test.py" line 1 column 1
|
||
Then an LSP error should be raised
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Tool adapter generates tool specs from capabilities
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_tool_adapter
|
||
Scenario: Tool adapter generates specs for each capability
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server config has capabilities "diagnostics,completions"
|
||
When I generate tool specs from the adapter
|
||
Then the tool spec list should contain 2 specs
|
||
And the tool spec names should include "local/pyright/diagnostics"
|
||
And the tool spec names should include "local/pyright/completions"
|
||
|
||
|
||
@lsp_tool_adapter
|
||
Scenario: Tool adapter handler raises LspNotAvailableError in local mode
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server config has capabilities "diagnostics"
|
||
When I generate tool specs from the adapter
|
||
And I invoke the first tool spec handler
|
||
Then an LspNotAvailableError should be raised
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Binding validation (valid/invalid references)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_binding
|
||
Scenario: Valid binding with registered server passes
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server is registered
|
||
When I create a binding for node "code-editor" to server "local/pyright"
|
||
Then the binding should be valid
|
||
And the binding node_name should be "code-editor"
|
||
And the binding lsp_server_name should be "local/pyright"
|
||
|
||
|
||
@lsp_binding @error_handling
|
||
Scenario: Binding with non-namespaced server name is rejected
|
||
Given a clean LSP registry
|
||
When I create a binding for node "code-editor" to server "pyright"
|
||
Then a ValidationError should be raised mentioning "namespaced"
|
||
|
||
|
||
@lsp_binding
|
||
Scenario: Binding auto_detect defaults to True
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
And the LSP server is registered
|
||
When I create a binding for node "code-editor" to server "local/pyright"
|
||
Then the binding auto_detect should be True
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Model validation
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_model_validation
|
||
Scenario: LspServerConfig rejects non-namespaced name
|
||
Given a clean LSP registry
|
||
When I try to create an LSP server config with name "pyright" and command "pyright-langserver"
|
||
Then a ValidationError should be raised mentioning "namespaced"
|
||
|
||
|
||
@lsp_model_validation
|
||
Scenario: LspServerConfig normalizes language strings to lowercase
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "Python" with command "pyright-langserver"
|
||
Then the LSP server config language "python" should be present
|
||
|
||
|
||
@lsp_model_validation
|
||
Scenario: LspServerConfig namespace property extracts prefix
|
||
Given a clean LSP registry
|
||
Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver"
|
||
Then the LSP server config namespace should be "local"
|
||
And the LSP server config short_name should be "pyright"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Error hierarchy
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@lsp_errors
|
||
Scenario: LspError inherits from CleverAgentsError
|
||
Given a clean LSP registry
|
||
Then LspError should be a subclass of CleverAgentsError
|
||
|
||
|
||
@lsp_errors
|
||
Scenario: LspNotAvailableError has default message
|
||
Given a clean LSP registry
|
||
When I create an LspNotAvailableError with default message
|
||
Then the LSP error message should contain "not available in local mode"
|
||
|
||
|
||
@lsp_errors
|
||
Scenario: LspServerNotFoundError includes server name
|
||
Given a clean LSP registry
|
||
When I create an LspServerNotFoundError for server "local/pyright"
|
||
Then the LSP error message should contain "local/pyright"
|
||
And the error server_name should be "local/pyright"
|
||
|
||
|
||
@lsp_errors @error_handling
|
||
Scenario: LspServerNotFoundError rejects empty server name
|
||
Given a clean LSP registry
|
||
When I try to create an LspServerNotFoundError with empty name
|
||
Then a ValueError should be raised
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: memory_service_coverage.feature
|
||
# Feature: Memory Service Coverage
|
||
# ============================================================
|
||
|
||
Scenario: Conversation adapter returns buffer text when configured for summaries
|
||
Given a conversation adapter configured to return text values
|
||
When I load memory variables from the adapter
|
||
Then the adapter provides a string buffer representation
|
||
|
||
|
||
Scenario: Conversation adapter handles nested context and async operations
|
||
Given a conversation adapter with prune tracking
|
||
When I save nested user and ai context through the adapter
|
||
And I invoke the adapter async operations
|
||
Then the adapter normalizes all values and prunes history three times
|
||
|
||
|
||
Scenario: Memory service prunes history to the configured window
|
||
Given a memory service limited to 3 messages
|
||
When I add five interactions via save context
|
||
And I ask for the two most recent messages
|
||
And I ask for more messages than available
|
||
Then the service keeps only the three latest messages
|
||
And the recent message requests respect their sizes
|
||
And the service summary honors a 20 character limit
|
||
And memory variables expose history and counts
|
||
|
||
|
||
Scenario: Memory service manages direct messages and adapters
|
||
Given a memory service with default settings
|
||
When I add direct message objects to the service
|
||
And I inspect the default conversation adapter
|
||
And I reset the max message limit to 1
|
||
And I save a new interaction to enforce pruning
|
||
And I clear the service memory
|
||
Then the service computed token counts and summaries before clearing
|
||
And the service history is empty after clearing
|
||
And a custom conversation adapter returns text values
|
||
|
||
|
||
Scenario: Memory service uses SQL chat history when available
|
||
Given a patched SQL chat history for memory service
|
||
When I create a memory service with a connection string
|
||
Then the SQL history constructor receives the session and connection
|
||
And stored messages flow through the patched SQL history
|
||
|
||
# ==================== EntityMemory Tests ====================
|
||
|
||
|
||
Scenario: Track a new entity
|
||
Given a memory service with entity tracking enabled
|
||
When I track a project entity named "my-project"
|
||
Then the entity should be stored successfully
|
||
And the entity should have type "project"
|
||
And the entity mention count should be 1
|
||
|
||
|
||
Scenario: Track entity with metadata
|
||
Given a memory service with entity tracking enabled
|
||
When I track a plan entity named "feature-plan" with metadata
|
||
Then the entity metadata should include the provided values
|
||
And the entity should be retrievable by name and type
|
||
|
||
|
||
Scenario: Update existing entity increments mention count
|
||
Given a memory service with entity tracking enabled
|
||
And I have tracked a file entity named "main.py"
|
||
When I track the same entity again
|
||
Then the entity mention count should be 2
|
||
And the entity last_seen should be updated
|
||
|
||
|
||
Scenario: Get entities by type
|
||
Given a memory service with multiple entities tracked
|
||
When I get all entities of type "project"
|
||
Then I should receive only project entities
|
||
And I should not receive entities of other types
|
||
|
||
|
||
Scenario: Get all entities
|
||
Given a memory service with multiple entities tracked
|
||
When I get all tracked entities
|
||
Then I should receive all entities regardless of type
|
||
|
||
|
||
Scenario: Get recent entities
|
||
Given a memory service with multiple entities tracked over time
|
||
When I get the 3 most recent entities
|
||
Then I should receive entities sorted by last access time
|
||
And I should receive at most 3 entities
|
||
|
||
|
||
Scenario: Search entities by name
|
||
Given a memory service with multiple entities tracked
|
||
When I search for entities with "main" in the name
|
||
Then I should receive entities whose names contain "main"
|
||
And the search should be case-insensitive
|
||
|
||
|
||
Scenario: Remove specific entity
|
||
Given a memory service with entity tracking enabled
|
||
And I have tracked a context entity named "src/utils.py"
|
||
When I remove the entity
|
||
Then the entity should no longer exist
|
||
And attempting to get the removed entity should return None
|
||
|
||
|
||
Scenario: Clear all entities
|
||
Given a memory service with multiple entities tracked
|
||
When I clear all entities
|
||
Then the entity store should be empty
|
||
And get_entities should return an empty list
|
||
|
||
|
||
Scenario: Clear entities by type
|
||
Given a memory service with multiple entities tracked
|
||
When I clear only project entities
|
||
Then project entities should be removed
|
||
And entities of other types should remain
|
||
|
||
|
||
Scenario: Get entity summary
|
||
Given a memory service with multiple entities tracked
|
||
When I get the entity summary
|
||
Then the summary should include total entity count
|
||
And the summary should include counts by type
|
||
And the summary should include recent entities
|
||
|
||
|
||
Scenario: Entity type can be specified as string
|
||
Given a memory service with entity tracking enabled
|
||
When I track an entity with type specified as string "custom"
|
||
Then the entity should be tracked with EntityType.CUSTOM
|
||
And the entity should be retrievable using either string or enum type
|
||
|
||
|
||
Scenario: Entity store is accessible directly
|
||
Given a memory service with entity tracking enabled
|
||
When I access the entity_store property
|
||
Then I should receive the underlying EntityStore instance
|
||
And I can track entities through it directly
|
||
|
||
# ==================== Coverage Enhancement Tests ====================
|
||
|
||
|
||
Scenario: Entity from_dict deserializes entity from dictionary
|
||
Given an entity dictionary with valid data
|
||
When I create an Entity from the dictionary using from_dict
|
||
Then the Entity should have the correct name and type
|
||
And the Entity should have the correct metadata
|
||
And the Entity should have the correct timestamps
|
||
And the Entity should have the correct mention count
|
||
|
||
|
||
Scenario: Updating existing entity also updates metadata
|
||
Given a memory service with entity tracking enabled
|
||
And I have tracked a project entity named "metadata-project" with initial metadata
|
||
When I track the same project entity again with additional metadata
|
||
Then the entity metadata should contain both initial and additional values
|
||
And the entity mention count should be 2
|
||
|
||
|
||
Scenario: Remove non-existent entity returns False
|
||
Given a memory service with entity tracking enabled
|
||
When I attempt to remove an entity that does not exist
|
||
Then the remove operation should return False
|
||
|
||
|
||
Scenario: EntityStore with connection string triggers persistence
|
||
Given an entity store with a mock connection string
|
||
When I track an entity to trigger dirty state
|
||
Then the persist callback should mark the store as not dirty
|
||
And subsequent reads should find the entity
|
||
|
||
|
||
Scenario: Get summary returns empty string for empty conversation
|
||
Given a memory service with no messages
|
||
When I request the conversation summary
|
||
Then the summary should be an empty string
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: migration_runner_coverage.feature
|
||
# Feature: Migration runner coverage
|
||
# ============================================================
|
||
|
||
Scenario: Missing Alembic config raises helpful error
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I attempt to load the alembic config without an alembic.ini file
|
||
Then a FileNotFoundError should be raised mentioning "alembic.ini"
|
||
|
||
|
||
Scenario: Running migrations without engine uses Alembic-managed connection
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I run migrations without providing an engine
|
||
Then the upgrade command should be invoked without a connection attribute
|
||
And the CLEVERAGENTS_DATABASE_URL environment variable should be restored
|
||
|
||
|
||
Scenario: In-memory SQLite initialization reuses cached engine
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I initialize or upgrade the database with cached in-memory engine
|
||
Then migrations should run using the cached engine connection
|
||
And the cached engine should remain available without disposal
|
||
|
||
|
||
Scenario: Legacy database is stamped when tables exist without alembic_version
|
||
Given a migration runner configured for "postgresql://user:pass@localhost/testdb"
|
||
When I initialize or upgrade the database with legacy tables present
|
||
Then the stamp command should run using the active connection
|
||
And the external database engine should be disposed after initialization
|
||
|
||
|
||
Scenario: Pending migrations include newest revisions when no current version exists
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I request pending migrations for a database with no current revision
|
||
Then the pending migration list should be ordered from oldest to newest
|
||
|
||
|
||
Scenario: Get current revision queries migration context
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I request the current revision from the database
|
||
Then the migration context should be queried for the current revision
|
||
And the temporary connection should be closed afterward
|
||
|
||
|
||
Scenario: get_current_revision uses check_same_thread=False for SQLite engines
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I request the current revision from the database
|
||
Then the SQLite engine for get_current_revision should use check_same_thread=False
|
||
|
||
|
||
Scenario: File-based SQLite database directory is created if missing
|
||
Given a migration runner configured for "sqlite:///tmp/test-db/mydb.db"
|
||
When I initialize or upgrade a file-based SQLite database
|
||
Then the parent directory should be created if it does not exist
|
||
And the database engine should be disposed after initialization
|
||
|
||
|
||
Scenario: File-based SQLite uses engine with proper connection args
|
||
Given a migration runner configured for "sqlite:///tmp/test-db2/mydb.db"
|
||
When I initialize or upgrade a file-based SQLite database
|
||
Then the engine should be created with check_same_thread set to False
|
||
And the database engine should be disposed after initialization
|
||
|
||
|
||
Scenario: Running migrations when database already up to date
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I initialize the database and migrations are already applied
|
||
Then no additional migrations should be run
|
||
And check migrations needed should return False
|
||
|
||
|
||
Scenario: Pending migrations trigger upgrade when alembic_version exists
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I initialize the database with pending migrations detected
|
||
Then run migrations should be invoked with the existing engine
|
||
And the in-memory engine should not be disposed
|
||
|
||
|
||
Scenario: Pending migrations require approval before running
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I initialize the database with pending migrations requiring confirmation
|
||
Then the migration approval prompt should be invoked
|
||
And migrations should run after approval
|
||
|
||
|
||
Scenario: Declining migration approval raises an error
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I decline migration approval when pending migrations exist
|
||
Then a migration approval error should be raised
|
||
|
||
|
||
Scenario: Default prompt auto-approves when running in CI
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I evaluate the default migration prompt while CI is set
|
||
Then the default prompt should auto approve without interaction
|
||
|
||
|
||
Scenario: Default prompt delegates to typer confirm in TTY
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I evaluate the default migration prompt in an interactive TTY
|
||
Then the default prompt should return the typer confirmation result
|
||
|
||
|
||
Scenario: Default prompt rejects migration after interactive prompt IOError
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When the default migration prompt raises an IOError while prompting
|
||
Then the default prompt should reject migration after the IO failure
|
||
|
||
Scenario: Default prompt rejects migration in non-interactive environment
|
||
Given a migration runner configured for "sqlite:///:memory:"
|
||
When I evaluate the default migration prompt in a non-interactive environment
|
||
Then the default prompt should reject without interaction
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: system_coverage_boost.feature
|
||
# Feature: System commands coverage boost
|
||
# ============================================================
|
||
|
||
Scenario: git sha returns unknown when git not available
|
||
Given a system module for coverage testing
|
||
When I call git_sha with git not on path
|
||
Then the git sha should be "unknown"
|
||
|
||
|
||
Scenario: dep_version returns not installed for missing package
|
||
Given a system module for coverage testing
|
||
When I call dep_version for a missing package
|
||
Then the dep version should be "not installed"
|
||
|
||
|
||
Scenario: build_info_data with missing database path
|
||
Given a system module for coverage testing
|
||
When I call build_info_data with no database file
|
||
Then the info data should contain db_size as "0 MB"
|
||
|
||
|
||
Scenario: build_info_data with missing log directory
|
||
Given a system module for coverage testing
|
||
When I call build_info_data with no log directory
|
||
Then the info data should contain logs as "0 MB"
|
||
|
||
|
||
Scenario: check_data_dir with non-existent dir
|
||
Given a system module for coverage testing
|
||
When I call check_data_dir with non-existent directory
|
||
Then the check result should have WARN status
|
||
|
||
|
||
Scenario: check_database with non-sqlite URL
|
||
Given a system module for coverage testing
|
||
When I call check_database with postgres URL
|
||
Then the check result should have OK status and details "configured"
|
||
|
||
|
||
Scenario: check_database with missing parent dir
|
||
Given a system module for coverage testing
|
||
When I call check_database with sqlite and non-writable parent
|
||
Then the check result should have ERROR status
|
||
|
||
|
||
Scenario: check_file_permissions with non-existent data dir
|
||
Given a system module for coverage testing
|
||
When I call check_file_permissions with non-existent directory
|
||
Then the check result should have WARN status
|
||
|
||
|
||
Scenario: check_file_permissions with read-only data dir
|
||
Given a system module for coverage testing
|
||
When I call check_file_permissions with read-only directory
|
||
Then the file perm result status should not be OK
|
||
|
||
|
||
Scenario: check_git when git is unavailable
|
||
Given a system module for coverage testing
|
||
When I call check_git with git unavailable
|
||
Then the check result should have ERROR status
|
||
|
||
|
||
Scenario: render_version_rich produces output
|
||
Given a system module for coverage testing
|
||
When I call render_version_rich with sample data
|
||
Then no system exception should be raised
|
||
|
||
|
||
Scenario: render_info_rich produces output
|
||
Given a system module for coverage testing
|
||
When I call render_info_rich with sample data
|
||
Then no system exception should be raised
|
||
|
||
|
||
Scenario: render_diagnostics_rich with errors
|
||
Given a system module for coverage testing
|
||
When I call render_diagnostics_rich with error data
|
||
Then no system exception should be raised
|
||
|
||
|
||
Scenario: render_diagnostics_rich with warnings only
|
||
Given a system module for coverage testing
|
||
When I call render_diagnostics_rich with warning data
|
||
Then no system exception should be raised
|
||
|
||
|
||
Scenario: render_diagnostics_rich with all OK
|
||
Given a system module for coverage testing
|
||
When I call render_diagnostics_rich with all OK data
|
||
Then no system exception should be raised
|
||
|
||
|
||
# ============================================================
|
||
# Originally from: yaml_engine_direct_coverage.feature
|
||
# Feature: YAML engine direct coverage
|
||
# ============================================================
|
||
|
||
Scenario: Engine construction registers filters and globals
|
||
When I construct a new YAMLTemplateEngine for direct testing
|
||
Then the direct engine should have yaml indent sum selectattr filters
|
||
And the direct engine globals should include builtins
|
||
|
||
|
||
Scenario: load_file reads and parses YAML from a temp file
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
And I write a temp YAML file with mapping content
|
||
When I call load_file on the direct engine without context
|
||
Then the direct load_file result should be a dict with app key
|
||
|
||
|
||
Scenario: load_string parses plain YAML mapping
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call load_string with plain mapping YAML on direct engine
|
||
Then the direct result should have key server
|
||
|
||
|
||
Scenario: load_string raises for plain YAML list
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call load_string with plain list YAML on direct engine
|
||
Then a direct ValueError mentioning dict should be raised
|
||
|
||
|
||
Scenario: load_string with context renders and parses template
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call load_string with template and context on direct engine
|
||
Then the direct rendered result should have substituted values
|
||
|
||
|
||
Scenario: load_string without context defers rendering
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call load_string with template but no context on direct engine
|
||
Then the direct deferred result should be a dict or raise YAMLError
|
||
|
||
|
||
Scenario: preprocess inserts indent hints for for loops
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call preprocess on content with a for loop
|
||
Then the direct preprocessed result should contain indent hint
|
||
|
||
|
||
Scenario: postprocess removes blank lines before unindented content
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call postprocess on content with blank lines before unindented
|
||
Then blank lines before unindented content should be gone
|
||
|
||
|
||
Scenario: fix_common_yaml_issues splits multi-colon lines
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call fix_common_yaml_issues on multi-colon content
|
||
Then the fixed content should have multiple lines
|
||
|
||
|
||
Scenario: analyze_yaml_structure finds blocks and inline templates
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call analyze_yaml_structure on mixed content
|
||
Then template_blocks and inline_templates should be populated
|
||
And hierarchy should have root key
|
||
|
||
|
||
Scenario: yaml_filter converts dict to YAML string
|
||
When I call the static yaml_filter with a dict
|
||
Then the yaml_filter result should contain key colon
|
||
|
||
|
||
Scenario: indent_filter indents lines
|
||
When I call the static indent_filter with 4 spaces
|
||
Then each direct line should start with 4 spaces
|
||
|
||
|
||
Scenario: sum_filter returns numeric sum
|
||
When I call the static sum_filter on a number list
|
||
Then the direct sum should be 10
|
||
|
||
|
||
Scenario: selectattr_filter extracts with defaults
|
||
When I call the static selectattr_filter for score
|
||
Then items with score return their value and others return 0
|
||
|
||
|
||
Scenario: create_render_context merges nested and top-level
|
||
When I call the static create_render_context with nested data
|
||
Then the direct context should have both nested and explicit keys
|
||
|
||
|
||
Scenario: render_and_parse falls back on initial YAML error
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call render_and_parse with colon-heavy value
|
||
Then the direct engine should attempt fix or raise
|
||
|
||
|
||
Scenario: render_and_parse raises ValueError for non-dict result
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call render_and_parse that produces a list
|
||
Then a direct ValueError mentioning dict should be raised
|
||
|
||
|
||
Scenario: simple_template_extraction raises YAMLError for invalid
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call simple_template_extraction with invalid YAML
|
||
Then a direct YAML error should be raised
|
||
|
||
|
||
Scenario: simple_template_extraction raises ValueError for non-dict
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
When I call simple_template_extraction with list YAML
|
||
Then a direct ValueError mentioning dict should be raised
|
||
|
||
|
||
Scenario: load_file with context renders templates from disk
|
||
Given I construct a new YAMLTemplateEngine for direct testing
|
||
And I write a temp YAML file with template content
|
||
When I call load_file with template context on direct engine
|
||
Then the direct file result should contain rendered values
|