import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { type RunningServer, startServer } from '../src/start'; import { TEST_HOST_IDENTITY } from './helpers/hostIdentity'; let home: string; let server: RunningServer | undefined; beforeAll(async () => { home = mkdtempSync(join(tmpdir(), 'kimi-server-v2-file-history-')); server = await startServer({ hostIdentity: TEST_HOST_IDENTITY, host: '127.0.0.1', port: 0, homeDir: home, logLevel: 'silent', }); }); afterAll(async () => { try { await server?.close(); } catch { } server = undefined; rmSync(home, { recursive: true, force: true }); }); async function boot(): Promise { return server as RunningServer; } interface InjectResponse { statusCode: number; body: string; json: () => unknown; } interface AppLike { inject: (req: unknown) => Promise; } function appOf(r: RunningServer): AppLike { const app = r.app as unknown as AppLike; return { inject(req: unknown): Promise { const request = req as { headers?: Record }; return app.inject({ ...request, headers: { ...request.headers, authorization: `Bearer ${r.authTokenService.getToken()}`, }, }); }, }; } interface Envelope { code: number; msg: string; data: T | null; } async function createSession(r: RunningServer): Promise { const res = await appOf(r).inject({ method: 'POST', url: '/api/v1/sessions', payload: { metadata: { cwd: home } }, headers: { 'content-type': 'application/json' }, }); const envelope = res.json() as Envelope<{ id: string }>; if (envelope.code !== 0 || envelope.data === null) { throw new Error(`failed to create session: ${res.body}`); } return envelope.data.id; } describe('file history routes', () => { it('serves empty changes and null content for a live session without history', async () => { const r = await boot(); const sessionId = await createSession(r); const changes = await appOf(r).inject({ method: 'GET', url: `/api/v1/sessions/${sessionId}/file-history/changes?turn_id=1`, }); expect(changes.statusCode).toBe(200); expect((changes.json() as Envelope<{ changes: unknown[] }>).data).toEqual({ changes: [], recorded: false, }); const content = await appOf(r).inject({ method: 'GET', url: `/api/v1/sessions/${sessionId}/file-history/content?turn_id=1&path=a.txt`, }); expect(content.statusCode).toBe(200); expect((content.json() as Envelope<{ content: unknown }>).data).toEqual({ content: null }); }); it('rejects a session that is not live', async () => { const r = await boot(); const res = await appOf(r).inject({ method: 'GET', url: '/api/v1/sessions/does-not-exist/file-history/changes?turn_id=1', }); const envelope = res.json() as Envelope; expect(envelope.code).not.toBe(0); expect(envelope.data).toBeNull(); }); it('rejects a malformed turn_id', async () => { const r = await boot(); const sessionId = await createSession(r); const res = await appOf(r).inject({ method: 'GET', url: `/api/v1/sessions/${sessionId}/file-history/changes?turn_id=abc`, }); const envelope = res.json() as Envelope; expect(envelope.code).not.toBe(0); }); });