""" Governance integration tests with database. Tests cover governance flows that require real database operations: - Agent registration and maturity updates with database persistence - Governance cache synchronization with database - Agent execution record creation with full lifecycle - Permission checks with database - Audit trail persistence and querying - Trigger interceptor database operations - Proposal creation and approval workflow - Training session tracking Uses transaction rollback pattern for test isolation. """ import pytest from datetime import datetime, timedelta from sqlalchemy.orm import Session from sqlalchemy import and_, or_ from enum import Enum import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from core.models import ( AgentRegistry, AgentExecution, AgentFeedback, BlockedTriggerContext, AgentProposal, ProposalStatus, ProposalType, SupervisionSession, SupervisionStatus, TrainingSession, TriggerSource, User, UserRole, AgentStatus as AgentStatusEnum ) # Use the AgentStatus enum from models AgentStatus = AgentStatusEnum from core.agent_governance_service import AgentGovernanceService from core.governance_cache import GovernanceCache from core.trigger_interceptor import TriggerInterceptor from tests.factories.agent_factory import ( AgentFactory, StudentAgentFactory, InternAgentFactory, SupervisedAgentFactory, AutonomousAgentFactory ) from tests.factories.user_factory import UserFactory class TestAgentGovernanceIntegration: """Test agent governance with database persistence.""" def test_register_agent_with_database(self, db_session: Session): """Test agent creation and query from database.""" # Create agent directly in database agent = AgentRegistry( name="TestAgent", description="Test agent for governance", category="testing", module_path="test.module", class_name="TestClass", status=AgentStatus.INTERN.value, confidence_score=0.6, user_id="test-user" ) db_session.add(agent) db_session.commit() db_session.refresh(agent) # Verify agent exists in database retrieved = db_session.query(AgentRegistry).filter( AgentRegistry.id == agent.id ).first() assert retrieved is not None assert retrieved.name == "TestAgent" assert retrieved.status == AgentStatus.INTERN.value def test_update_agent_maturity(self, db_session: Session): """Test updating agent maturity and verifying persistence.""" # Create agent agent = StudentAgentFactory(name="PromotionAgent", _session=db_session) db_session.commit() # Update maturity agent.status = AgentStatus.SUPERVISED.value agent.confidence_score = 0.8 db_session.commit() # Query and verify retrieved = db_session.query(AgentRegistry).filter( AgentRegistry.id == agent.id ).first() assert retrieved.status == AgentStatus.SUPERVISED.value assert retrieved.confidence_score == 0.8 def test_agent_execution_record_creation(self, db_session: Session): """Test full agent execution record lifecycle with database.""" # Create agent agent = AgentFactory(name="ExecutionAgent", _session=db_session) db_session.commit() # Create execution record execution = AgentExecution( agent_id=agent.id, status="running", input_summary="Test input", triggered_by="manual", started_at=datetime.utcnow() ) db_session.add(execution) db_session.commit() # Update execution execution.status = "completed" execution.completed_at = datetime.utcnow() execution.duration_seconds = 2.5 execution.result_summary = "Test completed" db_session.commit() # Verify full lifecycle retrieved = db_session.query(AgentExecution).filter( AgentExecution.id == execution.id ).first() assert retrieved.status == "completed" assert retrieved.duration_seconds == 2.5 assert retrieved.result_summary == "Test completed" def test_permission_check_with_database(self, db_session: Session): """Test permission checks using database agent data.""" # Create agents with different maturity levels student = StudentAgentFactory(name="StudentAgent", _session=db_session) intern = InternAgentFactory(name="InternAgent", _session=db_session) supervised = SupervisedAgentFactory(name="SupervisedAgent", _session=db_session) autonomous = AutonomousAgentFactory(name="AutonomousAgent", _session=db_session) db_session.commit() # Query and check permissions # STUDENT agents cannot execute automated triggers student_agent = db_session.query(AgentRegistry).filter( AgentRegistry.id == student.id ).first() assert student_agent.status == AgentStatus.STUDENT.value # INTERN agents can stream presentations intern_agent = db_session.query(AgentRegistry).filter( AgentRegistry.id == intern.id ).first() assert intern_agent.status == AgentStatus.INTERN.value # SUPERVISED agents can execute state changes supervised_agent = db_session.query(AgentRegistry).filter( AgentRegistry.id == supervised.id ).first() assert supervised_agent.status == AgentStatus.SUPERVISED.value # AUTONOMOUS agents can do critical operations autonomous_agent = db_session.query(AgentRegistry).filter( AgentRegistry.id == autonomous.id ).first() assert autonomous_agent.status == AgentStatus.AUTONOMOUS.value def test_audit_trail_persistence(self, db_session: Session): """Test audit trail creation and querying.""" # Create agent and execution agent = AgentFactory(name="AuditAgent", _session=db_session) execution = AgentExecution( agent_id=agent.id, status="running", triggered_by="manual" ) db_session.add(execution) db_session.commit() # Create audit trail (feedback) feedback = AgentFeedback( agent_id=agent.id, agent_execution_id=execution.id, user_id="test-user", rating=5, original_output="Excellent performance", user_correction="Great work" ) db_session.add(feedback) db_session.commit() # Query audit trail audit_records = db_session.query(AgentFeedback).filter( AgentFeedback.agent_id == agent.id ).all() assert len(audit_records) > 0 assert audit_records[0].rating == 5 assert "Excellent" in audit_records[0].original_output def test_multiple_executions_query(self, db_session: Session): """Test querying multiple executions for an agent.""" # Create agent agent = AgentFactory(name="MultiExecAgent", _session=db_session) db_session.commit() # Create multiple executions for i in range(5): execution = AgentExecution( agent_id=agent.id, status="completed", triggered_by="manual", duration_seconds=1.0 + i * 0.5, started_at=datetime.utcnow() ) db_session.add(execution) db_session.commit() # Query executions executions = db_session.query(AgentExecution).filter( AgentExecution.agent_id == agent.id ).all() assert len(executions) >= 5 def test_agent_status_filter(self, db_session: Session): """Test filtering agents by status.""" # Create agents with different statuses active = AgentFactory( name="ActiveAgent", status=AgentStatus.AUTONOMOUS, _session=db_session ) inactive = AgentFactory( name="InactiveAgent", status=AgentStatus.PAUSED, _session=db_session ) archived = AgentFactory( name="ArchivedAgent", status=AgentStatus.DEPRECATED, _session=db_session ) db_session.commit() # Query autonomous agents autonomous_agents = db_session.query(AgentRegistry).filter( AgentRegistry.status == AgentStatus.AUTONOMOUS ).all() assert len(autonomous_agents) >= 1 assert any(a.name == "ActiveAgent" for a in autonomous_agents) class TestTriggerInterceptorIntegration: """Test trigger interceptor with database operations.""" def test_blocked_trigger_context_saved(self, db_session: Session): """Test that blocked triggers are saved to database.""" # Create student agent agent = StudentAgentFactory(name="BlockedAgent", _session=db_session) user = UserFactory(email="blocked@test.com", _session=db_session) db_session.commit() # Create blocked trigger context blocked_context = BlockedTriggerContext( agent_id=agent.id, agent_name=agent.name, agent_maturity_at_block=AgentStatus.STUDENT.value, confidence_score_at_block=agent.confidence_score, trigger_source=TriggerSource.WORKFLOW_ENGINE.value, trigger_type="workflow_trigger", trigger_context={"workflow_id": "test-workflow"}, routing_decision="training", block_reason="STUDENT maturity blocks automated triggers" ) db_session.add(blocked_context) db_session.commit() # Verify blocked context was saved retrieved = db_session.query(BlockedTriggerContext).filter( BlockedTriggerContext.agent_id == agent.id ).first() assert retrieved is not None assert retrieved.resolved == False assert "STUDENT" in retrieved.block_reason def test_query_blocked_triggers_by_agent(self, db_session: Session): """Test querying blocked triggers for a specific agent.""" # Create student agent agent = StudentAgentFactory(name="MultiBlockedAgent", _session=db_session) user = UserFactory(email="multi-blocked@test.com", _session=db_session) db_session.commit() # Create multiple blocked contexts for i in range(3): blocked = BlockedTriggerContext( agent_id=agent.id, agent_name=agent.name, agent_maturity_at_block=AgentStatus.STUDENT.value, confidence_score_at_block=agent.confidence_score, trigger_source=TriggerSource.MANUAL.value, trigger_type=f"trigger_{i}", trigger_context={"index": i}, routing_decision="training", block_reason=f"STUDENT maturity blocks trigger {i}" ) db_session.add(blocked) db_session.commit() # Query blocked triggers blocked_triggers = db_session.query(BlockedTriggerContext).filter( BlockedTriggerContext.agent_id == agent.id ).all() assert len(blocked_triggers) >= 3 def test_blocked_trigger_time_range(self, db_session: Session): """Test querying blocked triggers within time range.""" # Create student agent agent = StudentAgentFactory(name="TimeRangeAgent", _session=db_session) user = UserFactory(email="timerange@test.com", _session=db_session) db_session.commit() now = datetime.utcnow() # Create blocked context at specific time blocked = BlockedTriggerContext( agent_id=agent.id, agent_name=agent.name, agent_maturity_at_block=AgentStatus.STUDENT.value, confidence_score_at_block=agent.confidence_score, trigger_source=TriggerSource.DATA_SYNC.value, trigger_type="test_trigger", trigger_context={}, routing_decision="training", block_reason="Test time range query", created_at=now ) db_session.add(blocked) db_session.commit() # Query within time range recent_blocked = db_session.query(BlockedTriggerContext).filter( and_( BlockedTriggerContext.agent_id == agent.id, BlockedTriggerContext.created_at >= now - timedelta(minutes=5) ) ).all() assert len(recent_blocked) >= 1 class TestProposalWorkflowIntegration: """Test proposal creation and approval workflow with database.""" def test_proposal_creation_and_approval(self, db_session: Session): """Test full proposal lifecycle from creation to approval.""" # Create intern agent agent = InternAgentFactory(name="ProposalAgent", _session=db_session) user = UserFactory(email="proposal@test.com", _session=db_session) db_session.commit() # Create proposal proposal = AgentProposal( agent_id=agent.id, agent_name=agent.name, proposal_type=ProposalType.ACTION.value, title="Delete record proposal", description="Test proposal for integration", proposed_action={"record_id": "test-123"}, reasoning="Test proposal for integration", proposed_by=agent.id ) db_session.add(proposal) db_session.commit() # Update proposal status proposal.status = ProposalStatus.APPROVED.value proposal.reviewed_at = datetime.utcnow() proposal.review_comments="Approved for testing" db_session.commit() # Verify proposal lifecycle retrieved = db_session.query(AgentProposal).filter( AgentProposal.id == proposal.id ).first() assert retrieved.status == ProposalStatus.APPROVED.value assert retrieved.review_comments == "Approved for testing" def test_query_pending_proposals(self, db_session: Session): """Test querying pending proposals.""" # Create agent and user agent = InternAgentFactory(name="PendingProposalAgent", _session=db_session) user = UserFactory(email="pending@test.com", _session=db_session) db_session.commit() # Create multiple proposals for i in range(3): proposal = AgentProposal( agent_id=agent.id, agent_name=agent.name, proposal_type=ProposalType.ACTION.value, title=f"Action {i}", description="Test pending proposal", proposed_action={"action": f"action_{i}"}, reasoning="Test pending proposal", proposed_by=agent.id ) db_session.add(proposal) db_session.commit() # Query pending proposals pending = db_session.query(AgentProposal).filter( and_( AgentProposal.agent_id == agent.id, AgentProposal.status == ProposalStatus.PROPOSED ) ).all() assert len(pending) >= 3 def test_proposal_rejection(self, db_session: Session): """Test proposal rejection.""" # Create agent and user agent = InternAgentFactory(name="RejectAgent", _session=db_session) user = UserFactory(email="reject@test.com", _session=db_session) db_session.commit() # Create and reject proposal proposal = AgentProposal( agent_id=agent.id, agent_name=agent.name, proposal_type=ProposalType.ACTION.value, title="Risky action", description="Test rejection", proposed_action={"action": "risky_action"}, reasoning="Test rejection", proposed_by=agent.id ) db_session.add(proposal) db_session.commit() # Reject proposal proposal.status = ProposalStatus.REJECTED.value proposal.reviewed_at = datetime.utcnow() proposal.review_comments="Too risky" db_session.commit() # Verify rejection retrieved = db_session.query(AgentProposal).filter( AgentProposal.id == proposal.id ).first() assert retrieved.status == ProposalStatus.REJECTED.value assert "Too risky" in retrieved.review_comments class TestTrainingSessionIntegration: """Test training session tracking with database.""" def test_training_session_tracking(self, db_session: Session): """Test creating and tracking training sessions.""" # Create student agent agent = StudentAgentFactory(name="TrainingAgent", _session=db_session) user = UserFactory(email="training@test.com", _session=db_session) db_session.commit() # Create a proposal first (required for TrainingSession) proposal = AgentProposal( agent_id=agent.id, agent_name=agent.name, proposal_type=ProposalType.TRAINING.value, title="Basic Workflow Execution", description="Scenario based training", reasoning="Test training session", proposed_by=agent.id ) db_session.add(proposal) db_session.commit() # Create training session session = TrainingSession( proposal_id=proposal.id, agent_id=agent.id, agent_name=agent.name, supervisor_id=user.id, status="in_progress", started_at=datetime.utcnow() ) db_session.add(session) db_session.commit() # Update session session.status = "completed" session.completed_at = datetime.utcnow() session.performance_score = 0.95 db_session.commit() # Verify training session retrieved = db_session.query(TrainingSession).filter( TrainingSession.id == session.id ).first() assert retrieved.status == "completed" assert retrieved.performance_score == 0.95 def test_query_training_sessions_by_agent(self, db_session: Session): """Test querying training sessions for an agent.""" # Create student agent agent = StudentAgentFactory(name="MultiTrainingAgent", _session=db_session) user = UserFactory(email="multi-training@test.com", _session=db_session) db_session.commit() # Create multiple training sessions for i in range(3): # Create proposal first proposal = AgentProposal( agent_id=agent.id, agent_name=agent.name, proposal_type=ProposalType.TRAINING.value, title=f"Scenario {i}", description=f"Training scenario {i}", reasoning="Test training session", proposed_by=agent.id ) db_session.add(proposal) db_session.commit() session = TrainingSession( proposal_id=proposal.id, agent_id=agent.id, agent_name=agent.name, supervisor_id=user.id, status="completed", performance_score=0.8 + (i * 0.05), started_at=datetime.utcnow(), completed_at=datetime.utcnow() ) db_session.add(session) db_session.commit() # Query training sessions sessions = db_session.query(TrainingSession).filter( TrainingSession.agent_id == agent.id ).all() assert len(sessions) >= 3 def test_supervision_session_tracking(self, db_session: Session): """Test supervision session database operations.""" # Create supervised agent agent = SupervisedAgentFactory(name="SupervisedAgent", _session=db_session) user = UserFactory(email="supervision@test.com", _session=db_session) db_session.commit() # Create supervision session supervision = SupervisionSession( agent_id=agent.id, agent_name=agent.name, supervisor_id=user.id, workspace_id="test-workspace", trigger_context={"workflow_id": "test-workflow"}, status=SupervisionStatus.RUNNING.value, started_at=datetime.utcnow() ) db_session.add(supervision) db_session.commit() # Update supervision supervision.status = SupervisionStatus.COMPLETED.value supervision.completed_at = datetime.utcnow() supervision.intervention_count = 2 db_session.commit() # Verify supervision session retrieved = db_session.query(SupervisionSession).filter( SupervisionSession.id == supervision.id ).first() assert retrieved.status == SupervisionStatus.COMPLETED assert retrieved.intervention_count == 2 class TestGovernanceDatabaseQueries: """Test governance-related database queries and aggregations.""" def test_count_agents_by_maturity(self, db_session: Session): """Test counting agents grouped by maturity level.""" from sqlalchemy import func # Create agents with different maturity levels StudentAgentFactory(name="CountStudent1", _session=db_session) StudentAgentFactory(name="CountStudent2", _session=db_session) InternAgentFactory(name="CountIntern", _session=db_session) SupervisedAgentFactory(name="CountSupervised", _session=db_session) db_session.commit() # Count by maturity result = db_session.query( AgentRegistry.status, func.count(AgentRegistry.id) ).group_by(AgentRegistry.status).all() maturity_counts = {row[0]: row[1] for row in result} assert maturity_counts.get(AgentStatus.STUDENT.value, 0) >= 2 def test_query_recent_executions(self, db_session: Session): """Test querying recent executions across all agents.""" # Create agent and executions agent = AgentFactory(name="RecentExecAgent", _session=db_session) db_session.commit() for i in range(5): execution = AgentExecution( agent_id=agent.id, status="completed", triggered_by="manual", started_at=datetime.utcnow() ) db_session.add(execution) db_session.commit() # Query recent executions recent = db_session.query(AgentExecution).order_by( AgentExecution.started_at.desc() ).limit(10).all() assert len(recent) >= 5 def test_agent_feedback_aggregation(self, db_session: Session): """Test aggregating feedback scores for an agent.""" from sqlalchemy import func # Create agent and feedback agent = AgentFactory(name="FeedbackAgent", _session=db_session) db_session.commit() ratings = [5, 4, 5, 3, 4] for rating in ratings: feedback = AgentFeedback( agent_id=agent.id, user_id="test-user", rating=rating, original_output="Test feedback", user_correction="No correction needed" ) db_session.add(feedback) db_session.commit() # Calculate average rating avg_rating = db_session.query( func.avg(AgentFeedback.rating) ).filter( AgentFeedback.agent_id == agent.id ).scalar() expected_avg = sum(ratings) / len(ratings) assert abs(avg_rating - expected_avg) < 0.01