| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import { signal } from '@angular/core';
- import { TestBed } from '@angular/core/testing';
- import { Agent, Message } from './api/models';
- import { ChatService } from './chat.service';
- import { LiveService } from './live.service';
- import { LoopGuardService } from './loop-guard.service';
- /**
- * 回环守卫(运行时)。
- *
- * 纯判定逻辑在 loop-guard.spec.ts 已覆盖;这里测**运行时接线**:
- * ① 消息变化后是否重新判定
- * ② 人类发言是否解除暂停
- * ③ 换会话是否清空状态
- * ④ off 模式是否真的不检测
- */
- describe('LoopGuardService', () => {
- let svc!: LoopGuardService;
- const MY = '@me:fmode.cn';
- const AGENT = '@a:fmode.cn';
- /**
- * 当前会话标识。
- *
- * 🔴 必须是**真信号**:`paused` / `pauseDetail` 是 computed,
- * 只会在依赖的信号变化时重算。用 jasmine spy 返回可变值时
- * computed 不会失效,断言会读到旧缓存(2026-11-09 踩坑)。
- */
- const sessionSig = signal<string | null>('s1');
- const chatStub = {
- messages: jasmine.createSpy('messages').and.returnValue([] as Message[]),
- agentId: jasmine.createSpy('agentId').and.returnValue('a1'),
- sessionId: () => sessionSig(),
- provisionInfo: { userMxId: MY, agentMxId: AGENT } as { userMxId?: string; agentMxId?: string } | null,
- };
- /** 切换当前会话(守卫只认 `chat.sessionId()` 这一个来源) */
- function setSession(id: string): void {
- sessionSig.set(id);
- // 信号变化后让守卫重算一次(生产里由 effect 自动做)
- svc.reevaluate();
- }
- function msg(sender: 'me' | 'agent', text: string, i: number): Message {
- return {
- id: `m${i}`,
- conversationId: 'c',
- sender,
- kind: 'text',
- text,
- ts: new Date(Date.now() - (100 - i) * 1000).toISOString(),
- state: 'sent',
- };
- }
- beforeEach(() => {
- sessionSig.set('s1');
- chatStub.messages.calls.reset(); chatStub.agentId.calls.reset();
- chatStub.messages.and.returnValue([]);
- chatStub.agentId.and.returnValue('a1');
- chatStub.provisionInfo = { userMxId: MY, agentMxId: AGENT };
- TestBed.resetTestingModule();
- TestBed.configureTestingModule({
- providers: [
- LoopGuardService,
- LiveService,
- { provide: ChatService, useValue: chatStub },
- { provide: 'FmodeApi', useValue: {} },
- ],
- });
- // LiveService 需要 FmodeApi,这里用 override 简化
- TestBed.overrideProvider(LiveService as never, { useValue: { agents: () => [], tick: () => 0 } } as never);
- svc = TestBed.inject(LoopGuardService);
- });
- it('🔴 默认是 off 模式(2026-11-13 用户裁决:误报太多,彻底关闭)', () => {
- expect(svc.mode()).toBe('off');
- expect(svc.paused()).toBeFalse();
- });
- it('默认 off 时,即使连续 20 条相同内容也不暂停', () => {
- chatStub.messages.and.returnValue(
- Array.from({ length: 20 }, (_, i) => msg('agent', '收到', i)),
- );
- svc.reevaluate();
- expect(svc.paused()).toBeFalse();
- });
- it('off 模式 → 永远不暂停', () => {
- chatStub.messages.and.returnValue(
- Array.from({ length: 20 }, (_, i) => msg('agent', '收到', i)),
- );
- svc.setMode('off');
- expect(svc.paused()).toBeFalse();
- });
- it('🔴 连续重复消息 → 判定暂停', () => {
- chatStub.messages.and.returnValue(
- Array.from({ length: 8 }, (_, i) => msg('agent', '收到', i)),
- );
- svc.setMode('standard');
- expect(svc.paused()).toBeTrue();
- expect(svc.pauseDetail()).toContain('连续');
- });
- it('🔴 人类发言后 → resume 清除暂停', () => {
- chatStub.messages.and.returnValue(
- Array.from({ length: 8 }, (_, i) => msg('agent', '收到', i)),
- );
- svc.setMode('standard');
- expect(svc.paused()).toBeTrue();
- svc.resume();
- expect(svc.paused()).toBeFalse();
- expect(svc.pauseDetail()).toBe('');
- });
- it('内容各不相同的连续消息 → 不暂停(在干活)', () => {
- chatStub.messages.and.returnValue([
- msg('agent', '我先备份配置文件', 0),
- msg('agent', '已备份到 bak 目录', 1),
- msg('agent', '正在修改白名单', 2),
- msg('agent', 'diff 确认只改了一行', 3),
- msg('agent', '重启 gateway 完成', 4),
- msg('agent', '验证通过', 5),
- ]);
- svc.setMode('standard');
- expect(svc.paused()).toBeFalse();
- });
- it('没有打开会话 → 不暂停', () => {
- chatStub.agentId.and.returnValue(null);
- chatStub.messages.and.returnValue(
- Array.from({ length: 10 }, (_, i) => msg('agent', '收到', i)),
- );
- svc.setMode('standard');
- expect(svc.paused()).toBeFalse();
- });
- /*
- * 🔴 会话隔离(2026-11-09)。
- *
- * 原实现 `dismissedFor` 存的是 agentId,同一个容器的两个会话
- * 之间切换不会清暂停态 → 上个会话的判定串到下一个会话。
- */
- describe('🔴 按会话隔离', () => {
- /*
- * 🔴 显式打开检测(2026-11-13)。
- * 默认模式已改为 `off`(误报太多),而本组用例测的是
- * 「暂停态是否按会话隔离」——与默认值无关,必须自己开起来。
- */
- beforeEach(() => svc.setMode('standard'));
- /** s1:回环中 */
- const LOOPING = () => Array.from({ length: 8 }, (_, i) => msg('agent', '收到', i));
- /** s2:正常对话,不该暂停 */
- const NORMAL = () => [
- msg('agent', '我先看一下配置文件', 0),
- msg('agent', '改好了,重启验证', 1),
- msg('me', '好的', 2),
- ];
- it('同 agentId 切到另一个会话 → 前一会话的暂停态不显示', () => {
- chatStub.messages.and.returnValue(LOOPING());
- setSession('s1');
- expect(svc.paused()).toBeTrue();
- chatStub.messages.and.returnValue(NORMAL());
- setSession('s2');
- expect(svc.paused()).withContext('s2 不应继承 s1 的暂停态').toBeFalse();
- });
- it('两个会话各自独立:切回 s1 仍是暂停', () => {
- chatStub.messages.and.returnValue(LOOPING());
- setSession('s1');
- chatStub.messages.and.returnValue(NORMAL());
- setSession('s2');
- expect(svc.paused()).toBeFalse();
- chatStub.messages.and.returnValue(LOOPING());
- setSession('s1');
- expect(svc.paused()).withContext('切回来应恢复暂停').toBeTrue();
- });
- it('resume() 只解除当前会话,不影响别的会话', () => {
- chatStub.messages.and.returnValue(LOOPING());
- setSession('s1');
- expect(svc.paused()).toBeTrue();
- setSession('s2'); svc.resume();
- expect(svc.paused()).withContext('s2 本来就没暂停').toBeFalse();
- setSession('s1');
- expect(svc.paused()).withContext('s1 不该被 s2 的 resume 解除').toBeTrue();
- });
- it('🔴 provisionInfo 为空时仍能正确判定(不依赖 mxId)', () => {
- chatStub.provisionInfo = null;
- chatStub.messages.and.returnValue(
- Array.from({ length: 8 }, (_, i) => msg('agent', '收到', i)),
- );
- setSession('s1');
- svc.setMode('standard');
- expect(svc.paused()).toBeTrue();
- });
- it('🔴 我方在多个会话各发一句「你好」→ 不暂停', () => {
- chatStub.messages.and.returnValue([
- msg('me', '你好', 0),
- msg('me', '你好', 1),
- msg('me', '你好', 2),
- ]);
- setSession('s1');
- svc.setMode('standard');
- expect(svc.paused()).toBeFalse();
- });
- });
- });
|