Files
cleveragents-core/features/acp_facade.feature

287 lines
12 KiB
Gherkin

@phase2 @acp @facade
Feature: ACP Local Facade and Server Stubs
As a developer
I want an ACP integration layer with local-mode facade and server stubs
So that ACP operations work locally and server mode is stubbed out
# -----------------------------------------------------------------------
# AcpLocalFacade creation and service registration
# -----------------------------------------------------------------------
Scenario: Create facade with no services
Given a new AcpLocalFacade with no services
Then the facade should be created successfully
Scenario: Create facade with services dict
Given a new AcpLocalFacade with services {"session": "mock_session"}
Then the facade should be created successfully
Scenario: Register a service on the facade
Given a new AcpLocalFacade 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 AcpLocalFacade with no services
When I try to register a service with an empty name
Then an ACP ValueError should be raised
# -----------------------------------------------------------------------
# AcpLocalFacade — dispatch for each supported operation
# -----------------------------------------------------------------------
Scenario: Dispatch session.create returns session_id and status
Given a new AcpLocalFacade 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 AcpLocalFacade with no services
When I dispatch operation "session.close" with params {}
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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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 AcpLocalFacade 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"
# -----------------------------------------------------------------------
# AcpLocalFacade — unknown operation
# -----------------------------------------------------------------------
Scenario: Dispatch unknown operation raises AcpOperationNotFoundError
Given a new AcpLocalFacade with no services
When I dispatch an unknown operation "does.not.exist"
Then an AcpOperationNotFoundError should be raised
And the error operation attribute should be "does.not.exist"
# -----------------------------------------------------------------------
# AcpLocalFacade — list_operations
# -----------------------------------------------------------------------
Scenario: list_operations returns all supported operations
Given a new AcpLocalFacade 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 11 items
# -----------------------------------------------------------------------
# AcpHttpTransport — all stubs raise AcpNotAvailableError
# -----------------------------------------------------------------------
Scenario: Transport send raises AcpNotAvailableError
Given a new AcpHttpTransport
When I try to send a request via the transport
Then an AcpNotAvailableError should be raised
Scenario: Transport connect raises AcpNotAvailableError
Given a new AcpHttpTransport
When I try to connect via the transport to "http://localhost:8080"
Then an AcpNotAvailableError should be raised
Scenario: Transport disconnect raises AcpNotAvailableError
Given a new AcpHttpTransport
When I try to disconnect the transport
Then an AcpNotAvailableError should be raised
Scenario: Transport is_connected returns False
Given a new AcpHttpTransport
Then the transport should not be connected
# -----------------------------------------------------------------------
# AcpEventQueue — local mode works
# -----------------------------------------------------------------------
Scenario: Publish event to local queue
Given a new AcpEventQueue
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 AcpEventQueue
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 AcpEventQueue
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 AcpEventQueue
When I unsubscribe using a non-existent subscription id
Then the unsubscribe should return False
Scenario: Get events respects limit
Given a new AcpEventQueue
When I publish 5 events
And I get events with limit 3
Then I should receive 3 events
# -----------------------------------------------------------------------
# AcpEventQueue — remote stub raises
# -----------------------------------------------------------------------
Scenario: Remote subscribe raises AcpNotAvailableError
Given a new AcpEventQueue
When I try to subscribe remotely to "http://remote:9090/events"
Then an AcpNotAvailableError should be raised
# -----------------------------------------------------------------------
# AcpVersionNegotiator
# -----------------------------------------------------------------------
Scenario: Negotiate supported version succeeds
Given a new AcpVersionNegotiator
When I negotiate version "1.0"
Then the negotiated version should be "1.0"
Scenario: Negotiate unsupported version raises error
Given a new AcpVersionNegotiator
When I try to negotiate version "2.0"
Then an AcpVersionMismatchError should be raised
And the error requested_version should be "2.0"
Scenario: is_supported returns True for valid version
Given a new AcpVersionNegotiator
Then version "1.0" should be supported
Scenario: is_supported returns False for invalid version
Given a new AcpVersionNegotiator
Then version "99.0" should not be supported
Scenario: get_current returns current version
Given a new AcpVersionNegotiator
Then the current version should be "1.0"
# -----------------------------------------------------------------------
# AcpRequest model validation
# -----------------------------------------------------------------------
Scenario: AcpRequest with valid operation succeeds
When I create an AcpRequest with operation "session.create"
Then the request should have a non-empty request_id
And the request acp_version should be "1.0"
Scenario: AcpRequest with empty operation fails validation
When I try to create an AcpRequest with empty operation
Then a validation error should be raised
# -----------------------------------------------------------------------
# AcpResponse model validation
# -----------------------------------------------------------------------
Scenario: AcpResponse with valid status ok succeeds
When I create an AcpResponse with status "ok" and request_id "REQ001"
Then the response should be valid
Scenario: AcpResponse with invalid status fails validation
When I try to create an AcpResponse with status "maybe"
Then a validation error should be raised
# -----------------------------------------------------------------------
# AcpErrorDetail model validation
# -----------------------------------------------------------------------
Scenario: AcpErrorDetail with valid fields succeeds
When I create an AcpErrorDetail with code "NOT_FOUND" and message "gone"
Then the error detail should be valid
Scenario: AcpErrorDetail with empty code fails validation
When I try to create an AcpErrorDetail with empty code
Then a validation error should be raised
# -----------------------------------------------------------------------
# AcpEvent model construction
# -----------------------------------------------------------------------
Scenario: AcpEvent auto-generates event_id and timestamp
When I create an AcpEvent 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: AcpError is a CleverAgentsError
Then AcpError should be a subclass of CleverAgentsError
Scenario: AcpNotAvailableError is an AcpError
Then AcpNotAvailableError should be a subclass of AcpError
Scenario: AcpVersionMismatchError is an AcpError
Then AcpVersionMismatchError should be a subclass of AcpError
Scenario: AcpOperationNotFoundError is an AcpError
Then AcpOperationNotFoundError should be a subclass of AcpError
Scenario: AcpNotAvailableError has default message
When I create an AcpNotAvailableError with default message
Then the error message should contain "not available in local mode"