/** * Smoke tests: Project.llmProvider as Llm reference (Phase 4). * * Verifies the describe-project warning behavior against live mcpd: * 1. Project with `--llm ` → no warning. * 2. Project with `--llm ` → describe flags the orphan. * 3. Project with `--llm none` → explicit disable, no warning. */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import http from 'node:http'; import https from 'node:https'; import { execSync } from 'node:child_process'; const MCPD_URL = process.env.MCPD_URL ?? 'https://mcpctl.ad.itaz.eu'; const SUFFIX = Date.now().toString(36); const LLM_NAME = `smoke-proj-llm-${SUFFIX}`; const PROJ_OK = `smoke-proj-ok-${SUFFIX}`; const PROJ_ORPHAN = `smoke-proj-orphan-${SUFFIX}`; const PROJ_NONE = `smoke-proj-none-${SUFFIX}`; interface CliResult { code: number; stdout: string; stderr: string } function run(args: string): CliResult { try { const stdout = execSync(`mcpctl --direct ${args}`, { encoding: 'utf-8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'], }); return { code: 0, stdout: stdout.trim(), stderr: '' }; } catch (err) { const e = err as { status?: number; stdout?: Buffer | string; stderr?: Buffer | string }; return { code: e.status ?? 1, stdout: e.stdout ? (typeof e.stdout === 'string' ? e.stdout : e.stdout.toString('utf-8')) : '', stderr: e.stderr ? (typeof e.stderr === 'string' ? e.stderr : e.stderr.toString('utf-8')) : '', }; } } function healthz(url: string, timeoutMs = 5000): Promise { return new Promise((resolve) => { const parsed = new URL(`${url.replace(/\/$/, '')}/healthz`); const driver = parsed.protocol === 'https:' ? https : http; const req = driver.get( { hostname: parsed.hostname, port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80), path: parsed.pathname, timeout: timeoutMs, }, (res) => { resolve((res.statusCode ?? 500) < 500); res.resume(); }, ); req.on('error', () => resolve(false)); req.on('timeout', () => { req.destroy(); resolve(false); }); }); } let mcpdUp = false; describe('project-llm-ref smoke', () => { beforeAll(async () => { mcpdUp = await healthz(MCPD_URL); if (!mcpdUp) { // eslint-disable-next-line no-console console.warn(`\n ○ project-llm-ref smoke: skipped — ${MCPD_URL}/healthz unreachable.\n`); return; } // Fixture: an Llm we can point projects at. run(`delete llm ${LLM_NAME}`); const createLlm = run([ `create llm ${LLM_NAME}`, '--type openai', '--model gpt-4o-mini', '--tier fast', '--url http://127.0.0.1:1', ].join(' ')); if (createLlm.code !== 0) { // eslint-disable-next-line no-console console.warn(` ○ could not create fixture Llm: ${createLlm.stderr || createLlm.stdout}`); } }, 30_000); afterAll(() => { if (!mcpdUp) return; run(`delete project ${PROJ_OK} --force`); run(`delete project ${PROJ_ORPHAN} --force`); run(`delete project ${PROJ_NONE} --force`); run(`delete llm ${LLM_NAME}`); }); it('project with --llm pointing at a registered Llm describes without warning', () => { if (!mcpdUp) return; run(`delete project ${PROJ_OK} --force`); const created = run(`create project ${PROJ_OK} --llm ${LLM_NAME}`); expect(created.code, created.stderr || created.stdout).toBe(0); const described = run(`describe project ${PROJ_OK}`); expect(described.code).toBe(0); expect(described.stdout).toContain('LLM:'); expect(described.stdout).toContain(LLM_NAME); expect(described.stdout).not.toContain('warning:'); }); it('project with --llm naming an unregistered Llm shows the warning line', () => { if (!mcpdUp) return; run(`delete project ${PROJ_ORPHAN} --force`); const created = run(`create project ${PROJ_ORPHAN} --llm claude-ghost-${SUFFIX}`); expect(created.code, created.stderr || created.stdout).toBe(0); const described = run(`describe project ${PROJ_ORPHAN}`); expect(described.code).toBe(0); expect(described.stdout).toContain(`claude-ghost-${SUFFIX}`); expect(described.stdout).toContain('warning:'); expect(described.stdout).toContain('registry default'); }); it('project with --llm none treats it as an explicit disable (no warning)', () => { if (!mcpdUp) return; run(`delete project ${PROJ_NONE} --force`); const created = run(`create project ${PROJ_NONE} --llm none`); expect(created.code).toBe(0); const described = run(`describe project ${PROJ_NONE}`); expect(described.code).toBe(0); expect(described.stdout).toContain('LLM:'); expect(described.stdout).toContain('none'); expect(described.stdout).not.toContain('warning:'); }); });