profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM
generate.test.ts 3,478 bytes
1 import { mkdtemp, readdir, rm } from 'node:fs/promises'
2 import { tmpdir } from 'node:os'
3 import path from 'node:path'
4 import type { FastifyInstance } from 'fastify'
5 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6 import type { GenerateResponse, Session } from '../../shared/types'
7 import { buildApp } from '../app'
8 import { createLlmMock } from '../providers/llmMock'
9 import { SessionStore } from '../store/sessionStore'
10
11 describe('generate route', () => {
12 let storeDir: string
13 let targetDir: string
14 let app: FastifyInstance
15
16 beforeEach(async () => {
17 storeDir = await mkdtemp(path.join(tmpdir(), 'voicetask-store-'))
18 targetDir = await mkdtemp(path.join(tmpdir(), 'voicetask-target-'))
19 app = buildApp({ store: new SessionStore(storeDir), llm: createLlmMock() })
20 })
21
22 afterEach(async () => {
23 await app.close()
24 await rm(storeDir, { recursive: true, force: true })
25 await rm(targetDir, { recursive: true, force: true })
26 })
27
28 async function createSessionWithAnAnswer(): Promise<Session> {
29 const created = await app
30 .inject({ method: 'POST', url: '/api/sessions', payload: { name: 'p', targetDir } })
31 .then((r) => r.json<Session>())
32 await app.inject({
33 method: 'POST',
34 url: `/api/sessions/${created.id}/answer`,
35 payload: { text: 'we are building a todo app' },
36 })
37 return app.inject({ method: 'GET', url: `/api/sessions/${created.id}` }).then((r) => r.json<Session>())
38 }
39
40 it('generates the six spec pack files', async () => {
41 const session = await createSessionWithAnAnswer()
42 const res = await app.inject({ method: 'POST', url: `/api/sessions/${session.id}/generate`, payload: {} })
43 expect(res.statusCode).toBe(200)
44 const body = res.json<GenerateResponse>()
45 expect(body.files).toHaveLength(6)
46 expect(body.files).toContain('spec/SPEC.md')
47 })
48
49 it('returns 409 on a second generate without overwrite', async () => {
50 const session = await createSessionWithAnAnswer()
51 await app.inject({ method: 'POST', url: `/api/sessions/${session.id}/generate`, payload: {} })
52 const res = await app.inject({ method: 'POST', url: `/api/sessions/${session.id}/generate`, payload: {} })
53 expect(res.statusCode).toBe(409)
54 })
55
56 it('regenerates and creates a backup dir when overwrite is set', async () => {
57 const session = await createSessionWithAnAnswer()
58 await app.inject({ method: 'POST', url: `/api/sessions/${session.id}/generate`, payload: {} })
59 const res = await app.inject({
60 method: 'POST',
61 url: `/api/sessions/${session.id}/generate`,
62 payload: { overwrite: true },
63 })
64 expect(res.statusCode).toBe(200)
65 const entries = await readdir(path.join(targetDir, 'spec'))
66 expect(entries.some((e) => e.startsWith('backup-'))).toBe(true)
67 })
68
69 it('returns 404 for an unknown session', async () => {
70 const res = await app.inject({ method: 'POST', url: '/api/sessions/nonexistent/generate', payload: {} })
71 expect(res.statusCode).toBe(404)
72 })
73
74 it('returns 400 when the target directory does not exist', async () => {
75 const created = await app
76 .inject({
77 method: 'POST',
78 url: '/api/sessions',
79 payload: { name: 'p', targetDir: path.join(targetDir, 'does-not-exist') },
80 })
81 .then((r) => r.json<Session>())
82 const res = await app.inject({ method: 'POST', url: `/api/sessions/${created.id}/generate`, payload: {} })
83 expect(res.statusCode).toBe(400)
84 })
85 })
86