| """ |
| Canvas presentation integration tests (INTG-06). |
| |
| Tests cover: |
| - Canvas creation and presentation |
| - Form submissions with governance |
| - Chart rendering |
| - Sheet operations |
| - Canvas audit trail |
| - Multi-agent coordination |
| """ |
| import pytest |
| from fastapi.testclient import TestClient |
| from sqlalchemy.orm import Session |
| from tests.factories.canvas_factory import CanvasAuditFactory |
| from tests.factories.agent_factory import StudentAgentFactory, AutonomousAgentFactory, InternAgentFactory, SupervisedAgentFactory |
| from tests.factories.user_factory import UserFactory |
| from core.models import CanvasAudit, AgentRegistry |
| from unittest.mock import Mock, AsyncMock, patch |
| import uuid |
|
|
|
|
| class TestCanvasCreation: |
| """Test canvas creation and presentation.""" |
|
|
| def test_create_canvas_requires_authentication(self, client_no_auth: TestClient): |
| """Test canvas creation requires authentication.""" |
| response = client_no_auth.post("/api/canvas/create", json={ |
| "type": "generic", |
| "title": "Test Canvas" |
| }) |
|
|
| |
| assert response.status_code in [401, 403, 404] |
|
|
| def test_create_canvas_with_valid_token(self, client: TestClient, auth_token: str): |
| """Test canvas creation with valid authentication.""" |
| response = client.post( |
| "/api/canvas/create", |
| json={ |
| "type": "generic", |
| "title": "Test Canvas", |
| "data": {"message": "Hello World"} |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| |
| assert response.status_code in [201, 200, 404, 405] |
|
|
| def test_present_canvas_with_agent(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test presenting canvas through agent.""" |
| agent = AutonomousAgentFactory(_session=db_session) |
| canvas = CanvasAuditFactory(canvas_type="generic", agent_id=agent.id, _session=db_session) |
| db_session.commit() |
|
|
| |
| audit = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=agent.id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="generic", |
| action="present", |
| audit_metadata={} |
| ) |
| db_session.add(audit) |
| db_session.commit() |
|
|
| |
| created_audit = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id, |
| CanvasAudit.action == "present" |
| ).first() |
| assert created_audit is not None |
|
|
|
|
| class TestCanvasForms: |
| """Test canvas form submissions.""" |
|
|
| def test_form_submit_requires_authentication(self, client_no_auth: TestClient): |
| """Test form submission requires authentication.""" |
| response = client_no_auth.post("/api/canvas/submit", json={ |
| "canvas_id": "test-canvas", |
| "form_data": {"field1": "value1"} |
| }) |
|
|
| assert response.status_code == 401 |
|
|
| def test_form_submit_with_valid_data(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test form submission with valid data.""" |
| canvas = CanvasAuditFactory( |
| canvas_type="form", |
| component_name="test_form", |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| response = client.post( |
| "/api/canvas/submit", |
| json={ |
| "canvas_id": canvas.id, |
| "form_data": { |
| "name": "Test User", |
| "email": "test@example.com" |
| } |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 202] |
|
|
| def test_form_submit_with_agent_governance(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test form submission with agent governance validation.""" |
| student = StudentAgentFactory(_session=db_session) |
| canvas = CanvasAuditFactory(canvas_type="form", _session=db_session) |
| db_session.commit() |
|
|
| |
| response = client.post( |
| "/api/canvas/submit", |
| json={ |
| "canvas_id": canvas.id, |
| "form_data": {"field1": "value1"}, |
| "agent_id": student.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [403, 202] |
|
|
|
|
| class TestCanvasCharts: |
| """Test canvas chart rendering.""" |
|
|
| def test_chart_creation(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test creating chart canvas.""" |
| response = client.post( |
| "/api/canvas/create", |
| json={ |
| "type": "chart", |
| "chart_type": "line", |
| "title": "Test Chart", |
| "data": { |
| "labels": ["Jan", "Feb", "Mar"], |
| "datasets": [{"data": [10, 20, 30]}] |
| } |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [201, 200, 404] |
|
|
| def test_chart_data_validation(self, client: TestClient, auth_token: str): |
| """Test chart data is validated.""" |
| |
| response = client.post( |
| "/api/canvas/create", |
| json={ |
| "type": "chart", |
| "chart_type": "line", |
| "title": "Invalid Chart" |
| |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [400, 422, 404] |
|
|
| def test_supported_chart_types(self, client: TestClient, auth_token: str): |
| """Test all supported chart types.""" |
| chart_types = ["line", "bar", "pie", "doughnut"] |
|
|
| for chart_type in chart_types: |
| response = client.post( |
| "/api/canvas/create", |
| json={ |
| "type": "chart", |
| "chart_type": chart_type, |
| "title": f"{chart_type} chart", |
| "data": { |
| "labels": ["A", "B"], |
| "datasets": [{"data": [1, 2]}] |
| } |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [201, 200, 404], \ |
| f"Chart type {chart_type} failed with status {response.status_code}" |
|
|
|
|
| class TestCanvasSheets: |
| """Test canvas sheet (spreadsheet) operations.""" |
|
|
| def test_sheet_creation(self, client: TestClient, auth_token: str): |
| """Test creating sheet canvas.""" |
| response = client.post( |
| "/api/canvas/create", |
| json={ |
| "type": "sheet", |
| "title": "Test Sheet", |
| "data": { |
| "rows": [ |
| ["Name", "Value"], |
| ["Row 1", 100], |
| ["Row 2", 200] |
| ] |
| } |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [201, 200, 404] |
|
|
| def test_sheet_cell_update(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test updating sheet cell.""" |
| sheet = CanvasAuditFactory( |
| canvas_type="sheet", |
| audit_metadata={"data": {"rows": [["A1", "B1"]]}}, |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| response = client.post( |
| f"/api/canvas/sheet/{sheet.id}/update", |
| json={ |
| "cell": "A1", |
| "value": "Updated Value" |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 202, 404] |
|
|
|
|
| class TestCanvasAuditTrail: |
| """Test canvas audit trail.""" |
|
|
| def test_canvas_action_creates_audit(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test canvas actions create audit entries.""" |
| canvas = CanvasAuditFactory(canvas_type="generic", _session=db_session) |
| db_session.commit() |
|
|
| |
| audit = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=canvas.agent_id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="generic", |
| action="present", |
| audit_metadata={} |
| ) |
| db_session.add(audit) |
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id |
| ).all() |
|
|
| assert len(audits) > 0 |
| assert any(a.action == "present" for a in audits) |
|
|
| def test_canvas_audit_includes_agent_context(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test canvas audit includes agent context.""" |
| agent = AutonomousAgentFactory(_session=db_session) |
| canvas = CanvasAuditFactory(canvas_type="generic", agent_id=agent.id, _session=db_session) |
| db_session.commit() |
|
|
| |
| audit = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=agent.id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="generic", |
| action="present", |
| audit_metadata={} |
| ) |
| db_session.add(audit) |
| db_session.commit() |
|
|
| retrieved_audit = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id |
| ).first() |
|
|
| assert retrieved_audit is not None |
| assert retrieved_audit.agent_id == agent.id |
|
|
| def test_canvas_audit_filters_by_action(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test filtering audits by action type.""" |
| canvas = CanvasAuditFactory(canvas_type="form", _session=db_session) |
| db_session.commit() |
|
|
| |
| actions = ["present", "submit", "close", "update"] |
| for action in actions: |
| audit = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=canvas.agent_id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="form", |
| action=action, |
| audit_metadata={} |
| ) |
| db_session.add(audit) |
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id |
| ).all() |
|
|
| assert len(audits) >= len(actions) |
| recorded_actions = {a.action for a in audits} |
| assert recorded_actions.issuperset(set(actions)) |
|
|
|
|
| class TestMultiAgentCanvasCoordination: |
| """Test multi-agent canvas collaboration.""" |
|
|
| def test_sequential_agent_collaboration(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test sequential agent collaboration on canvas.""" |
| agent1 = AutonomousAgentFactory(name="Agent 1", _session=db_session) |
| agent2 = AutonomousAgentFactory(name="Agent 2", _session=db_session) |
| canvas = CanvasAuditFactory(canvas_type="orchestration", _session=db_session) |
| db_session.commit() |
|
|
| |
| audit1 = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=agent1.id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="orchestration", |
| action="present", |
| audit_metadata={} |
| ) |
| db_session.add(audit1) |
|
|
| |
| audit2 = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=agent2.id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="orchestration", |
| action="present", |
| audit_metadata={} |
| ) |
| db_session.add(audit2) |
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id, |
| CanvasAudit.action == "present" |
| ).all() |
|
|
| agent_ids = {a.agent_id for a in audits} |
| assert agent1.id in agent_ids |
| assert agent2.id in agent_ids |
|
|
| def test_canvas_governance_by_maturity(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test canvas governance enforcement by maturity level.""" |
| student = StudentAgentFactory(_session=db_session) |
| intern = InternAgentFactory(_session=db_session) |
| supervised = SupervisedAgentFactory(_session=db_session) |
| autonomous = AutonomousAgentFactory(_session=db_session) |
|
|
| canvas = CanvasAuditFactory(canvas_type="form", _session=db_session) |
| db_session.commit() |
|
|
| |
| agents_and_expected = [ |
| (student, False), |
| (intern, True), |
| (supervised, True), |
| (autonomous, True) |
| ] |
|
|
| for agent, should_succeed in agents_and_expected: |
| |
| audit = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=agent.id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="form", |
| action="present", |
| audit_metadata={"agent_maturity": agent.status} |
| ) |
| db_session.add(audit) |
|
|
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id |
| ).all() |
| assert len(audits) >= 4 |
|
|
| def test_canvas_collaboration_modes(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test different canvas collaboration modes.""" |
| agent1 = AutonomousAgentFactory(_session=db_session) |
| agent2 = AutonomousAgentFactory(_session=db_session) |
| canvas = CanvasAuditFactory(canvas_type="orchestration", _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| collaboration_modes = ["sequential", "parallel", "locked"] |
|
|
| for mode in collaboration_modes: |
| audit = CanvasAudit( |
| id=str(uuid.uuid4()), |
| workspace_id="default", |
| agent_id=agent1.id, |
| user_id=canvas.user_id, |
| canvas_id=canvas.id, |
| component_type="orchestration", |
| action="present", |
| audit_metadata={"collaboration_mode": mode} |
| ) |
| db_session.add(audit) |
|
|
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_id == canvas.id |
| ).all() |
|
|
| recorded_modes = { |
| a.audit_metadata.get("collaboration_mode") |
| for a in audits |
| if a.audit_metadata and "collaboration_mode" in a.audit_metadata |
| } |
| assert recorded_modes.issuperset(set(collaboration_modes)) |
|
|
|
|
| class TestCanvasTypeSupport: |
| """Test support for different canvas types.""" |
|
|
| def test_canvas_types(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test all supported canvas types.""" |
| canvas_types = [ |
| "generic", "docs", "email", "sheets", |
| "orchestration", "terminal", "coding" |
| ] |
|
|
| for canvas_type in canvas_types: |
| canvas = CanvasAuditFactory(canvas_type=canvas_type, _session=db_session) |
|
|
| |
| assert canvas.canvas_type == canvas_type |
|
|
| db_session.commit() |
|
|
| |
| canvases = db_session.query(CanvasAudit).filter( |
| CanvasAudit.canvas_type.in_(canvas_types) |
| ).all() |
|
|
| assert len(canvases) == len(canvas_types) |
|
|
| def test_canvas_component_types(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test different component types within canvases.""" |
| component_types = ["chart", "markdown", "form", "sheet", "terminal"] |
|
|
| for comp_type in component_types: |
| audit = CanvasAuditFactory( |
| canvas_type="generic", |
| component_type=comp_type, |
| _session=db_session |
| ) |
|
|
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).filter( |
| CanvasAudit.component_type.in_(component_types) |
| ).all() |
|
|
| assert len(audits) >= len(component_types) |
|
|
|
|
| class TestCanvasAuditMetadata: |
| """Test canvas audit metadata handling.""" |
|
|
| def test_audit_metadata_storage(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test audit metadata is stored correctly.""" |
| metadata = { |
| "form_fields": 3, |
| "validation_errors": [], |
| "processing_time_ms": 150, |
| "user_agent": "test-client" |
| } |
|
|
| audit = CanvasAuditFactory( |
| canvas_type="form", |
| audit_metadata=metadata, |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| |
| retrieved = db_session.query(CanvasAudit).filter( |
| CanvasAudit.id == audit.id |
| ).first() |
|
|
| assert retrieved is not None |
| assert retrieved.audit_metadata == metadata |
| assert retrieved.audit_metadata["form_fields"] == 3 |
|
|
| def test_audit_metadata_with_complex_data(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test audit metadata with nested structures.""" |
| complex_metadata = { |
| "chart_config": { |
| "type": "line", |
| "datasets": [ |
| {"label": "Series 1", "data": [1, 2, 3]}, |
| {"label": "Series 2", "data": [4, 5, 6]} |
| ], |
| "options": { |
| "responsive": True, |
| "plugins": {"legend": {"display": True}} |
| } |
| }, |
| "render_stats": { |
| "duration_ms": 200, |
| "data_points": 6 |
| } |
| } |
|
|
| audit = CanvasAuditFactory( |
| canvas_type="chart", |
| audit_metadata=complex_metadata, |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| |
| retrieved = db_session.query(CanvasAudit).filter( |
| CanvasAudit.id == audit.id |
| ).first() |
|
|
| assert retrieved.audit_metadata["chart_config"]["datasets"][0]["data"] == [1, 2, 3] |
|
|