96 lines
3.4 KiB
TypeScript
96 lines
3.4 KiB
TypeScript
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();
|
|
}
|
|
});
|