import { test, expect } from '@playwright/test' import { API_KEY_HEADER } from './helpers' test.describe('Exec Approval Allowlist API', () => { // ── GET /api/exec-approvals?action=allowlist ── test('GET allowlist returns agents map and hash', async ({ request }) => { const res = await request.get('/api/exec-approvals?action=allowlist', { headers: API_KEY_HEADER, }) expect(res.status()).toBe(200) const body = await res.json() expect(body).toHaveProperty('agents') expect(typeof body.agents).toBe('object') expect(body).toHaveProperty('hash') expect(typeof body.hash).toBe('string') }) // ── PUT /api/exec-approvals (save allowlist) ── test('PUT save-allowlist persists and round-trips', async ({ request }) => { // Read current state to get hash const getRes = await request.get('/api/exec-approvals?action=allowlist', { headers: API_KEY_HEADER, }) expect(getRes.status()).toBe(200) const current = await getRes.json() // Save with a test agent pattern const testAgent = `e2e-test-agent-${Date.now()}` const putRes = await request.put('/api/exec-approvals', { headers: API_KEY_HEADER, data: { agents: { [testAgent]: [{ pattern: 'echo *' }] }, hash: current.hash, }, }) expect(putRes.status()).toBe(200) const putBody = await putRes.json() expect(putBody.ok).toBe(true) expect(putBody.hash).toBeDefined() // Read back and verify const verifyRes = await request.get('/api/exec-approvals?action=allowlist', { headers: API_KEY_HEADER, }) expect(verifyRes.status()).toBe(200) const verifyBody = await verifyRes.json() expect(verifyBody.agents[testAgent]).toBeDefined() expect(verifyBody.agents[testAgent]).toEqual([{ pattern: 'echo *' }]) // Clean up: remove the test agent by saving empty patterns await request.put('/api/exec-approvals', { headers: API_KEY_HEADER, data: { agents: { [testAgent]: [] }, hash: verifyBody.hash, }, }) }) test('PUT save-allowlist with stale hash returns 409', async ({ request }) => { const res = await request.put('/api/exec-approvals', { headers: API_KEY_HEADER, data: { agents: { 'conflict-agent': [{ pattern: 'test' }] }, hash: 'stale-hash-value-that-does-not-match', }, }) // 409 if the file exists and hash mismatches; 200 if file doesn't exist (no conflict possible) if (res.status() === 409) { const body = await res.json() expect(body.code).toBe('CONFLICT') } else { // File didn't exist, so no conflict — clean up expect(res.status()).toBe(200) const body = await res.json() // Re-read and clean up the test entry const getRes = await request.get('/api/exec-approvals?action=allowlist', { headers: API_KEY_HEADER, }) const current = await getRes.json() await request.put('/api/exec-approvals', { headers: API_KEY_HEADER, data: { agents: { 'conflict-agent': [] }, hash: current.hash, }, }) } }) test('PUT save-allowlist rejects missing agents field', async ({ request }) => { const res = await request.put('/api/exec-approvals', { headers: API_KEY_HEADER, data: {}, }) expect(res.status()).toBe(400) const body = await res.json() expect(body.error).toContain('agents') }) // ── GET /api/exec-approvals (pending approvals) ── test('GET pending approvals returns array or empty on gateway unavailable', async ({ request }) => { const res = await request.get('/api/exec-approvals', { headers: API_KEY_HEADER }) expect(res.status()).toBe(200) const body = await res.json() // Returns { approvals: [] } when gateway is unavailable expect(body).toHaveProperty('approvals') expect(Array.isArray(body.approvals)).toBe(true) }) // ── Auth ────────────────────────────────────── test('GET without auth is rejected', async ({ request }) => { const res = await request.get('/api/exec-approvals?action=allowlist') expect(res.status()).toBe(401) }) test('PUT without auth is rejected', async ({ request }) => { const res = await request.put('/api/exec-approvals', { data: { agents: {} }, }) expect(res.status()).toBe(401) }) test('POST without auth is rejected', async ({ request }) => { const res = await request.post('/api/exec-approvals', { data: { id: 'test', action: 'approve' }, }) expect(res.status()).toBe(401) }) })