guanghulab/tests/contract/ftchat.test.js

196 lines
7.0 KiB
JavaScript
Raw Normal View History

/**
* FTCHAT 集成测试
* - 登录全流程: send-code verify-code slot 占用
* - 11 个邮箱被拒绝 (满槽位)
* - 同邮箱重复登录复用槽位
* - 时间锚点格式
* - Notion 提示词兜底
* - memory-agent 兜底压缩
*
* 不依赖外部网络DashScope/Notion/SMTP, 全部本地校验
*/
'use strict';
process.env.FTCHAT_AUTH_DEV_MODE = 'true';
process.env.FTCHAT_DATA_DIR = '/tmp/ftchat-itest-' + Date.now();
const assert = require('assert');
const path = require('path');
const fs = require('fs');
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
passed++;
console.log(`${name}`);
} catch (e) {
failed++;
console.error(`${name}: ${e.message}`);
}
}
async function testAsync(name, fn) {
try {
await fn();
passed++;
console.log(`${name}`);
} catch (e) {
failed++;
console.error(`${name}: ${e.message}`);
}
}
(async () => {
console.log('🧪 FTCHAT 集成测试\n');
// ─── 1. email-auth ───
console.log('── 模块: email-auth (10 槽位 / QQ 邮箱) ──');
const auth = require('../../server/ftchat/services/email-auth');
auth._resetForTests();
test('非 QQ 邮箱被拒绝', () => {
const r = auth.isValidQqEmail('foo@gmail.com');
assert.strictEqual(r, false);
});
test('QQ 邮箱通过校验', () => {
assert.strictEqual(auth.isValidQqEmail('EMAIL_REDACTED@qq.com'), true);
assert.strictEqual(auth.isValidQqEmail('456@vip.qq.com'), true);
assert.strictEqual(auth.isValidQqEmail('789@foxmail.com'), true);
});
await testAsync('发送验证码 (DEV 模式) 并占据槽位', async () => {
const r = await auth.sendCode('EMAIL_REDACTED@qq.com');
assert.ok(r.success, '应成功');
assert.ok(/^\d{6}$/.test(r.dev_code), 'dev_code 应是 6 位数字');
assert.strictEqual(r.slots_remaining, 10);
});
// 模拟登录流: send → verify
async function loginFlow(email) {
const sent = await auth.sendCode(email);
if (!sent.success) return { ok: false, sent };
const verified = auth.verifyCode(email, sent.dev_code);
return { ok: verified.success, verified, dev_code: sent.dev_code };
}
await testAsync('完整登录流程 → 拿到 token', async () => {
auth._resetForTests();
const r = await loginFlow('EMAIL_REDACTED@qq.com');
assert.ok(r.ok, '登录应成功');
assert.ok(typeof r.verified.token === 'string' && r.verified.token.length >= 32);
assert.strictEqual(r.verified.slot_index, 1);
});
await testAsync('10 个不同邮箱依次登录占满槽位', async () => {
auth._resetForTests();
for (let i = 1; i <= 10; i++) {
const r = await loginFlow(`u${i}@qq.com`);
assert.ok(r.ok, `${i} 个邮箱应成功`);
assert.strictEqual(r.verified.slot_index, i);
}
const status = auth.getStatus();
assert.strictEqual(status.slots_taken, 10);
assert.strictEqual(status.slots_remaining, 0);
});
await testAsync('第 11 个新邮箱被拒绝 (sendCode 阶段就拦截)', async () => {
const r = await auth.sendCode('EMAIL_REDACTED@qq.com');
assert.strictEqual(r.success, false);
assert.ok(r.message.includes('已满'));
assert.strictEqual(r.slots_remaining, 0);
});
await testAsync('已占位邮箱满槽位下仍可重新登录 (复用槽位)', async () => {
const r = await loginFlow('EMAIL_REDACTED@qq.com');
assert.ok(r.ok, '已占位邮箱应可重新登录');
assert.strictEqual(r.verified.slot_index, 3, '应复用原槽位');
});
test('错误验证码 → 失败 + 计数', () => {
const r = auth.verifyCode('EMAIL_REDACTED@qq.com', '000000');
assert.strictEqual(r.success, false);
});
// ─── 2. time-anchor ───
console.log('\n── 模块: time-anchor ──');
const time = require('../../server/ftchat/services/time-anchor');
test('时间锚点包含完整字段', () => {
const a = time.getTimeAnchor();
assert.ok(/^\d{4}-\d{2}-\d{2}T/.test(a.iso));
assert.ok(/^\d{4}-\d{2}-\d{2} /.test(a.beijing));
assert.ok(/^周[一二三四五六日]$/.test(a.weekday));
assert.ok(a.anchor_text.includes('现实时间瞄点'));
assert.ok(a.anchor_text.includes('北京时间'));
});
// ─── 3. notion-prompt 兜底 ───
console.log('\n── 模块: notion-prompt (无 token 兜底) ──');
const np = require('../../server/ftchat/services/notion-prompt');
np._clearCache();
delete process.env.FT_NOTION_API_TOKEN;
delete process.env.FT_NOTION_PROMPT_PAGE_ID;
await testAsync('无配置时返回 fallback 提示词', async () => {
const r = await np.getSystemPrompt({});
assert.strictEqual(r.source, 'fallback');
assert.ok(r.text.length > 50);
assert.ok(r.text.includes('光湖'));
});
// ─── 4. memory-agent 兜底 ───
console.log('\n── 模块: memory-agent (无 LLM 兜底) ──');
const mem = require('../../server/ftchat/services/memory-agent');
test('fallbackImprint 正确格式化最近消息', () => {
const out = mem.fallbackImprint([
{ role: 'user', content: '你好啊' },
{ role: 'assistant', content: '你好' },
{ role: 'user', content: '今天怎么样' },
{ role: 'assistant', content: '不错呢' }
]);
assert.ok(out.includes('上次会话母语印记'));
assert.ok(out.includes('用户'));
assert.ok(out.includes('我'));
});
test('消息不足时不压缩', async () => {
const r = await mem.compressToImprint([{ role: 'user', content: 'hi' }]);
assert.strictEqual(r.compressed, false);
assert.strictEqual(r.imprint, '');
});
// ─── 5. session-store ───
console.log('\n── 模块: session-store ──');
const store = require('../../server/ftchat/services/session-store');
test('upsert 与 list 一致', () => {
const hash = 'abcdef0123456789';
store.upsertSession(hash, { session_id: 's1', title: '第一个对话', message_count: 5, has_memory_imprint: false });
store.upsertSession(hash, { session_id: 's2', title: '第二个', message_count: 10, has_memory_imprint: true });
const list = store.listSessions(hash);
assert.strictEqual(list.length, 2);
assert.strictEqual(list[0].session_id, 's2');
});
test('imprint set/get', () => {
const hash = 'abcdef0123456789';
store.setLatestImprint(hash, '【上次会话母语印记】测试段');
assert.strictEqual(store.getLatestImprint(hash), '【上次会话母语印记】测试段');
});
test('非法 hash 拒绝', () => {
let threw = false;
try { store.setLatestImprint('../etc/passwd', 'x'); } catch (_e) { threw = true; }
assert.strictEqual(threw, true);
});
// ─── 报告 ───
console.log(`\n📊 集成测试: ${passed} 通过, ${failed} 失败`);
// 清理
try {
fs.rmSync(process.env.FTCHAT_DATA_DIR, { recursive: true, force: true });
} catch (_e) { /* ignore */ }
if (failed > 0) {
console.error('❌ FTCHAT Integration Test FAILED');
process.exit(1);
} else {
console.log('✅ FTCHAT Integration Test PASSED');
}
})();