import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import Fastify from 'fastify'; import { registerChatRoutes, resolvePromptFollowupMode, resolveStaticContentType } from './chat.js'; const repoRoot = path.resolve(process.cwd(), '../../..'); async function removeSessionUploads(sessionId: string) { await fs.rm(path.join(repoRoot, 'public', '.codex_chat', sessionId), { recursive: true, force: true, }); } test('resolveStaticContentType returns html content type for chat resource html files', () => { assert.equal(resolveStaticContentType('/tmp/sample.html'), 'text/html; charset=utf-8'); assert.equal(resolveStaticContentType('/tmp/sample.htm'), 'text/html; charset=utf-8'); }); test('resolveStaticContentType keeps plain text content type for code resources', () => { assert.equal(resolveStaticContentType('/tmp/sample.ts'), 'text/plain; charset=utf-8'); assert.equal(resolveStaticContentType('/tmp/sample.diff'), 'text/plain; charset=utf-8'); }); test('resolvePromptFollowupMode defaults to queue and preserves direct mode', () => { assert.equal(resolvePromptFollowupMode(undefined), 'queue'); assert.equal(resolvePromptFollowupMode(null), 'queue'); assert.equal(resolvePromptFollowupMode('queue'), 'queue'); assert.equal(resolvePromptFollowupMode('direct'), 'direct'); }); test('chat attachments accept binary octet-stream uploads without base64 expansion', async () => { const app = Fastify(); await registerChatRoutes(app); const sessionId = `binary-upload-${Date.now()}`; const payload = Buffer.alloc(829_627, 1); try { const response = await app.inject({ method: 'POST', url: '/api/chat/attachments', headers: { 'content-type': 'application/octet-stream', 'x-chat-attachment-session-id': sessionId, 'x-chat-attachment-file-name': encodeURIComponent('image.png'), 'x-chat-attachment-mime-type': encodeURIComponent('image/png'), }, payload, }); assert.equal(response.statusCode, 200); const body = response.json() as { ok: boolean; item: { path: string; size: number; mimeType: string } }; assert.equal(body.ok, true); assert.equal(body.item.size, payload.byteLength); assert.equal(body.item.mimeType, 'image/png'); assert.match(body.item.path, new RegExp(`^public/\\.codex_chat/${sessionId}/resource/uploads/.+image\\.png$`)); } finally { await removeSessionUploads(sessionId); await app.close(); } }); test('chat attachments keep legacy JSON base64 uploads working', async () => { const app = Fastify(); await registerChatRoutes(app); const sessionId = `json-upload-${Date.now()}`; try { const response = await app.inject({ method: 'POST', url: '/api/chat/attachments', headers: { 'content-type': 'application/json', }, payload: JSON.stringify({ sessionId, fileName: 'note.txt', mimeType: 'text/plain', contentBase64: Buffer.from('hello', 'utf8').toString('base64'), }), }); assert.equal(response.statusCode, 200); const body = response.json() as { ok: boolean; item: { size: number; mimeType: string; name: string } }; assert.equal(body.ok, true); assert.equal(body.item.size, 5); assert.equal(body.item.mimeType, 'text/plain'); assert.equal(body.item.name, 'note.txt'); } finally { await removeSessionUploads(sessionId); await app.close(); } });