@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"