import { test, expect } from '@playwright/test' import { API_KEY_HEADER, createTestProject, deleteTestProject, createTestAgent, deleteTestAgent } from './helpers' test.describe('Project Agent Assignments', () => { const projectCleanup: number[] = [] const agentCleanup: number[] = [] test.afterEach(async ({ request }) => { for (const id of projectCleanup) { await deleteTestProject(request, id).catch(() => {}) } for (const id of agentCleanup) { await deleteTestAgent(request, id).catch(() => {}) } projectCleanup.length = 0 agentCleanup.length = 0 }) // ── POST /api/projects/[id]/agents ────────────── test('POST assigns agent to project', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) const res = await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) expect(res.status()).toBe(201) const body = await res.json() expect(body.success).toBe(true) }) test('POST assigns agent with custom role', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) const res = await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName, role: 'lead' }, }) expect(res.status()).toBe(201) }) test('POST is idempotent for same agent', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) // First assign const res1 = await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) expect(res1.status()).toBe(201) // Second assign (should not fail due to INSERT OR IGNORE) const res2 = await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) expect(res2.status()).toBe(201) }) test('POST rejects empty agent_name', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const res = await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: '' }, }) expect(res.status()).toBe(400) }) test('POST returns 404 for missing project', async ({ request }) => { const res = await request.post('/api/projects/999999/agents', { headers: API_KEY_HEADER, data: { agent_name: 'ghost-agent' }, }) expect(res.status()).toBe(404) }) // ── GET /api/projects/[id]/agents ─────────────── test('GET lists assigned agents', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) // Assign agent await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName, role: 'reviewer' }, }) // List const res = await request.get(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) expect(res.status()).toBe(200) const body = await res.json() expect(body).toHaveProperty('assignments') expect(Array.isArray(body.assignments)).toBe(true) const found = body.assignments.find((a: any) => a.agent_name === agentName) expect(found).toBeDefined() expect(found.role).toBe('reviewer') expect(found.project_id).toBe(projectId) }) test('GET returns empty array for project with no agents', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const res = await request.get(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) expect(res.status()).toBe(200) const body = await res.json() expect(body.assignments).toEqual([]) }) test('GET returns 404 for missing project', async ({ request }) => { const res = await request.get('/api/projects/999999/agents', { headers: API_KEY_HEADER }) expect(res.status()).toBe(404) }) // ── DELETE /api/projects/[id]/agents ──────────── test('DELETE unassigns agent from project', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) // Assign await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) // Unassign const res = await request.delete( `/api/projects/${projectId}/agents?agent_name=${encodeURIComponent(agentName)}`, { headers: API_KEY_HEADER } ) expect(res.status()).toBe(200) const body = await res.json() expect(body.success).toBe(true) // Verify gone const listRes = await request.get(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) const listBody = await listRes.json() const found = listBody.assignments.find((a: any) => a.agent_name === agentName) expect(found).toBeUndefined() }) test('DELETE without agent_name returns 400', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const res = await request.delete(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) expect(res.status()).toBe(400) }) test('DELETE returns 404 for missing project', async ({ request }) => { const res = await request.delete('/api/projects/999999/agents?agent_name=x', { headers: API_KEY_HEADER }) expect(res.status()).toBe(404) }) // ── assigned_agents in project response ───────── test('assigned_agents appears in project list response after assignment', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) // Assign await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) // Check project list const listRes = await request.get('/api/projects', { headers: API_KEY_HEADER }) const listBody = await listRes.json() const project = listBody.projects.find((p: any) => p.id === projectId) expect(project).toBeDefined() expect(project.assigned_agents).toContain(agentName) }) test('assigned_agents appears in single project response', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) // Assign await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) // Check single project const getRes = await request.get(`/api/projects/${projectId}`, { headers: API_KEY_HEADER }) const getBody = await getRes.json() expect(getBody.project.assigned_agents).toContain(agentName) }) // ── Full lifecycle ─────────────────────────── test('full lifecycle: assign multiple → list → unassign one → verify', async ({ request }) => { const { id: projectId } = await createTestProject(request) projectCleanup.push(projectId) const { id: agent1Id, name: agent1Name } = await createTestAgent(request) agentCleanup.push(agent1Id) const { id: agent2Id, name: agent2Name } = await createTestAgent(request) agentCleanup.push(agent2Id) // Assign both await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agent1Name, role: 'developer' }, }) await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agent2Name, role: 'reviewer' }, }) // List — both present const listRes1 = await request.get(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) const list1 = await listRes1.json() expect(list1.assignments.length).toBe(2) // Unassign first await request.delete( `/api/projects/${projectId}/agents?agent_name=${encodeURIComponent(agent1Name)}`, { headers: API_KEY_HEADER } ) // List — only second remains const listRes2 = await request.get(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) const list2 = await listRes2.json() expect(list2.assignments.length).toBe(1) expect(list2.assignments[0].agent_name).toBe(agent2Name) expect(list2.assignments[0].role).toBe('reviewer') }) // ── Cascade deletion ─────────────────────────── test('deleting a project cascades to remove agent assignments', async ({ request }) => { const { id: projectId } = await createTestProject(request) const { id: agentId, name: agentName } = await createTestAgent(request) agentCleanup.push(agentId) // Assign await request.post(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER, data: { agent_name: agentName }, }) // Delete project permanently await request.delete(`/api/projects/${projectId}?mode=delete`, { headers: API_KEY_HEADER }) // Agent assignments should be gone (project is gone, can't query assignments) const res = await request.get(`/api/projects/${projectId}/agents`, { headers: API_KEY_HEADER }) expect(res.status()).toBe(404) }) })