/** * Unit tests for src/repositories/AgentRepository.ts * Uses a mocked pg.Pool — no real database connection. */ import { Pool } from 'pg'; import { AgentRepository } from '../../../src/repositories/AgentRepository'; import { IAgent, ICreateAgentRequest, IUpdateAgentRequest, IAgentListFilters } from '../../../src/types/index'; jest.mock('pg', () => ({ Pool: jest.fn().mockImplementation(() => ({ query: jest.fn(), connect: jest.fn(), })), })); // ─── helpers ───────────────────────────────────────────────────────────────── const AGENT_ROW = { agent_id: 'a1b2c3d4-0000-0000-0000-000000000001', email: 'agent@sentryagent.ai', agent_type: 'screener', version: '1.0.0', capabilities: ['resume:read'], owner: 'team-a', deployment_env: 'production', status: 'active', created_at: new Date('2026-03-28T09:00:00Z'), updated_at: new Date('2026-03-28T09:00:00Z'), }; const EXPECTED_AGENT: IAgent = { agentId: AGENT_ROW.agent_id, email: AGENT_ROW.email, agentType: 'screener', version: AGENT_ROW.version, capabilities: AGENT_ROW.capabilities, owner: AGENT_ROW.owner, deploymentEnv: 'production', status: 'active', createdAt: AGENT_ROW.created_at, updatedAt: AGENT_ROW.updated_at, }; // ─── suite ─────────────────────────────────────────────────────────────────── describe('AgentRepository', () => { let pool: jest.Mocked; let repo: AgentRepository; beforeEach(() => { jest.clearAllMocks(); pool = new Pool() as jest.Mocked; repo = new AgentRepository(pool); }); // ── create ────────────────────────────────────────────────────────────────── describe('create()', () => { const createData: ICreateAgentRequest = { email: 'agent@sentryagent.ai', agentType: 'screener', version: '1.0.0', capabilities: ['resume:read'], owner: 'team-a', deploymentEnv: 'production', }; it('should insert a row and return a mapped IAgent', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [AGENT_ROW], rowCount: 1 }); const result = await repo.create(createData); expect(pool.query).toHaveBeenCalledTimes(1); const [sql, params] = (pool.query as jest.Mock).mock.calls[0] as [string, unknown[]]; expect(sql).toContain('INSERT INTO agents'); expect(params).toContain(createData.email); expect(params).toContain(createData.agentType); expect(result).toMatchObject({ email: EXPECTED_AGENT.email, agentType: EXPECTED_AGENT.agentType, status: 'active', }); }); }); // ── findById ───────────────────────────────────────────────────────────────── describe('findById()', () => { it('should return a mapped IAgent when the row exists', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [AGENT_ROW], rowCount: 1 }); const result = await repo.findById(AGENT_ROW.agent_id); expect(pool.query).toHaveBeenCalledWith( expect.stringContaining('SELECT'), [AGENT_ROW.agent_id], ); expect(result).toMatchObject(EXPECTED_AGENT); }); it('should return null when no rows are returned', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [], rowCount: 0 }); const result = await repo.findById('nonexistent'); expect(result).toBeNull(); }); }); // ── findByEmail ────────────────────────────────────────────────────────────── describe('findByEmail()', () => { it('should return a mapped IAgent when the email exists', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [AGENT_ROW], rowCount: 1 }); const result = await repo.findByEmail(AGENT_ROW.email); expect(pool.query).toHaveBeenCalledWith( expect.stringContaining('email'), [AGENT_ROW.email], ); expect(result).toMatchObject(EXPECTED_AGENT); }); it('should return null when no rows are returned', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [], rowCount: 0 }); const result = await repo.findByEmail('notfound@example.com'); expect(result).toBeNull(); }); }); // ── findAll ────────────────────────────────────────────────────────────────── describe('findAll()', () => { it('should return paginated agents with total count (no filters)', async () => { (pool.query as jest.Mock) .mockResolvedValueOnce({ rows: [{ count: '1' }], rowCount: 1 }) // count query .mockResolvedValueOnce({ rows: [AGENT_ROW], rowCount: 1 }); // data query const filters: IAgentListFilters = { page: 1, limit: 20 }; const result = await repo.findAll(filters); expect(pool.query).toHaveBeenCalledTimes(2); expect(result.total).toBe(1); expect(result.agents).toHaveLength(1); expect(result.agents[0]).toMatchObject(EXPECTED_AGENT); }); it('should apply owner, agentType, and status filters', async () => { (pool.query as jest.Mock) .mockResolvedValueOnce({ rows: [{ count: '0' }], rowCount: 1 }) .mockResolvedValueOnce({ rows: [], rowCount: 0 }); const filters: IAgentListFilters = { page: 1, limit: 10, owner: 'team-a', agentType: 'screener', status: 'active', }; const result = await repo.findAll(filters); const [countSql] = (pool.query as jest.Mock).mock.calls[0] as [string, unknown[]]; expect(countSql).toContain('owner'); expect(countSql).toContain('agent_type'); expect(countSql).toContain('status'); expect(result.total).toBe(0); expect(result.agents).toHaveLength(0); }); it('should return an empty list when no agents exist', async () => { (pool.query as jest.Mock) .mockResolvedValueOnce({ rows: [{ count: '0' }], rowCount: 1 }) .mockResolvedValueOnce({ rows: [], rowCount: 0 }); const result = await repo.findAll({ page: 1, limit: 20 }); expect(result.total).toBe(0); expect(result.agents).toEqual([]); }); }); // ── update ─────────────────────────────────────────────────────────────────── describe('update()', () => { it('should update fields and return mapped IAgent', async () => { const updatedRow = { ...AGENT_ROW, version: '2.0.0' }; (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [updatedRow], rowCount: 1 }); const data: IUpdateAgentRequest = { version: '2.0.0' }; const result = await repo.update(AGENT_ROW.agent_id, data); expect(pool.query).toHaveBeenCalledTimes(1); const [sql] = (pool.query as jest.Mock).mock.calls[0] as [string, unknown[]]; expect(sql).toContain('UPDATE agents'); expect(result).not.toBeNull(); expect(result?.version).toBe('2.0.0'); }); it('should return null when the agent is not found after update', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [], rowCount: 0 }); const result = await repo.update('nonexistent', { version: '2.0.0' }); expect(result).toBeNull(); }); it('should return null when no fields are provided', async () => { const result = await repo.update(AGENT_ROW.agent_id, {}); expect(pool.query).not.toHaveBeenCalled(); expect(result).toBeNull(); }); it('should update multiple fields at once', async () => { const updatedRow = { ...AGENT_ROW, version: '3.0.0', status: 'suspended', owner: 'team-b' }; (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [updatedRow], rowCount: 1 }); const data: IUpdateAgentRequest = { version: '3.0.0', status: 'suspended', owner: 'team-b' }; const result = await repo.update(AGENT_ROW.agent_id, data); expect(result?.status).toBe('suspended'); expect(result?.owner).toBe('team-b'); }); }); // ── decommission ────────────────────────────────────────────────────────────── describe('decommission()', () => { it('should set status to decommissioned and return the agent', async () => { const decomRow = { ...AGENT_ROW, status: 'decommissioned' }; (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [decomRow], rowCount: 1 }); const result = await repo.decommission(AGENT_ROW.agent_id); const [sql, params] = (pool.query as jest.Mock).mock.calls[0] as [string, unknown[]]; expect(sql).toContain('decommissioned'); expect(params).toContain(AGENT_ROW.agent_id); expect(result?.status).toBe('decommissioned'); }); it('should return null when agent is not found', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [], rowCount: 0 }); const result = await repo.decommission('nonexistent'); expect(result).toBeNull(); }); }); // ── countActive ─────────────────────────────────────────────────────────────── describe('countActive()', () => { it('should return the count of non-decommissioned agents', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [{ count: '42' }], rowCount: 1 }); const count = await repo.countActive(); const [sql] = (pool.query as jest.Mock).mock.calls[0] as [string]; expect(sql).toContain('decommissioned'); expect(count).toBe(42); }); it('should return 0 when there are no active agents', async () => { (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [{ count: '0' }], rowCount: 1 }); const count = await repo.countActive(); expect(count).toBe(0); }); }); });