Files
cleveragents-core/features/consolidated_misc.feature
HAL9000 191482d0ef test(a2a): align session.close smoke scenarios with session_id guard
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
2026-06-17 08:49:34 -04:00

2033 lines
81 KiB
Gherkin
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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