profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM
smoke.test.ts 2,816 bytes
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 })
82