| """ |
| Cross-Service E2E Workflow Tests |
| |
| Tests for end-to-end workflows spanning multiple services. |
| |
| These tests validate that: |
| 1. Agent execution leads to canvas presentation |
| 2. Governance checks integrate with LLM calls |
| 3. Canvas presentation creates episode records |
| 4. Episode retrieval integrates with feedback |
| 5. Multiple services chain together correctly |
| 6. Workflow rollback works on failure |
| 7. Workflow timeout handling is correct |
| 8. Concurrent users don't interfere |
| |
| Key Workflows Tested: |
| - Agent → Canvas presentation |
| - Governance → LLM call |
| - Canvas → Episode creation |
| - Episode → Feedback aggregation |
| - Multi-service chaining |
| - Rollback on failure |
| """ |
|
|
| import pytest |
| import time |
| from datetime import datetime, timedelta |
| from typing import Dict, Any |
|
|
| from sqlalchemy.orm import Session |
|
|
| from core.models import ( |
| AgentRegistry, |
| AgentExecution, |
| CanvasAudit, |
| AgentEpisode, |
| AgentFeedback |
| ) |
|
|
|
|
| class TestAgentToCanvasWorkflow: |
| """Test agent execution to canvas presentation workflow.""" |
|
|
| def test_agent_to_canvas_workflow(self, db_session: Session): |
| """ |
| E2E: Agent execution → canvas presentation. |
| |
| Tests that agent execution can generate insights that are |
| presented as a canvas to the user. |
| |
| Workflow: |
| 1. Create agent |
| 2. Execute agent task |
| 3. Generate insights |
| 4. Create canvas with insights |
| 5. Verify canvas audit record |
| """ |
| |
| agent = AgentRegistry( |
| id="workflow-agent-1", |
| name="Workflow Agent 1", |
| description="Test agent-to-canvas workflow", |
| category="Testing", |
| module_path="test.workflow", |
| class_name="WorkflowAgent1", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="workflow-test-user", |
| task="Generate insights from data", |
| status="completed", |
| input_data={"query": "analyze sales data"}, |
| output_data={ |
| "insights": [ |
| "Sales increased by 20%", |
| "Top product: Widget A", |
| "Region: North America" |
| ] |
| }, |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| |
| canvas_id = "workflow-canvas-1" |
| canvas = CanvasAudit( |
| id=str(canvas_id), |
| canvas_id=canvas_id, |
| tenant_id="test-tenant", |
| action_type="present", |
| canvas_type="docs", |
| canvas_data={ |
| "title": "Sales Insights", |
| "content": execution.output_data["insights"], |
| "source_execution_id": execution.id |
| }, |
| created_at=datetime.utcnow() |
| ) |
| db_session.add(canvas) |
| db_session.commit() |
|
|
| |
| retrieved_execution = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution.id |
| ).first() |
| assert retrieved_execution is not None |
| assert retrieved_execution.status == "completed" |
| assert "insights" in retrieved_execution.output_data |
|
|
| retrieved_canvas = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas_id |
| ).first() |
| assert retrieved_canvas is not None |
| assert retrieved_canvas.canvas_type == "docs" |
| assert "insights" in retrieved_canvas.canvas_data |
|
|
| def test_multi_agent_to_canvas_workflow(self, db_session: Session): |
| """ |
| E2E: Multiple agents executing to single canvas. |
| |
| Tests that insights from multiple agents can be combined |
| into a single canvas presentation. |
| |
| Workflow: |
| 1. Create multiple agents |
| 2. Execute all agents |
| 3. Combine insights |
| 4. Create unified canvas |
| """ |
| |
| agents = [] |
| for i in range(3): |
| agent = AgentRegistry( |
| id=f"multi-agent-{i}", |
| name=f"Multi Agent {i}", |
| description="Test multi-agent workflow", |
| category="Testing", |
| module_path="test.multi", |
| class_name=f"MultiAgent{i}", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| agents.append(agent) |
| db_session.commit() |
|
|
| |
| executions = [] |
| for agent in agents: |
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="multi-agent-user", |
| task=f"Task {agent.id}", |
| status="completed", |
| output_data={"insight": f"Insight from {agent.id}"}, |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| executions.append(execution) |
| db_session.commit() |
|
|
| |
| combined_insights = [ |
| exec.output_data["insight"] for exec in executions |
| ] |
|
|
| canvas_id = "multi-agent-canvas" |
| canvas = CanvasAudit( |
| id=str(canvas_id), |
| canvas_id=canvas_id, |
| tenant_id="test-tenant", |
| action_type="present", |
| canvas_type="docs", |
| canvas_data={ |
| "title": "Combined Insights", |
| "insights": combined_insights, |
| "agent_count": len(agents) |
| }, |
| created_at=datetime.utcnow() |
| ) |
| db_session.add(canvas) |
| db_session.commit() |
|
|
| |
| retrieved = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas_id |
| ).first() |
| assert retrieved is not None |
| assert retrieved.canvas_data["agent_count"] == 3 |
| assert len(retrieved.canvas_data["insights"]) == 3 |
|
|
|
|
| class TestGovernanceToLLMWorkflow: |
| """Test governance check to LLM call workflow.""" |
|
|
| def test_governance_to_llm_workflow(self, db_session: Session): |
| """ |
| E2E: Governance check → LLM call. |
| |
| Tests that governance checks are performed before LLM calls, |
| and that agents with appropriate maturity can invoke LLM. |
| |
| Workflow: |
| 1. Create AUTONOMOUS agent |
| 2. Perform governance check |
| 3. Check passes (AUTONOMOUS can use LLM) |
| 4. Simulate LLM call result |
| 5. Record execution with LLM output |
| """ |
| |
| agent = AgentRegistry( |
| id="llm-workflow-agent", |
| name="LLM Workflow Agent", |
| description="Test governance to LLM workflow", |
| category="Testing", |
| module_path="test.llm", |
| class_name="LLMWorkflowAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| can_use_llm = agent.status == "AUTONOMOUS" |
| assert can_use_llm, "AUTONOMOUS agent should be able to use LLM" |
|
|
| |
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="llm-workflow-user", |
| task="Generate summary with LLM", |
| status="completed", |
| input_data={"prompt": "Summarize the data"}, |
| output_data={ |
| "llm_response": "This is a summary of the data...", |
| "tokens_used": 150, |
| "model": "gpt-4" |
| }, |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| |
| retrieved = db_session.query(AgentExecution).filter( |
| AgentExecution.agent_id == agent.id |
| ).first() |
| assert retrieved is not None |
| assert "llm_response" in retrieved.output_data |
| assert retrieved.output_data["tokens_used"] == 150 |
|
|
| def test_governance_blocks_student_from_llm(self, db_session: Session): |
| """ |
| E2E: STUDENT agent blocked from LLM call. |
| |
| Tests that governance properly blocks STUDENT agents from |
| making LLM calls (read-only maturity). |
| |
| Workflow: |
| 1. Create STUDENT agent |
| 2. Perform governance check |
| 3. Check fails (STUDENT cannot use LLM) |
| 4. Verify no LLM execution created |
| """ |
| |
| agent = AgentRegistry( |
| id="student-llm-agent", |
| name="Student LLM Agent", |
| description="Test STUDENT blocked from LLM", |
| category="Testing", |
| module_path="test.student", |
| class_name="StudentLLMAgent", |
| status="STUDENT", |
| confidence_score=0.4 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| can_use_llm = agent.status == "AUTONOMOUS" |
| assert not can_use_llm, "STUDENT agent should be blocked from LLM" |
|
|
| |
| executions = db_session.query(AgentExecution).filter( |
| AgentExecution.agent_id == agent.id, |
| AgentExecution.output_data["llm_response"].isnot(None) |
| ).all() |
| assert len(executions) == 0, "STUDENT agent should not have LLM executions" |
|
|
|
|
| class TestCanvasToEpisodeWorkflow: |
| """Test canvas presentation to episode creation workflow.""" |
|
|
| def test_canvas_to_episode_workflow(self, db_session: Session): |
| """ |
| E2E: Canvas presentation → episode creation. |
| |
| Tests that canvas presentations create episode records |
| for episodic memory tracking. |
| |
| Workflow: |
| 1. Create agent and execute |
| 2. Present canvas |
| 3. Create episode linked to canvas |
| 4. Verify episode references canvas |
| """ |
| |
| agent = AgentRegistry( |
| id="canvas-episode-agent", |
| name="Canvas Episode Agent", |
| description="Test canvas to episode workflow", |
| category="Testing", |
| module_path="test.canvas_ep", |
| class_name="CanvasEpisodeAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="canvas-ep-user", |
| task="Present data visualization", |
| status="completed", |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| |
| canvas_id = "canvas-episode-1" |
| canvas = CanvasAudit( |
| id=str(canvas_id), |
| canvas_id=canvas_id, |
| tenant_id="test-tenant", |
| action_type="present", |
| canvas_type="chart", |
| canvas_data={"type": "line", "data": [1, 2, 3]}, |
| created_at=datetime.utcnow() |
| ) |
| db_session.add(canvas) |
| db_session.commit() |
|
|
| |
| episode = AgentEpisode( |
| id=f"episode-{canvas_id}", |
| agent_id=agent.id, |
| tenant_id="test-tenant", |
| execution_id=execution.id, |
| task_description="Present data visualization", |
| maturity_at_time="autonomous", |
| constitutional_score=1.0, |
| outcome="success" |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| retrieved = db_session.query(AgentEpisode).filter( |
| AgentEpisode.id == episode.id |
| ).first() |
| assert retrieved is not None |
| assert retrieved.execution_id == execution.id |
|
|
|
|
| class TestEpisodeToFeedbackWorkflow: |
| """Test episode retrieval to feedback aggregation workflow.""" |
|
|
| def test_episode_to_feedback_workflow(self, db_session: Session): |
| """ |
| E2E: Episode retrieval → feedback aggregation. |
| |
| Tests that user feedback on episodes is properly aggregated |
| and linked for retrieval weighting. |
| |
| Workflow: |
| 1. Create episode |
| 2. User provides feedback |
| 3. Feedback linked to episode |
| 4. Verify feedback aggregation |
| """ |
| |
| agent = AgentRegistry( |
| id="feedback-episode-agent", |
| name="Feedback Episode Agent", |
| description="Test episode to feedback workflow", |
| category="Testing", |
| module_path="test.fb_ep", |
| class_name="FeedbackEpisodeAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| episode = AgentEpisode( |
| id="feedback-episode-1", |
| agent_id=agent.id, |
| tenant_id="test-tenant", |
| task_description="Task for feedback testing", |
| maturity_at_time="autonomous", |
| constitutional_score=1.0, |
| outcome="success" |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| feedback1 = AgentFeedback( |
| agent_id=agent.id, |
| user_id="feedback-user-1", |
| feedback_type="thumbs_up", |
| rating=1.0, |
| comments="Great job!", |
| episode_id=episode.id, |
| timestamp=datetime.utcnow() |
| ) |
| db_session.add(feedback1) |
|
|
| feedback2 = AgentFeedback( |
| agent_id=agent.id, |
| user_id="feedback-user-2", |
| feedback_type="thumbs_up", |
| rating=1.0, |
| comments="Very helpful", |
| episode_id=episode.id, |
| timestamp=datetime.utcnow() |
| ) |
| db_session.add(feedback2) |
| db_session.commit() |
|
|
| |
| feedbacks = db_session.query(AgentFeedback).filter( |
| AgentFeedback.episode_id == episode.id |
| ).all() |
| assert len(feedbacks) == 2 |
|
|
| |
| avg_rating = sum(f.rating for f in feedbacks) / len(feedbacks) |
| assert avg_rating == 1.0 |
|
|
|
|
| class TestMultiServiceChaining: |
| """Test multiple services chained together.""" |
|
|
| def test_multi_service_chaining(self, db_session: Session): |
| """ |
| E2E: Multiple services chained in sequence. |
| |
| Tests complete workflow: Agent → LLM → Canvas → Episode → Feedback |
| |
| Workflow: |
| 1. Execute agent with LLM |
| 2. Present canvas with LLM output |
| 3. Create episode |
| 4. Collect feedback |
| 5. Verify complete chain |
| """ |
| |
| agent = AgentRegistry( |
| id="chain-agent", |
| name="Chain Agent", |
| description="Test multi-service chaining", |
| category="Testing", |
| module_path="test.chain", |
| class_name="ChainAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="chain-user", |
| task="Analyze and present", |
| status="completed", |
| output_data={ |
| "llm_response": "Analysis complete", |
| "insights": ["Insight 1", "Insight 2"] |
| }, |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| |
| canvas_id = "chain-canvas" |
| canvas = CanvasAudit( |
| id=str(canvas_id), |
| canvas_id=canvas_id, |
| tenant_id="test-tenant", |
| action_type="present", |
| canvas_type="docs", |
| canvas_data={ |
| "title": "Analysis Results", |
| "content": execution.output_data["llm_response"] |
| }, |
| created_at=datetime.utcnow() |
| ) |
| db_session.add(canvas) |
| db_session.commit() |
|
|
| |
| episode = AgentEpisode( |
| id="chain-episode", |
| agent_id=agent.id, |
| tenant_id="test-tenant", |
| execution_id=execution.id, |
| task_description="Analyze and present", |
| maturity_at_time="autonomous", |
| constitutional_score=1.0, |
| outcome="success" |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| feedback = AgentFeedback( |
| agent_id=agent.id, |
| user_id="chain-user", |
| feedback_type="thumbs_up", |
| rating=1.0, |
| comments="Excellent analysis", |
| canvas_id=canvas_id, |
| episode_id=episode.id, |
| timestamp=datetime.utcnow() |
| ) |
| db_session.add(feedback) |
| db_session.commit() |
|
|
| |
| assert execution.id is not None |
| assert canvas.canvas_id == canvas_id |
| assert episode.execution_id == execution.id |
| assert feedback.canvas_id == canvas_id |
| assert feedback.episode_id == episode.id |
|
|
|
|
| class TestWorkflowRollbackOnFailure: |
| """Test workflow rollback when intermediate step fails.""" |
|
|
| def test_workflow_rollback_on_failure(self, db_session: Session): |
| """ |
| E2E: Workflow rollback on intermediate step failure. |
| |
| Tests that when an intermediate step fails, the workflow |
| doesn't leave inconsistent state. |
| |
| Workflow: |
| 1. Execute agent (succeeds) |
| 2. Try to create canvas (fails - invalid data) |
| 3. Verify agent execution still recorded |
| 4. Verify canvas not created |
| """ |
| |
| agent = AgentRegistry( |
| id="rollback-agent", |
| name="Rollback Agent", |
| description="Test workflow rollback", |
| category="Testing", |
| module_path="test.rollback", |
| class_name="RollbackAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="rollback-user", |
| task="Task that will fail at canvas stage", |
| status="completed", |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| |
| |
|
|
| |
| retrieved_exec = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution.id |
| ).first() |
| assert retrieved_exec is not None, "Agent execution should be recorded" |
|
|
| canvas_count = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == "rollback-canvas" |
| ).count() |
| assert canvas_count == 0, "Canvas should not be created" |
|
|
|
|
| class TestWorkflowTimeoutHandling: |
| """Test workflow timeout handling.""" |
|
|
| def test_workflow_timeout_handling(self, db_session: Session): |
| """ |
| E2E: Workflow timeout doesn't leave inconsistent state. |
| |
| Tests that when a workflow times out, partial state is |
| properly handled. |
| |
| Workflow: |
| 1. Start agent execution |
| 2. Simulate timeout |
| 3. Mark execution as failed |
| 4. Verify no partial canvas/episode created |
| """ |
| |
| agent = AgentRegistry( |
| id="timeout-agent", |
| name="Timeout Agent", |
| description="Test workflow timeout", |
| category="Testing", |
| module_path="test.timeout", |
| class_name="TimeoutAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| execution = AgentExecution( |
| agent_id=agent.id, |
| user_id="timeout-user", |
| task="Long running task", |
| status="in_progress", |
| started_at=datetime.utcnow() |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| |
| execution.status = "failed" |
| execution.error_message = "Workflow timeout after 30 seconds" |
| execution.completed_at = datetime.utcnow() |
| db_session.commit() |
|
|
| |
| |
| retrieved = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution.id |
| ).first() |
| assert retrieved.status == "failed" |
| assert "timeout" in retrieved.error_message.lower() |
|
|
|
|
| class TestWorkflowWithConcurrentUsers: |
| """Test workflow with multiple concurrent users.""" |
|
|
| def test_workflow_with_concurrent_users(self, db_session: Session): |
| """ |
| E2E: Multiple users executing workflows simultaneously. |
| |
| Tests that concurrent users don't interfere with each other's |
| workflow state. |
| |
| Workflow: |
| 1. User A executes agent and creates canvas |
| 2. User B executes agent and creates canvas concurrently |
| 3. Verify both workflows complete independently |
| 4. Verify no data mixing between users |
| """ |
| |
| agent = AgentRegistry( |
| id="concurrent-users-agent", |
| name="Concurrent Users Agent", |
| description="Test concurrent user workflows", |
| category="Testing", |
| module_path="test.concurrent_users", |
| class_name="ConcurrentUsersAgent", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| execution_a = AgentExecution( |
| agent_id=agent.id, |
| user_id="user-a", |
| task="User A task", |
| status="completed", |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution_a) |
|
|
| canvas_a = CanvasAudit( |
| id="canvas-a", |
| canvas_id="canvas-a", |
| tenant_id="test-tenant", |
| action_type="present", |
| canvas_type="docs", |
| canvas_data={"user": "A"}, |
| created_at=datetime.utcnow() |
| ) |
| db_session.add(canvas_a) |
|
|
| |
| execution_b = AgentExecution( |
| agent_id=agent.id, |
| user_id="user-b", |
| task="User B task", |
| status="completed", |
| started_at=datetime.utcnow(), |
| completed_at=datetime.utcnow() |
| ) |
| db_session.add(execution_b) |
|
|
| canvas_b = CanvasAudit( |
| id="canvas-b", |
| canvas_id="canvas-b", |
| tenant_id="test-tenant", |
| action_type="present", |
| canvas_type="docs", |
| canvas_data={"user": "B"}, |
| created_at=datetime.utcnow() |
| ) |
| db_session.add(canvas_b) |
| db_session.commit() |
|
|
| |
| exec_a = db_session.query(AgentExecution).filter( |
| AgentExecution.user_id == "user-a" |
| ).first() |
| exec_b = db_session.query(AgentExecution).filter( |
| AgentExecution.user_id == "user-b" |
| ).first() |
|
|
| assert exec_a is not None |
| assert exec_b is not None |
| assert exec_a.user_id != exec_b.user_id |
|
|
| |
| canvas_a_retrieved = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == "canvas-a" |
| ).first() |
| canvas_b_retrieved = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == "canvas-b" |
| ).first() |
|
|
| assert canvas_a_retrieved.canvas_data["user"] == "A" |
| assert canvas_b_retrieved.canvas_data["user"] == "B" |
|
|