/** * Terminal Agent API Routes Tests (ADR-076) * Testing REST API endpoints for terminal session management * * Owner-only access to terminal sessions */ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; import request from 'supertest'; import express from 'express'; // Mock TerminalService before importing the route vi.mock('../../../services/TerminalService.js', () => { let sessionIdCounter = 0; let commandIdCounter = 0; return { createSession: vi.fn(async (userId, title, cwd) => { sessionIdCounter++; return { id: sessionIdCounter, user_id: userId, title: title || 'Terminal', cwd: cwd || '/root', status: 'active', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; }), getOrCreateSession: vi.fn(async (userId) => { sessionIdCounter++; return { id: sessionIdCounter, user_id: userId, title: 'Terminal', status: 'active', }; }), listSessions: vi.fn(async () => []), getSession: vi.fn(async (id) => ({ id, user_id: 1, title: 'Test', status: 'active', })), closeSession: vi.fn(async () => ({ closed: true })), executeCommand: vi.fn(async (sessionId, command, opts) => { commandIdCounter++; return { needsApproval: false, command: { id: commandIdCounter, session_id: sessionId, command, output: 'hello\n', exit_code: 0, risk_level: 'safe', source: opts?.source || 'user', }, }; }), approveCommand: vi.fn(async (commandId) => ({ approved: true })), rejectCommand: vi.fn(async (commandId) => ({ rejected: true })), getCommands: vi.fn(async () => []), }; }); const { default: terminalRoutes } = await import('../terminal.js'); // Create test app const app = express(); app.use(express.json()); // Variables for mock middleware let mockUserRole = 'owner'; let mockUserId = 1; // Mock authenticate middleware with dynamic role app.use((req, res, next) => { req.user = { id: mockUserId, role: mockUserRole, email: 'owner@test.com' }; next(); }); app.use('/api/v3/terminal', terminalRoutes); describe('Terminal Agent API Routes (ADR-076)', () => { beforeEach(async () => { // Reset to owner role by default mockUserRole = 'owner'; mockUserId = 1; }); afterEach(() => { vi.restoreAllMocks(); }); // ============================================================ // Access Control Tests // ============================================================ describe('Access Control: Owner-only', () => { test('owner can access sessions endpoint', async () => { mockUserRole = 'owner'; const response = await request(app) .get('/api/v3/terminal/sessions') .expect(200); expect(response.body.success).toBe(true); }); test('admin gets 403 Forbidden', async () => { mockUserRole = 'admin'; const response = await request(app) .get('/api/v3/terminal/sessions') .expect(403); expect(response.body.success).toBe(false); expect(response.body.error.code).toBe('FORBIDDEN'); }); test('regular user gets 403 Forbidden', async () => { mockUserRole = 'user'; const response = await request(app) .get('/api/v3/terminal/sessions') .expect(403); expect(response.body.success).toBe(false); expect(response.body.error.code).toBe('FORBIDDEN'); }); }); // ============================================================ // Session Management // ============================================================ describe('POST /api/v3/terminal/sessions - Create Session', () => { test('owner can create session', async () => { mockUserRole = 'owner'; const response = await request(app) .post('/api/v3/terminal/sessions') .send({ title: 'Test Session' }) .expect(201); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('id'); expect(response.body.data).toHaveProperty('title', 'Test Session'); }); test('non-owner gets 403', async () => { mockUserRole = 'user'; const response = await request(app) .post('/api/v3/terminal/sessions') .send({ title: 'Test Session' }) .expect(403); expect(response.body.success).toBe(false); }); }); describe('GET /api/v3/terminal/sessions - List Sessions', () => { test('owner can list sessions', async () => { mockUserRole = 'owner'; const response = await request(app) .get('/api/v3/terminal/sessions') .expect(200); expect(response.body.success).toBe(true); expect(Array.isArray(response.body.data)).toBe(true); }); }); // ============================================================ // Command Execution // ============================================================ describe('POST /api/v3/terminal/sessions/:sessionId/execute - Execute Command', () => { test('owner can execute command in session', async () => { mockUserRole = 'owner'; // First create a session const createRes = await request(app) .post('/api/v3/terminal/sessions') .send({ title: 'Test Session' }); const sessionId = createRes.body.data?.id; const response = await request(app) .post(`/api/v3/terminal/sessions/${sessionId}/execute`) .send({ command: 'echo hello' }) .expect(200); expect(response.body.success).toBe(true); }); test('non-owner cannot execute command', async () => { mockUserRole = 'admin'; const response = await request(app) .post('/api/v3/terminal/sessions/1/execute') .send({ command: 'echo hello' }) .expect(403); expect(response.body.success).toBe(false); }); }); describe('GET /api/v3/terminal/sessions/:sessionId/commands - Get Commands', () => { test('owner can get session commands', async () => { mockUserRole = 'owner'; // Create a session first const createRes = await request(app) .post('/api/v3/terminal/sessions') .send({ title: 'Test Session' }); const sessionId = createRes.body.data?.id || 1; const response = await request(app) .get(`/api/v3/terminal/sessions/${sessionId}/commands`) .expect(200); expect(response.body.success).toBe(true); expect(Array.isArray(response.body.data)).toBe(true); }); }); // ============================================================ // Quick Execute // ============================================================ describe('POST /api/v3/terminal/execute - Quick Execute', () => { test('owner can quick-execute a command', async () => { mockUserRole = 'owner'; const response = await request(app) .post('/api/v3/terminal/execute') .send({ command: 'echo test' }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('sessionId'); }); test('requires command parameter', async () => { mockUserRole = 'owner'; const response = await request(app) .post('/api/v3/terminal/execute') .send({}) .expect(400); expect(response.body.success).toBe(false); }); }); // ============================================================ // Audit Logging // ============================================================ describe('Audit Logging', () => { test('terminal commands are logged', async () => { mockUserRole = 'owner'; mockUserId = 99; // Create session (which should be logged) await request(app) .post('/api/v3/terminal/sessions') .send({ title: 'Audit Test Session' }); // We can't easily verify logs in test, but we verify the endpoint works // and assume logging happens (covered by integration tests) expect(true).toBe(true); }); }); });