# tests/crud/syndarix/test_agent_instance_crud.py """ Tests for AgentInstance CRUD operations. """ import uuid from decimal import Decimal import pytest from app.crud.syndarix import agent_instance as agent_instance_crud from app.models.syndarix import AgentStatus from app.schemas.syndarix import AgentInstanceCreate, AgentInstanceUpdate class TestAgentInstanceCreate: """Tests for agent instance creation.""" @pytest.mark.asyncio async def test_create_agent_instance_success( self, async_test_db, test_project_crud, test_agent_type_crud ): """Test successfully creating an agent instance.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: instance_data = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name="TestBot", status=AgentStatus.IDLE, current_task=None, short_term_memory={"context": "initial"}, long_term_memory_ref="project-123/agent-456", session_id="session-abc", ) result = await agent_instance_crud.create(session, obj_in=instance_data) assert result.id is not None assert result.agent_type_id == test_agent_type_crud.id assert result.project_id == test_project_crud.id assert result.status == AgentStatus.IDLE assert result.short_term_memory == {"context": "initial"} @pytest.mark.asyncio async def test_create_agent_instance_minimal( self, async_test_db, test_project_crud, test_agent_type_crud ): """Test creating agent instance with minimal fields.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: instance_data = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name="MinimalBot", ) result = await agent_instance_crud.create(session, obj_in=instance_data) assert result.status == AgentStatus.IDLE # Default assert result.tasks_completed == 0 assert result.tokens_used == 0 class TestAgentInstanceRead: """Tests for agent instance read operations.""" @pytest.mark.asyncio async def test_get_agent_instance_by_id( self, async_test_db, test_agent_instance_crud ): """Test getting agent instance by ID.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.get( session, id=str(test_agent_instance_crud.id) ) assert result is not None assert result.id == test_agent_instance_crud.id @pytest.mark.asyncio async def test_get_agent_instance_by_id_not_found(self, async_test_db): """Test getting non-existent agent instance returns None.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.get(session, id=str(uuid.uuid4())) assert result is None @pytest.mark.asyncio async def test_get_with_details(self, async_test_db, test_agent_instance_crud): """Test getting agent instance with related details.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.get_with_details( session, instance_id=test_agent_instance_crud.id, ) assert result is not None assert result["instance"].id == test_agent_instance_crud.id assert result["agent_type_name"] is not None assert result["project_name"] is not None class TestAgentInstanceUpdate: """Tests for agent instance update operations.""" @pytest.mark.asyncio async def test_update_agent_instance_status( self, async_test_db, test_agent_instance_crud ): """Test updating agent instance status.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: instance = await agent_instance_crud.get( session, id=str(test_agent_instance_crud.id) ) update_data = AgentInstanceUpdate( status=AgentStatus.WORKING, current_task="Processing feature request", ) result = await agent_instance_crud.update( session, db_obj=instance, obj_in=update_data ) assert result.status == AgentStatus.WORKING assert result.current_task == "Processing feature request" @pytest.mark.asyncio async def test_update_agent_instance_memory( self, async_test_db, test_agent_instance_crud ): """Test updating agent instance short-term memory.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: instance = await agent_instance_crud.get( session, id=str(test_agent_instance_crud.id) ) new_memory = { "conversation": ["msg1", "msg2"], "decisions": {"key": "value"}, } update_data = AgentInstanceUpdate(short_term_memory=new_memory) result = await agent_instance_crud.update( session, db_obj=instance, obj_in=update_data ) assert result.short_term_memory == new_memory class TestAgentInstanceStatusUpdate: """Tests for agent instance status update method.""" @pytest.mark.asyncio async def test_update_status(self, async_test_db, test_agent_instance_crud): """Test updating agent instance status via dedicated method.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.update_status( session, instance_id=test_agent_instance_crud.id, status=AgentStatus.WORKING, current_task="Working on feature X", ) assert result is not None assert result.status == AgentStatus.WORKING assert result.current_task == "Working on feature X" assert result.last_activity_at is not None @pytest.mark.asyncio async def test_update_status_nonexistent(self, async_test_db): """Test updating status of non-existent instance returns None.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.update_status( session, instance_id=uuid.uuid4(), status=AgentStatus.WORKING, ) assert result is None class TestAgentInstanceTerminate: """Tests for agent instance termination.""" @pytest.mark.asyncio async def test_terminate_agent_instance( self, async_test_db, test_project_crud, test_agent_type_crud ): """Test terminating an agent instance.""" _test_engine, AsyncTestingSessionLocal = async_test_db # Create an instance to terminate async with AsyncTestingSessionLocal() as session: instance_data = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name="TerminateBot", status=AgentStatus.WORKING, ) created = await agent_instance_crud.create(session, obj_in=instance_data) instance_id = created.id # Terminate async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.terminate( session, instance_id=instance_id ) assert result is not None assert result.status == AgentStatus.TERMINATED assert result.terminated_at is not None assert result.current_task is None assert result.session_id is None @pytest.mark.asyncio async def test_terminate_nonexistent_instance(self, async_test_db): """Test terminating non-existent instance returns None.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.terminate( session, instance_id=uuid.uuid4() ) assert result is None class TestAgentInstanceMetrics: """Tests for agent instance metrics operations.""" @pytest.mark.asyncio async def test_record_task_completion( self, async_test_db, test_agent_instance_crud ): """Test recording task completion with metrics.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.record_task_completion( session, instance_id=test_agent_instance_crud.id, tokens_used=1500, cost_incurred=Decimal("0.0150"), ) assert result is not None assert result.tasks_completed == 1 assert result.tokens_used == 1500 assert result.cost_incurred == Decimal("0.0150") assert result.last_activity_at is not None @pytest.mark.asyncio async def test_record_multiple_task_completions( self, async_test_db, test_project_crud, test_agent_type_crud ): """Test recording multiple task completions accumulates metrics.""" _test_engine, AsyncTestingSessionLocal = async_test_db # Create fresh instance async with AsyncTestingSessionLocal() as session: instance_data = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name="MetricsBot", ) created = await agent_instance_crud.create(session, obj_in=instance_data) instance_id = created.id # Record first task async with AsyncTestingSessionLocal() as session: await agent_instance_crud.record_task_completion( session, instance_id=instance_id, tokens_used=1000, cost_incurred=Decimal("0.0100"), ) # Record second task async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.record_task_completion( session, instance_id=instance_id, tokens_used=2000, cost_incurred=Decimal("0.0200"), ) assert result.tasks_completed == 2 assert result.tokens_used == 3000 assert result.cost_incurred == Decimal("0.0300") @pytest.mark.asyncio async def test_get_project_metrics( self, async_test_db, test_project_crud, test_agent_instance_crud ): """Test getting aggregated metrics for a project.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: result = await agent_instance_crud.get_project_metrics( session, project_id=test_project_crud.id, ) assert result is not None assert "total_instances" in result assert "active_instances" in result assert "idle_instances" in result assert "total_tasks_completed" in result assert "total_tokens_used" in result assert "total_cost_incurred" in result class TestAgentInstanceByProject: """Tests for getting instances by project.""" @pytest.mark.asyncio async def test_get_by_project( self, async_test_db, test_project_crud, test_agent_instance_crud ): """Test getting instances by project.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: instances, total = await agent_instance_crud.get_by_project( session, project_id=test_project_crud.id, ) assert total >= 1 assert all(i.project_id == test_project_crud.id for i in instances) @pytest.mark.asyncio async def test_get_by_project_with_status( self, async_test_db, test_project_crud, test_agent_type_crud ): """Test getting instances by project filtered by status.""" _test_engine, AsyncTestingSessionLocal = async_test_db # Create instances with different statuses async with AsyncTestingSessionLocal() as session: idle_instance = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name="IdleBot", status=AgentStatus.IDLE, ) await agent_instance_crud.create(session, obj_in=idle_instance) working_instance = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name="WorkerBot", status=AgentStatus.WORKING, ) await agent_instance_crud.create(session, obj_in=working_instance) async with AsyncTestingSessionLocal() as session: instances, _total = await agent_instance_crud.get_by_project( session, project_id=test_project_crud.id, status=AgentStatus.WORKING, ) assert all(i.status == AgentStatus.WORKING for i in instances) class TestAgentInstanceByAgentType: """Tests for getting instances by agent type.""" @pytest.mark.asyncio async def test_get_by_agent_type( self, async_test_db, test_agent_type_crud, test_agent_instance_crud ): """Test getting instances by agent type.""" _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: instances = await agent_instance_crud.get_by_agent_type( session, agent_type_id=test_agent_type_crud.id, ) assert len(instances) >= 1 assert all(i.agent_type_id == test_agent_type_crud.id for i in instances) class TestBulkTerminate: """Tests for bulk termination of instances.""" @pytest.mark.asyncio async def test_bulk_terminate_by_project( self, async_test_db, test_project_crud, test_agent_type_crud ): """Test bulk terminating all instances in a project.""" _test_engine, AsyncTestingSessionLocal = async_test_db # Create multiple instances async with AsyncTestingSessionLocal() as session: for i in range(3): instance_data = AgentInstanceCreate( agent_type_id=test_agent_type_crud.id, project_id=test_project_crud.id, name=f"BulkBot-{i}", status=AgentStatus.WORKING if i < 2 else AgentStatus.IDLE, ) await agent_instance_crud.create(session, obj_in=instance_data) # Bulk terminate async with AsyncTestingSessionLocal() as session: count = await agent_instance_crud.bulk_terminate_by_project( session, project_id=test_project_crud.id, ) assert count >= 3 # Verify all are terminated async with AsyncTestingSessionLocal() as session: instances, _ = await agent_instance_crud.get_by_project( session, project_id=test_project_crud.id, ) for instance in instances: assert instance.status == AgentStatus.TERMINATED