profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM
fs.test.ts 3,223 bytes
1 import { mkdir, mkdtemp, 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 { FsBrowseResponse, FsMkdirResponse } from '../../shared/types'
7 import { buildApp } from '../app'
8 import { createLlmMock } from '../providers/llmMock'
9 import { SessionStore } from '../store/sessionStore'
10
11 describe('fs routes', () => {
12 let storeDir: string
13 let baseDir: string
14 let app: FastifyInstance
15
16 beforeEach(async () => {
17 storeDir = await mkdtemp(path.join(tmpdir(), 'voicetask-store-'))
18 baseDir = await mkdtemp(path.join(tmpdir(), 'voicetask-fs-'))
19 await mkdir(path.join(baseDir, 'zeta'))
20 await mkdir(path.join(baseDir, 'alpha'))
21 await mkdir(path.join(baseDir, '.hidden'))
22 app = buildApp({ store: new SessionStore(storeDir), llm: createLlmMock() })
23 })
24
25 afterEach(async () => {
26 await app.close()
27 await rm(storeDir, { recursive: true, force: true })
28 await rm(baseDir, { recursive: true, force: true })
29 })
30
31 it('lists subdirectories sorted, hides dotfiles, and reports the parent', async () => {
32 const res = await app.inject({ method: 'GET', url: `/api/fs/browse?path=${encodeURIComponent(baseDir)}` })
33 expect(res.statusCode).toBe(200)
34 const body = res.json<FsBrowseResponse>()
35 expect(body.path).toBe(baseDir)
36 expect(body.parent).toBe(path.dirname(baseDir))
37 expect(body.directories.map((d) => d.name)).toEqual(['alpha', 'zeta'])
38 expect(body.directories[0].path).toBe(path.join(baseDir, 'alpha'))
39 })
40
41 it('defaults to the home directory when no path is given', async () => {
42 const res = await app.inject({ method: 'GET', url: '/api/fs/browse' })
43 expect(res.statusCode).toBe(200)
44 expect(res.json<FsBrowseResponse>().path.length).toBeGreaterThan(0)
45 })
46
47 it('returns a 4xx for a path that cannot be read', async () => {
48 const res = await app.inject({
49 method: 'GET',
50 url: `/api/fs/browse?path=${encodeURIComponent(path.join(baseDir, 'does-not-exist'))}`,
51 })
52 expect(res.statusCode).toBeGreaterThanOrEqual(400)
53 expect(res.statusCode).toBeLessThan(500)
54 })
55
56 it('creates a new directory', async () => {
57 const res = await app.inject({
58 method: 'POST',
59 url: '/api/fs/mkdir',
60 payload: { path: baseDir, name: 'my-project' },
61 })
62 expect(res.statusCode).toBe(200)
63 const body = res.json<FsMkdirResponse>()
64 expect(body.path).toBe(path.join(baseDir, 'my-project'))
65
66 const listing = await app
67 .inject({ method: 'GET', url: `/api/fs/browse?path=${encodeURIComponent(baseDir)}` })
68 .then((r) => r.json<FsBrowseResponse>())
69 expect(listing.directories.map((d) => d.name)).toContain('my-project')
70 })
71
72 it('returns a 4xx when creating a directory that already exists', async () => {
73 await app.inject({ method: 'POST', url: '/api/fs/mkdir', payload: { path: baseDir, name: 'alpha' } })
74 const res = await app.inject({ method: 'POST', url: '/api/fs/mkdir', payload: { path: baseDir, name: 'alpha' } })
75 expect(res.statusCode).toBeGreaterThanOrEqual(400)
76 expect(res.statusCode).toBeLessThan(500)
77 })
78 })
79