| """ |
| Coverage expansion tests for proposal service. |
| |
| Tests cover critical code paths in: |
| - proposal_service.py: Proposal generation, validation, approval |
| - Governance integration: Permission checks for INTERN agents |
| |
| Target: Cover critical paths (happy path + error paths) to increase coverage. |
| """ |
|
|
| import pytest |
| from unittest.mock import Mock, patch, MagicMock, AsyncMock |
| from datetime import datetime, timedelta |
| from sqlalchemy.orm import Session |
|
|
| from core.proposal_service import ProposalService, PROPOSAL_EXECUTION_ENABLED |
| from core.models import ( |
| AgentProposal, |
| AgentRegistry, |
| AgentStatus, |
| ProposalStatus, |
| ProposalType, |
| UserRole |
| ) |
|
|
|
|
| class TestProposalServiceCoverage: |
| """Coverage expansion for ProposalService.""" |
|
|
| @pytest.fixture |
| def db_session(self): |
| """Get test database session.""" |
| from core.database import SessionLocal |
| session = SessionLocal() |
| yield session |
| session.rollback() |
| session.close() |
|
|
| @pytest.fixture |
| def proposal_service(self, db_session): |
| """Get proposal service instance.""" |
| return ProposalService(db_session) |
|
|
| @pytest.fixture |
| def intern_agent(self, db_session): |
| """Create test INTERN agent.""" |
| agent = AgentRegistry( |
| id="intern-agent-test", |
| name="Test Intern Agent", |
| status=AgentStatus.INTERN.value, |
| category="test", |
| module_path="test.module", |
| class_name="TestIntern", |
| confidence_score=0.6, |
| tenant_id="default", |
| user_id="test-user" |
| ) |
| db_session.add(agent) |
| db_session.commit() |
| db_session.refresh(agent) |
| return agent |
|
|
| |
| @pytest.mark.asyncio |
| async def test_create_action_proposal_success(self, proposal_service, intern_agent): |
| """Create proposal for INTERN agent action.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={"test": "context"}, |
| proposed_action={ |
| "action_type": "canvas_present", |
| "canvas_type": "chart", |
| "data": {"test": "data"} |
| }, |
| reasoning="This is a test proposal for coverage" |
| ) |
|
|
| assert proposal.id is not None |
| assert proposal.agent_id == intern_agent.id |
| assert proposal.status == ProposalStatus.PROPOSED.value |
| assert proposal.proposal_type == ProposalType.ACTION.value |
| assert "Test Intern Agent" in proposal.description |
|
|
| @pytest.mark.asyncio |
| async def test_create_action_proposal_with_canvas(self, proposal_service, intern_agent): |
| """Create proposal with canvas association.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "canvas_present"}, |
| reasoning="Test", |
| canvas_id="canvas-123", |
| session_id="session-456", |
| title="Custom Proposal Title" |
| ) |
|
|
| assert proposal.canvas_id == "canvas-123" |
| assert proposal.session_id == "session-456" |
| assert proposal.title == "Custom Proposal Title" |
|
|
| @pytest.mark.asyncio |
| async def test_create_action_proposal_agent_not_found(self, proposal_service): |
| """Handle proposal creation for non-existent agent.""" |
| with pytest.raises(ValueError, match="Agent .* not found"): |
| await proposal_service.create_action_proposal( |
| intern_agent_id="nonexistent-agent", |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_create_action_proposal_non_intern_status(self, proposal_service, db_session): |
| """Create proposal for agent with non-INTERN status (should log warning).""" |
| agent = AgentRegistry( |
| id="autonomous-agent", |
| name="Autonomous Agent", |
| status=AgentStatus.AUTONOMOUS.value, |
| category="test", |
| module_path="test", |
| class_name="TestAuto", |
| tenant_id="default", |
| user_id="test-user" |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test" |
| ) |
| assert proposal is not None |
|
|
| |
| @pytest.mark.asyncio |
| async def test_submit_for_approval_success(self, proposal_service, intern_agent): |
| """Submit proposal for approval.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test" |
| ) |
|
|
| |
| await proposal_service.submit_for_approval(proposal) |
|
|
| @pytest.mark.asyncio |
| async def test_submit_for_approval_invalid_status(self, proposal_service, intern_agent, db_session): |
| """Reject submission of proposal with invalid status.""" |
| proposal = AgentProposal( |
| id="test-proposal", |
| agent_id=intern_agent.id, |
| agent_name=intern_agent.name, |
| tenant_id="default", |
| user_id="test-user", |
| status=ProposalStatus.APPROVED.value, |
| proposal_type=ProposalType.ACTION.value, |
| title="Test", |
| description="Test" |
| ) |
| db_session.add(proposal) |
| db_session.commit() |
|
|
| with pytest.raises(ValueError, match="PROPOSED status"): |
| await proposal_service.submit_for_approval(proposal) |
|
|
| |
| @pytest.mark.asyncio |
| async def test_approve_proposal_success(self, proposal_service, intern_agent): |
| """Approve pending proposal.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "canvas_present"}, |
| reasoning="Test approval" |
| ) |
|
|
| with patch.object(proposal_service, '_execute_proposed_action', new_callable=AsyncMock) as mock_execute: |
| mock_execute.return_value = {"success": True, "executed_at": datetime.now().isoformat()} |
|
|
| result = await proposal_service.approve_proposal( |
| proposal.id, |
| user_id="admin-user" |
| ) |
|
|
| assert result["success"] == True |
|
|
| |
| proposal_service.db.refresh(proposal) |
| assert proposal.status == ProposalStatus.EXECUTED.value |
| assert proposal.approved_by == "admin-user" |
| assert proposal.approved_at is not None |
|
|
| @pytest.mark.asyncio |
| async def test_approve_proposal_with_modifications(self, proposal_service, intern_agent): |
| """Approve proposal with modifications.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "canvas_present", "param1": "value1"}, |
| reasoning="Test" |
| ) |
|
|
| modifications = {"param1": "modified_value", "param2": "new_value"} |
|
|
| with patch.object(proposal_service, '_execute_proposed_action', new_callable=AsyncMock) as mock_execute: |
| mock_execute.return_value = {"success": True} |
|
|
| await proposal_service.approve_proposal( |
| proposal.id, |
| user_id="admin-user", |
| modifications=modifications |
| ) |
|
|
| proposal_service.db.refresh(proposal) |
| assert proposal.modifications == modifications |
| |
| assert proposal.proposed_action["param1"] == "modified_value" |
| assert proposal.proposed_action["param2"] == "new_value" |
|
|
| @pytest.mark.asyncio |
| async def test_approve_proposal_not_found(self, proposal_service): |
| """Handle approval of non-existent proposal.""" |
| with pytest.raises(ValueError, match="not found"): |
| await proposal_service.approve_proposal("nonexistent-id", "admin-user") |
|
|
| @pytest.mark.asyncio |
| async def test_approve_proposal_invalid_status(self, proposal_service, intern_agent, db_session): |
| """Reject approval of already processed proposal.""" |
| proposal = AgentProposal( |
| id="test-proposal", |
| agent_id=intern_agent.id, |
| agent_name=intern_agent.name, |
| tenant_id="default", |
| user_id="test-user", |
| status=ProposalStatus.EXECUTED.value, |
| proposal_type=ProposalType.ACTION.value, |
| title="Test", |
| description="Test", |
| proposed_action={} |
| ) |
| db_session.add(proposal) |
| db_session.commit() |
|
|
| with pytest.raises(ValueError, match="PROPOSED status"): |
| await proposal_service.approve_proposal("test-proposal", "admin-user") |
|
|
| |
| @pytest.mark.asyncio |
| async def test_reject_proposal_success(self, proposal_service, intern_agent): |
| """Reject pending proposal.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test rejection" |
| ) |
|
|
| await proposal_service.reject_proposal( |
| proposal.id, |
| user_id="admin-user", |
| reason="Unsafe action detected" |
| ) |
|
|
| proposal_service.db.refresh(proposal) |
| assert proposal.status == ProposalStatus.REJECTED.value |
| assert proposal.approved_by == "admin-user" |
| assert proposal.approved_at is not None |
| assert proposal.execution_result["rejected"] == True |
| assert proposal.execution_result["reason"] == "Unsafe action detected" |
|
|
| @pytest.mark.asyncio |
| async def test_reject_proposal_not_found(self, proposal_service): |
| """Handle rejection of non-existent proposal.""" |
| with pytest.raises(ValueError, match="not found"): |
| await proposal_service.reject_proposal("nonexistent-id", "admin-user", "reason") |
|
|
| |
| @pytest.mark.asyncio |
| async def test_get_pending_proposals(self, proposal_service, intern_agent): |
| """Retrieve all pending proposals.""" |
| |
| for i in range(3): |
| await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={"index": i}, |
| proposed_action={"action_type": "test"}, |
| reasoning=f"Proposal {i}" |
| ) |
|
|
| pending = await proposal_service.get_pending_proposals() |
| assert len(pending) >= 3 |
| assert all(p.status == ProposalStatus.PROPOSED.value for p in pending) |
|
|
| @pytest.mark.asyncio |
| async def test_get_pending_proposals_by_agent(self, proposal_service, intern_agent, db_session): |
| """Retrieve pending proposals for specific agent.""" |
| |
| agent2 = AgentRegistry( |
| id="intern-agent-2", |
| name="Second Intern", |
| status=AgentStatus.INTERN.value, |
| category="test", |
| module_path="test", |
| class_name="Test", |
| tenant_id="default", |
| user_id="test-user" |
| ) |
| db_session.add(agent2) |
| db_session.commit() |
|
|
| |
| await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Agent 1 proposal" |
| ) |
| await proposal_service.create_action_proposal( |
| intern_agent_id=agent2.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Agent 2 proposal" |
| ) |
|
|
| |
| agent1_proposals = await proposal_service.get_pending_proposals(agent_id=intern_agent.id) |
| assert all(p.agent_id == intern_agent.id for p in agent1_proposals) |
|
|
| @pytest.mark.asyncio |
| async def test_get_pending_proposals_by_canvas(self, proposal_service, intern_agent): |
| """Retrieve pending proposals for specific canvas.""" |
| await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test", |
| canvas_id="canvas-123" |
| ) |
| await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test", |
| canvas_id="canvas-456" |
| ) |
|
|
| canvas_proposals = await proposal_service.get_pending_proposals(canvas_id="canvas-123") |
| assert all(p.canvas_id == "canvas-123" for p in canvas_proposals) |
|
|
| @pytest.mark.asyncio |
| async def test_get_pending_proposals_limit(self, proposal_service, intern_agent): |
| """Test limit parameter for pending proposals.""" |
| |
| for i in range(10): |
| await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={"index": i}, |
| proposed_action={}, |
| reasoning=f"Proposal {i}" |
| ) |
|
|
| |
| limited = await proposal_service.get_pending_proposals(limit=5) |
| assert len(limited) == 5 |
|
|
| |
| @pytest.mark.asyncio |
| async def test_get_proposal_history(self, proposal_service, intern_agent): |
| """Retrieve agent's proposal history.""" |
| |
| proposal1 = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Proposal 1" |
| ) |
| proposal1.status = ProposalStatus.APPROVED.value |
| proposal_service.db.commit() |
|
|
| proposal2 = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Proposal 2" |
| ) |
| proposal2.status = ProposalStatus.REJECTED.value |
| proposal2.execution_result = {"rejected": True, "reason": "test"} |
| proposal_service.db.commit() |
|
|
| history = await proposal_service.get_proposal_history(intern_agent.id) |
|
|
| assert len(history) >= 2 |
| assert all("proposal_id" in h for h in history) |
| assert all("status" in h for h in history) |
| assert all("created_at" in h for h in history) |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_proposed_action_disabled(self, proposal_service, intern_agent): |
| """Test execution when feature flag is disabled.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test" |
| ) |
|
|
| with patch.object(proposal_service, 'PROPOSAL_EXECUTION_ENABLED', False): |
| result = await proposal_service._execute_proposed_action(proposal) |
|
|
| assert result["success"] == False |
| assert result["skipped"] == True |
| assert "disabled" in result["message"] |
|
|
| @pytest.mark.asyncio |
| async def test_execute_proposed_action_unknown_type(self, proposal_service, intern_agent): |
| """Test execution with unknown action type.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "unknown_action"}, |
| reasoning="Test" |
| ) |
|
|
| result = await proposal_service._execute_proposed_action(proposal) |
|
|
| assert result["success"] == False |
| assert "Unknown action type" in result["error"] |
|
|
| @pytest.mark.asyncio |
| async def test_execute_proposed_action_exception_handling(self, proposal_service, intern_agent): |
| """Test exception handling in action execution.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "browser_automate"}, |
| reasoning="Test" |
| ) |
|
|
| |
| with patch.object(proposal_service, '_execute_browser_action', new_callable=AsyncMock) as mock_browser: |
| mock_browser.side_effect = Exception("Browser error") |
|
|
| result = await proposal_service._execute_proposed_action(proposal) |
|
|
| assert result["success"] == False |
| assert "Browser error" in result["error"] |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_browser_action(self, proposal_service, intern_agent): |
| """Test browser action execution.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={ |
| "action_type": "browser_automate", |
| "url": "https://example.com", |
| "action": "navigate" |
| }, |
| reasoning="Test browser action" |
| ) |
|
|
| |
| with patch('core.proposal_service.browser_tool') as mock_browser: |
| mock_browser.execute.return_value = {"success": True, "url": "https://example.com"} |
|
|
| result = await proposal_service._execute_browser_action(proposal, proposal.proposed_action) |
|
|
| assert result["success"] == True |
| mock_browser.execute.assert_called_once() |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_canvas_action(self, proposal_service, intern_agent): |
| """Test canvas action execution.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={ |
| "action_type": "canvas_present", |
| "canvas_type": "chart", |
| "data": {"test": "data"} |
| }, |
| reasoning="Test canvas action" |
| ) |
|
|
| |
| with patch('core.proposal_service.canvas_tool') as mock_canvas: |
| mock_canvas.create_canvas.return_value = "canvas-123" |
|
|
| result = await proposal_service._execute_canvas_action(proposal, proposal.proposed_action) |
|
|
| assert result["success"] == True |
| mock_canvas.create_canvas.assert_called_once() |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_integration_action(self, proposal_service, intern_agent): |
| """Test integration action execution.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={ |
| "action_type": "integration_connect", |
| "integration_id": "asana-123", |
| "action": "create_task" |
| }, |
| reasoning="Test integration action" |
| ) |
|
|
| |
| with patch('core.proposal_service.ServiceFactory') as mock_factory: |
| mock_service = AsyncMock() |
| mock_service.execute_action.return_value = {"success": True, "task_id": "task-123"} |
| mock_factory.get_service.return_value = mock_service |
|
|
| result = await proposal_service._execute_integration_action(proposal, proposal.proposed_action) |
|
|
| assert result["success"] == True |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_workflow_action(self, proposal_service, intern_agent): |
| """Test workflow action execution.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={ |
| "action_type": "workflow_trigger", |
| "workflow_id": "workflow-123" |
| }, |
| reasoning="Test workflow action" |
| ) |
|
|
| |
| with patch('core.proposal_service.WorkflowEngine') as mock_engine: |
| mock_engine_instance = AsyncMock() |
| mock_engine_instance.start_workflow.return_value = "execution-123" |
| mock_engine.return_value = mock_engine_instance |
|
|
| result = await proposal_service._execute_workflow_action(proposal, proposal.proposed_action) |
|
|
| assert result["success"] == True |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_device_action(self, proposal_service, intern_agent): |
| """Test device action execution.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={ |
| "action_type": "device_command", |
| "command": "screenshot" |
| }, |
| reasoning="Test device action" |
| ) |
|
|
| |
| with patch('core.proposal_service.device_tool') as mock_device: |
| mock_device.execute_command.return_value = {"success": True, "screenshot_path": "/tmp/screenshot.png"} |
|
|
| result = await proposal_service._execute_device_action(proposal, proposal.proposed_action) |
|
|
| assert result["success"] == True |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execute_agent_action(self, proposal_service, intern_agent): |
| """Test agent execution action.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={ |
| "action_type": "agent_execute", |
| "target_agent_id": "target-agent-123", |
| "prompt": "Execute this task" |
| }, |
| reasoning="Test agent execution" |
| ) |
|
|
| |
| with patch('core.proposal_service.atom_meta_agent') as mock_meta: |
| mock_meta.execute_agent.return_value = {"success": True, "response": "Task completed"} |
|
|
| result = await proposal_service._execute_agent_action(proposal, proposal.proposed_action) |
|
|
| assert result["success"] == True |
|
|
| |
| @pytest.mark.asyncio |
| async def test_create_proposal_episode_approved(self, proposal_service, intern_agent): |
| """Test episode creation for approved proposal.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test episode creation" |
| ) |
|
|
| |
| await proposal_service._create_proposal_episode( |
| proposal=proposal, |
| outcome="approved", |
| execution_result={"success": True} |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_create_proposal_episode_rejected(self, proposal_service, intern_agent): |
| """Test episode creation for rejected proposal.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test" |
| ) |
|
|
| |
| await proposal_service._create_proposal_episode( |
| proposal=proposal, |
| outcome="rejected", |
| rejection_reason="Unsafe action" |
| ) |
|
|
| |
| @pytest.mark.asyncio |
| async def test_agent_name_denormalized(self, proposal_service, intern_agent): |
| """Verify agent name is denormalized into proposal.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test agent name" |
| ) |
|
|
| assert proposal.agent_name == "Test Intern Agent" |
| assert proposal.agent_name in proposal.description |
|
|
| |
| @pytest.mark.asyncio |
| async def test_create_proposal_empty_action(self, proposal_service, intern_agent): |
| """Create proposal with empty proposed_action.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test empty action" |
| ) |
|
|
| assert proposal.proposed_action == {} |
| assert proposal.proposal_type == ProposalType.ACTION.value |
|
|
| |
| @pytest.mark.asyncio |
| async def test_confidence_in_description(self, proposal_service, db_session): |
| """Verify agent confidence appears in proposal description.""" |
| agent = AgentRegistry( |
| id="confident-agent", |
| name="Confident Agent", |
| status=AgentStatus.INTERN.value, |
| category="test", |
| module_path="test", |
| class_name="Test", |
| confidence_score=0.85, |
| tenant_id="default", |
| user_id="test-user" |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test confidence" |
| ) |
|
|
| assert "0.85" in proposal.description |
|
|
| |
| @pytest.mark.asyncio |
| async def test_proposal_timestamps(self, proposal_service, intern_agent): |
| """Verify proposal timestamps are set correctly.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test timestamps" |
| ) |
|
|
| assert proposal.created_at is not None |
| assert isinstance(proposal.created_at, datetime) |
|
|
| |
| with patch.object(proposal_service, '_execute_proposed_action', new_callable=AsyncMock) as mock_execute: |
| mock_execute.return_value = {"success": True} |
|
|
| await proposal_service.approve_proposal(proposal.id, "admin-user") |
|
|
| proposal_service.db.refresh(proposal) |
| assert proposal.approved_at is not None |
| assert proposal.completed_at is not None |
|
|
| |
| @pytest.mark.asyncio |
| async def test_multi_tenant_proposals(self, proposal_service, intern_agent, db_session): |
| """Test proposals are isolated by tenant.""" |
| |
| agent2 = AgentRegistry( |
| id="tenant2-agent", |
| name="Tenant 2 Agent", |
| status=AgentStatus.INTERN.value, |
| category="test", |
| module_path="test", |
| class_name="Test", |
| tenant_id="tenant-2", |
| user_id="user-2" |
| ) |
| db_session.add(agent2) |
| db_session.commit() |
|
|
| |
| await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Tenant 1" |
| ) |
| await proposal_service.create_action_proposal( |
| intern_agent_id=agent2.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Tenant 2" |
| ) |
|
|
| |
| tenant1_proposals = await proposal_service.get_pending_proposals(tenant_id="default") |
| assert all(p.tenant_id == "default" for p in tenant1_proposals) |
|
|
| |
| tenant2_proposals = await proposal_service.get_pending_proposals(tenant_id="tenant-2") |
| assert all(p.tenant_id == "tenant-2" for p in tenant2_proposals) |
|
|
| |
| @pytest.mark.asyncio |
| async def test_proposal_data_integrity(self, proposal_service, intern_agent): |
| """Verify proposal data is stored correctly.""" |
| proposed_action = { |
| "action_type": "canvas_present", |
| "canvas_type": "form", |
| "data": { |
| "fields": [ |
| {"name": "email", "type": "email"}, |
| {"name": "message", "type": "text"} |
| ] |
| } |
| } |
|
|
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={"test": "context"}, |
| proposed_action=proposed_action, |
| reasoning="Test data integrity" |
| ) |
|
|
| assert proposal.proposed_action == proposed_action |
| assert proposal.proposed_action["data"]["fields"][0]["name"] == "email" |
|
|
| |
| @pytest.mark.asyncio |
| async def test_proposal_status_transitions(self, proposal_service, intern_agent): |
| """Verify proposal status transitions are correct.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test status transitions" |
| ) |
|
|
| |
| assert proposal.status == ProposalStatus.PROPOSED.value |
|
|
| |
| with patch.object(proposal_service, '_execute_proposed_action', new_callable=AsyncMock) as mock_execute: |
| mock_execute.return_value = {"success": True} |
|
|
| await proposal_service.approve_proposal(proposal.id, "admin-user") |
| proposal_service.db.refresh(proposal) |
|
|
| |
| assert proposal.status == ProposalStatus.EXECUTED.value |
|
|
| |
| @pytest.mark.asyncio |
| async def test_proposal_with_session(self, proposal_service, intern_agent): |
| """Test proposal associated with chat session.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={}, |
| reasoning="Test session association", |
| session_id="chat-session-123" |
| ) |
|
|
| assert proposal.session_id == "chat-session-123" |
|
|
| |
| session_proposals = await proposal_service.get_pending_proposals( |
| agent_id=intern_agent.id |
| ) |
| assert any(p.session_id == "chat-session-123" for p in session_proposals) |
|
|
| |
| @pytest.mark.asyncio |
| async def test_execution_result_storage(self, proposal_service, intern_agent): |
| """Verify execution result is stored in proposal.""" |
| proposal = await proposal_service.create_action_proposal( |
| intern_agent_id=intern_agent.id, |
| trigger_context={}, |
| proposed_action={"action_type": "test"}, |
| reasoning="Test execution result" |
| ) |
|
|
| execution_result = { |
| "success": True, |
| "output": {"result": "completed"}, |
| "executed_at": datetime.now().isoformat() |
| } |
|
|
| with patch.object(proposal_service, '_execute_proposed_action', new_callable=AsyncMock) as mock_execute: |
| mock_execute.return_value = execution_result |
|
|
| await proposal_service.approve_proposal(proposal.id, "admin-user") |
| proposal_service.db.refresh(proposal) |
|
|
| assert proposal.execution_result == execution_result |
| assert proposal.execution_result["success"] == True |
|
|