/** * 落库用例(任务 A3.7):会话、消息与幂等键要在进程重启后仍然可查。 * * 用同一个 SQLite 文件起两次服务,模拟「平台重启」。 */ import assert from 'node:assert/strict'; import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { test } from 'node:test'; async function startWithDatabase(dbPath) { const { startServer } = await import('../server/dist/index.js'); return startServer({ host: '127.0.0.1', port: 0, version: 'test', databasePath: dbPath, engine: 'fake' }); } const postJson = (base, path, body) => fetch(`${base}${path}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }); test('会话、消息与幂等键在重启后可查', async () => { const dir = await mkdtemp(join(tmpdir(), 'aps-storage-')); const dbPath = join(dir, 'aps.sqlite'); try { let sessionId = ''; let firstRunId = ''; { const server = await startWithDatabase(dbPath); try { const created = await (await postJson(server.url, '/v1/sessions', { scope: 'project', projectId: 'p-default', title: '持久化用例' })).json(); sessionId = created.session.sessionId; const accepted = await ( await postJson(server.url, `/v1/sessions/${sessionId}/messages`, { text: '试排一版看看', idempotencyKey: 'idem-storage-0001' }) ).json(); firstRunId = accepted.runId; await new Promise((resolve) => setTimeout(resolve, 300)); } finally { await server.close(); } } // 重启:同一个库文件,新的服务实例 { const server = await startWithDatabase(dbPath); try { const sessions = await (await fetch(`${server.url}/v1/sessions`)).json(); assert.equal(sessions.sessions.length, 1, '重启后应还能列出会话'); assert.equal(sessions.sessions[0].sessionId, sessionId); assert.equal(sessions.sessions[0].title, '持久化用例'); const detail = await (await fetch(`${server.url}/v1/sessions/${sessionId}`)).json(); assert.ok(detail.messages.length >= 2, `重启后应还能读到消息,实际 ${detail.messages.length} 条`); assert.equal(detail.messages[0].role, 'user'); assert.equal(detail.messages.at(-1).role, 'assistant'); // 幂等键也持久化了:同一 key 重试仍返回同一个 run const retry = await ( await postJson(server.url, `/v1/sessions/${sessionId}/messages`, { text: '试排一版看看', idempotencyKey: 'idem-storage-0001' }) ).json(); assert.equal(retry.runId, firstRunId, '重启后重试应命中同一条受理记录'); } finally { await server.close(); } } } finally { await rm(dir, { recursive: true, force: true }); } }); test('默认数据库路径来自配置(~/.aps/aps.sqlite)', async () => { const { loadConfig } = await import('@aps/server/config'); const config = loadConfig({ env: {}, homeDir: 'C:/Users/example' }); assert.equal(config.database.path.replace(/\\/g, '/'), 'C:/Users/example/.aps/aps.sqlite'); });