import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { NextRequest } from 'next/server' const requireRole = vi.fn() const runOpenClaw = vi.fn() const removeAgentFromConfig = vi.fn() const prepare = vi.fn() vi.mock('@/lib/auth', () => ({ requireRole, })) vi.mock('@/lib/command', () => ({ runOpenClaw, })) vi.mock('@/lib/agent-sync', () => ({ writeAgentToConfig: vi.fn(), enrichAgentConfigFromWorkspace: vi.fn((value) => value), removeAgentFromConfig, })) vi.mock('@/lib/db', () => ({ getDatabase: vi.fn(() => ({ prepare })), db_helpers: { logActivity: vi.fn(), }, logAuditEvent: vi.fn(), })) vi.mock('@/lib/event-bus', () => ({ eventBus: { broadcast: vi.fn(), }, })) vi.mock('@/lib/logger', () => ({ logger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), }, })) describe('DELETE /api/agents/[id]', () => { beforeEach(() => { vi.resetModules() requireRole.mockReturnValue({ user: { id: 1, username: 'admin', role: 'admin', workspace_id: 1 } }) runOpenClaw.mockReset() removeAgentFromConfig.mockReset() prepare.mockReset() }) afterEach(() => { vi.clearAllMocks() }) it('removes the agent from OpenClaw config even when workspace deletion is disabled', async () => { const agent = { id: 7, name: 'neo', role: 'tester', config: JSON.stringify({ openclawId: 'neo' }) } const selectStmt = { get: vi.fn(() => agent) } const deleteStmt = { run: vi.fn() } prepare.mockImplementation((sql: string) => { if (sql.startsWith('SELECT * FROM agents')) return selectStmt if (sql.startsWith('DELETE FROM agents')) return deleteStmt throw new Error(`Unexpected SQL: ${sql}`) }) const { DELETE } = await import('@/app/api/agents/[id]/route') const request = new NextRequest('http://localhost/api/agents/7', { method: 'DELETE', body: JSON.stringify({ remove_workspace: false }), headers: { 'content-type': 'application/json' }, }) const response = await DELETE(request, { params: Promise.resolve({ id: '7' }) }) const body = await response.json() expect(response.status).toBe(200) expect(runOpenClaw).not.toHaveBeenCalled() expect(removeAgentFromConfig).toHaveBeenCalledWith({ id: 'neo', name: 'neo' }) expect(deleteStmt.run).toHaveBeenCalledWith(7, 1) expect(body.success).toBe(true) }) it('removes workspace via OpenClaw and then removes the config entry', async () => { const agent = { id: 8, name: 'adam', role: 'tester', config: JSON.stringify({ openclawId: 'adam' }) } const selectStmt = { get: vi.fn(() => agent) } const deleteStmt = { run: vi.fn() } prepare.mockImplementation((sql: string) => { if (sql.startsWith('SELECT * FROM agents')) return selectStmt if (sql.startsWith('DELETE FROM agents')) return deleteStmt throw new Error(`Unexpected SQL: ${sql}`) }) const { DELETE } = await import('@/app/api/agents/[id]/route') const request = new NextRequest('http://localhost/api/agents/8', { method: 'DELETE', body: JSON.stringify({ remove_workspace: true }), headers: { 'content-type': 'application/json' }, }) const response = await DELETE(request, { params: Promise.resolve({ id: '8' }) }) expect(response.status).toBe(200) expect(runOpenClaw).toHaveBeenCalledWith(['agents', 'delete', 'adam', '--force'], { timeoutMs: 30000 }) expect(removeAgentFromConfig).toHaveBeenCalledWith({ id: 'adam', name: 'adam' }) expect(deleteStmt.run).toHaveBeenCalledWith(8, 1) }) it('still deletes the Mission Control agent when config cleanup fails', async () => { const agent = { id: 9, name: 'trinity', role: 'tester', config: JSON.stringify({ openclawId: 'trinity' }) } const selectStmt = { get: vi.fn(() => agent) } const deleteStmt = { run: vi.fn() } prepare.mockImplementation((sql: string) => { if (sql.startsWith('SELECT * FROM agents')) return selectStmt if (sql.startsWith('DELETE FROM agents')) return deleteStmt throw new Error(`Unexpected SQL: ${sql}`) }) removeAgentFromConfig.mockRejectedValue(new Error('OPENCLAW_CONFIG_PATH not configured')) const { DELETE } = await import('@/app/api/agents/[id]/route') const request = new NextRequest('http://localhost/api/agents/9', { method: 'DELETE', headers: { 'content-type': 'application/json' }, }) const response = await DELETE(request, { params: Promise.resolve({ id: '9' }) }) const body = await response.json() expect(response.status).toBe(200) expect(deleteStmt.run).toHaveBeenCalledWith(9, 1) expect(body.success).toBe(true) expect(body.warning).toContain('OpenClaw config cleanup skipped') }) })