208 lines
6.7 KiB
TypeScript
208 lines
6.7 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
import { execFile } from 'node:child_process'
|
|
import { promisify } from 'node:util'
|
|
import path from 'node:path'
|
|
import { API_KEY_HEADER, createTestAgent, deleteTestAgent, createTestTask, deleteTestTask } from './helpers'
|
|
|
|
const execFileAsync = promisify(execFile)
|
|
|
|
const CLI = path.resolve('scripts/mc-cli.cjs')
|
|
const BASE_URL = process.env.E2E_BASE_URL || 'http://127.0.0.1:3005'
|
|
const API_KEY = 'test-api-key-e2e-12345'
|
|
|
|
/** Run mc-cli command via execFile (no shell) and return parsed JSON output */
|
|
async function mc(...args: string[]): Promise<{ stdout: string; parsed: any; exitCode: number }> {
|
|
try {
|
|
const { stdout } = await execFileAsync('node', [CLI, ...args, '--json', '--url', BASE_URL, '--api-key', API_KEY], {
|
|
timeout: 15000,
|
|
env: { ...process.env, MC_URL: BASE_URL, MC_API_KEY: API_KEY },
|
|
})
|
|
let parsed: any
|
|
try { parsed = JSON.parse(stdout) } catch { parsed = { raw: stdout } }
|
|
return { stdout, parsed, exitCode: 0 }
|
|
} catch (err: any) {
|
|
const stdout = err.stdout || ''
|
|
let parsed: any
|
|
try { parsed = JSON.parse(stdout) } catch { parsed = { raw: stdout, stderr: err.stderr } }
|
|
return { stdout, parsed, exitCode: err.code ?? 1 }
|
|
}
|
|
}
|
|
|
|
test.describe('CLI Integration', () => {
|
|
// --- Help & Usage ---
|
|
|
|
test('--help shows usage and exits 0', async () => {
|
|
const { stdout, exitCode } = await mc('--help')
|
|
expect(exitCode).toBe(0)
|
|
expect(stdout).toContain('Mission Control CLI')
|
|
expect(stdout).toContain('agents')
|
|
expect(stdout).toContain('tasks')
|
|
})
|
|
|
|
test('unknown group exits 2 with error', async () => {
|
|
const { exitCode } = await mc('nonexistent', 'action')
|
|
expect(exitCode).toBe(2)
|
|
})
|
|
|
|
test('missing required flag exits 2 with error message', async () => {
|
|
const { exitCode, parsed } = await mc('agents', 'get')
|
|
expect(exitCode).toBe(2)
|
|
expect(parsed.error).toContain('--id')
|
|
})
|
|
|
|
// --- Status ---
|
|
|
|
test('status health returns healthy', async () => {
|
|
const { parsed, exitCode } = await mc('status', 'health')
|
|
expect(exitCode).toBe(0)
|
|
expect(parsed.data?.status || parsed.status).toBeDefined()
|
|
})
|
|
|
|
test('status overview returns system info', async () => {
|
|
const { parsed, exitCode } = await mc('status', 'overview')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
// --- Agents CRUD ---
|
|
|
|
test.describe('agents', () => {
|
|
const agentIds: number[] = []
|
|
|
|
test.afterEach(async ({ request }) => {
|
|
for (const id of agentIds.splice(0)) {
|
|
await deleteTestAgent(request, id).catch(() => {})
|
|
}
|
|
})
|
|
|
|
test('list returns array', async () => {
|
|
const { parsed, exitCode } = await mc('agents', 'list')
|
|
expect(exitCode).toBe(0)
|
|
const data = parsed.data || parsed
|
|
expect(data).toBeDefined()
|
|
})
|
|
|
|
test('get + heartbeat lifecycle', async ({ request }) => {
|
|
const agent = await createTestAgent(request)
|
|
agentIds.push(agent.id)
|
|
|
|
// Get via CLI
|
|
const { parsed: getResult, exitCode: getCode } = await mc('agents', 'get', '--id', String(agent.id))
|
|
expect(getCode).toBe(0)
|
|
const agentData = getResult.data?.agent || getResult.data || getResult
|
|
expect(agentData).toBeDefined()
|
|
|
|
// Heartbeat via CLI
|
|
const { exitCode: hbCode } = await mc('agents', 'heartbeat', '--id', String(agent.id))
|
|
expect(hbCode).toBe(0)
|
|
})
|
|
|
|
test('memory set and get work', async ({ request }) => {
|
|
const agent = await createTestAgent(request)
|
|
agentIds.push(agent.id)
|
|
|
|
// Set memory — may succeed or fail depending on workspace state
|
|
const { exitCode: setCode } = await mc('agents', 'memory', 'set', '--id', String(agent.id), '--content', 'CLI test memory')
|
|
expect([0, 2, 6]).toContain(setCode)
|
|
|
|
// Get memory
|
|
const { exitCode: getCode } = await mc('agents', 'memory', 'get', '--id', String(agent.id))
|
|
expect([0, 2, 6]).toContain(getCode)
|
|
})
|
|
|
|
test('attribution returns response', async ({ request }) => {
|
|
const agent = await createTestAgent(request)
|
|
agentIds.push(agent.id)
|
|
|
|
// Attribution may return 403 for test API key depending on auth scope — accept 0 or 4
|
|
const { exitCode } = await mc('agents', 'attribution', '--id', String(agent.id), '--hours', '1')
|
|
expect([0, 4]).toContain(exitCode)
|
|
})
|
|
})
|
|
|
|
// --- Tasks ---
|
|
|
|
test.describe('tasks', () => {
|
|
const taskIds: number[] = []
|
|
|
|
test.afterEach(async ({ request }) => {
|
|
for (const id of taskIds.splice(0)) {
|
|
await deleteTestTask(request, id).catch(() => {})
|
|
}
|
|
})
|
|
|
|
test('list returns data', async () => {
|
|
const { exitCode } = await mc('tasks', 'list')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
test('queue returns response', async () => {
|
|
const { exitCode } = await mc('tasks', 'queue', '--agent', 'e2e-test-agent')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
test('comments list/add lifecycle', async ({ request }) => {
|
|
const task = await createTestTask(request)
|
|
taskIds.push(task.id)
|
|
|
|
// Add comment via CLI
|
|
const { exitCode: addCode } = await mc('tasks', 'comments', 'add', '--id', String(task.id), '--content', 'CLI comment test')
|
|
expect(addCode).toBe(0)
|
|
|
|
// List comments via CLI
|
|
const { parsed, exitCode: listCode } = await mc('tasks', 'comments', 'list', '--id', String(task.id))
|
|
expect(listCode).toBe(0)
|
|
const comments = parsed.data?.comments || parsed.comments || []
|
|
expect(comments.length).toBeGreaterThanOrEqual(1)
|
|
})
|
|
})
|
|
|
|
// --- Sessions ---
|
|
|
|
test('sessions list returns response', async () => {
|
|
// Sessions endpoint may return various codes depending on gateway state
|
|
const { exitCode } = await mc('sessions', 'list')
|
|
expect([0, 2, 4, 6]).toContain(exitCode)
|
|
})
|
|
|
|
// --- Tokens ---
|
|
|
|
test('tokens stats returns data', async () => {
|
|
const { exitCode } = await mc('tokens', 'stats')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
test('tokens by-agent returns data', async () => {
|
|
const { exitCode } = await mc('tokens', 'by-agent', '--days', '7')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
// --- Skills ---
|
|
|
|
test('skills list returns data', async () => {
|
|
const { exitCode } = await mc('skills', 'list')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
// --- Cron ---
|
|
|
|
test('cron list returns response', async () => {
|
|
// Cron may return error in test mode — accept 0, 2, or 6
|
|
const { exitCode } = await mc('cron', 'list')
|
|
expect([0, 2, 6]).toContain(exitCode)
|
|
})
|
|
|
|
// --- Connect ---
|
|
|
|
test('connect list returns data', async () => {
|
|
const { exitCode } = await mc('connect', 'list')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
|
|
// --- Raw passthrough ---
|
|
|
|
test('raw GET /api/status works', async () => {
|
|
const { exitCode } = await mc('raw', '--method', 'GET', '--path', '/api/status?action=health')
|
|
expect(exitCode).toBe(0)
|
|
})
|
|
})
|