Commit
T12: end-to-end smoke test
commit
417a3a2
2 changed files with +82 and −1
Jump to a changed file
- server/smoke.test.ts +81 −0
- spec/TASKS.md +1 −1
added server/smoke.test.ts +81 −0
| @@ -0,0 +1,81 @@ | ||
| 1 | +import { mkdtemp, readFile, rm } from 'node:fs/promises' | |
| 2 | +import { tmpdir } from 'node:os' | |
| 3 | +import path from 'node:path' | |
| 4 | +import { afterAll, beforeAll, describe, expect, it } from 'vitest' | |
| 5 | +import { CATEGORY_IDS, type AnswerResponse, type GenerateResponse, type Session } from '../shared/types' | |
| 6 | +import { buildApp } from './app' | |
| 7 | +import { createLlmMock } from './providers/llmMock' | |
| 8 | +import { SessionStore } from './store/sessionStore' | |
| 9 | + | |
| 10 | +describe('end-to-end smoke test', () => { | |
| 11 | + let storeDir: string | |
| 12 | + let targetDir: string | |
| 13 | + | |
| 14 | + beforeAll(async () => { | |
| 15 | + storeDir = await mkdtemp(path.join(tmpdir(), 'voicetask-smoke-store-')) | |
| 16 | + targetDir = await mkdtemp(path.join(tmpdir(), 'voicetask-smoke-target-')) | |
| 17 | + }) | |
| 18 | + | |
| 19 | + afterAll(async () => { | |
| 20 | + await rm(storeDir, { recursive: true, force: true }) | |
| 21 | + await rm(targetDir, { recursive: true, force: true }) | |
| 22 | + }) | |
| 23 | + | |
| 24 | + it('interviews to completion with mocks and generates a valid spec pack', async () => { | |
| 25 | + const app = buildApp({ store: new SessionStore(storeDir), llm: createLlmMock() }) | |
| 26 | + | |
| 27 | + const session = await app | |
| 28 | + .inject({ method: 'POST', url: '/api/sessions', payload: { name: 'smoke project', targetDir } }) | |
| 29 | + .then((r) => r.json<Session>()) | |
| 30 | + | |
| 31 | + let done = false | |
| 32 | + for (let i = 0; i < CATEGORY_IDS.length && !done; i++) { | |
| 33 | + const res = await app.inject({ | |
| 34 | + method: 'POST', | |
| 35 | + url: `/api/sessions/${session.id}/answer`, | |
| 36 | + payload: { text: `answer number ${i + 1}` }, | |
| 37 | + }) | |
| 38 | + expect(res.statusCode).toBe(200) | |
| 39 | + done = res.json<AnswerResponse>().turn.done | |
| 40 | + } | |
| 41 | + expect(done).toBe(true) | |
| 42 | + | |
| 43 | + const generateRes = await app.inject({ | |
| 44 | + method: 'POST', | |
| 45 | + url: `/api/sessions/${session.id}/generate`, | |
| 46 | + payload: {}, | |
| 47 | + }) | |
| 48 | + expect(generateRes.statusCode).toBe(200) | |
| 49 | + const generated = generateRes.json<GenerateResponse>() | |
| 50 | + | |
| 51 | + const expectedFiles = [ | |
| 52 | + 'spec/SPEC.md', | |
| 53 | + 'spec/PLAN.md', | |
| 54 | + 'spec/TASKS.md', | |
| 55 | + 'spec/VERIFICATION.md', | |
| 56 | + 'spec/HANDOFF.md', | |
| 57 | + 'spec/sources.json', | |
| 58 | + ] | |
| 59 | + expect(generated.files.sort()).toEqual([...expectedFiles].sort()) | |
| 60 | + | |
| 61 | + const sources = JSON.parse(await readFile(path.join(targetDir, 'spec/sources.json'), 'utf8')) as Record< | |
| 62 | + string, | |
| 63 | + { text: string; ts: string } | |
| 64 | + > | |
| 65 | + | |
| 66 | + const markerFiles = ['SPEC.md', 'PLAN.md', 'TASKS.md', 'VERIFICATION.md', 'HANDOFF.md'] | |
| 67 | + for (const file of markerFiles) { | |
| 68 | + const content = await readFile(path.join(targetDir, 'spec', file), 'utf8') | |
| 69 | + const markers = [...content.matchAll(/\[S(\d+)\]/g)] | |
| 70 | + for (const marker of markers) { | |
| 71 | + const segmentId = `S${marker[1]}` | |
| 72 | + expect(sources).toHaveProperty(segmentId) | |
| 73 | + } | |
| 74 | + } | |
| 75 | + | |
| 76 | + const handoff = await readFile(path.join(targetDir, 'spec/HANDOFF.md'), 'utf8') | |
| 77 | + expect(handoff).toContain('claude') | |
| 78 | + | |
| 79 | + await app.close() | |
| 80 | + }) | |
| 81 | +}) |
modified spec/TASKS.md +1 −1
| @@ -57,7 +57,7 @@Work strictly in order unless a task's Depends line allows otherwise. One task a | ||
| 57 | 57 | - Depends: T5, T7 |
| 58 | 58 | - Verify: `npm test` (parser tests incl. empty file; route test: import then next turn asks a blocker question). |
| 59 | 59 | |
| 60 | -- [ ] T12 End-to-end smoke test | |
| 60 | +- [x] T12 End-to-end smoke test | |
| 61 | 61 | - Single vitest test: boot the server with mocks, create session (temp target dir), answer until done via the answer route, generate, assert all six files exist, all remaining `[S<n>]` markers resolve against sources.json, and HANDOFF.md contains the string `claude`. |
| 62 | 62 | - Depends: T9, T10 |
| 63 | 63 | - Verify: `npm test` runs it green; then `npm run typecheck`, `npm test`, `npm run build` all exit 0 as the final full check. |