| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- /**
- * 验证 ApiChatCoordinator **多实例之间的事件隔离**。
- *
- * 为什么必须测:把「同一时间只能一个会话生成」改成「每会话并行」的前提,就是
- * 两个协调器实例的事件总线互不串台。一旦串台,A 会话的流式内容会写进 B、
- * 停止 A 会把 B 也停掉 —— 也就是重构前的那个 bug 的翻版。
- *
- * 不需要联网:`stopGenerate()` 纯本地;`generateAnswer()` 在空 baseUrl 下
- * 直接 emitError('missing_base_url') 短路,不会发请求。
- *
- * 怎么跑(在项目根目录):
- * npx esbuild harness/tools/_entry-coordinator.ts --bundle --format=esm \
- * --outfile=harness/tools/_coordinator.mjs --alias:@=./src --define:import.meta.env='{}'
- * node harness/tools/verify-coordinator-multi-instance.mjs
- */
- import { ApiChatCoordinator } from './_coordinator.mjs';
- let pass = 0;
- let fail = 0;
- const check = (name, cond, extra = '') => {
- if (cond) {
- pass++;
- console.log(' ok ' + name);
- } else {
- fail++;
- console.log(' FAIL ' + name + ' ' + extra);
- }
- };
- /** 给一个协调器实例挂上收集器,返回它收到的事件名数组 */
- const instrument = (coordinator) => {
- const received = [];
- for (const evt of ['message', 'loadingState', 'totalResponse', 'close', 'error', 'info']) {
- coordinator.addEventListener(evt, () => received.push(evt));
- }
- return received;
- };
- const makeA = () => new ApiChatCoordinator({ baseUrl: '', threadId: 'thread-A' });
- const makeB = () => new ApiChatCoordinator({ baseUrl: '', threadId: 'thread-B' });
- console.log('场景:两个会话各自的协调器实例\n');
- console.log('【1】停止 A 不得影响 B');
- {
- const a = makeA();
- const b = makeB();
- const gotA = instrument(a);
- const gotB = instrument(b);
- a.stopGenerate();
- check('A 收到了自己的 close', gotA.includes('close'), gotA.join(','));
- check('A 收到了自己的 loadingState', gotA.includes('loadingState'), gotA.join(','));
- check('**B 完全没收到任何事件**', gotB.length === 0, gotB.join(','));
- }
- console.log('\n【2】B 出错/发请求失败不得影响 A');
- {
- const a = makeA();
- const b = makeB();
- const gotA = instrument(a);
- const gotB = instrument(b);
- await b.generateAnswer('问题'); // 空 baseUrl → 立即 emitError,不发网络请求
- check('B 收到了 error', gotB.includes('error'), gotB.join(','));
- check('B 收到了 info(错误文案通道)', gotB.includes('info'), gotB.join(','));
- check('**A 完全没收到任何事件**', gotA.length === 0, gotA.join(','));
- }
- console.log('\n【3】两个实例各自独立停止(并行会话同时生成后各停各的)');
- {
- const a = makeA();
- const b = makeB();
- const gotA = instrument(a);
- const gotB = instrument(b);
- a.stopGenerate();
- const afterA = gotB.length;
- b.stopGenerate();
- check('A 停完时 B 仍为零事件', afterA === 0, String(afterA));
- check('B 随后收到自己的 close', gotB.includes('close'), gotB.join(','));
- check('A 与 B 的事件计数各自独立', gotA.filter((e) => e === 'close').length === 1 && gotB.filter((e) => e === 'close').length === 1);
- }
- console.log('\n【4】实例状态互不污染');
- {
- const a = makeA();
- const b = makeB();
- a.stopGenerate();
- check('停 A 后,B 的 threadId 不受影响', b.threadId === 'thread-B', b.threadId);
- check('两个实例不是同一个对象', a !== b);
- }
- console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
- process.exit(fail ? 1 : 0);
|