leader-membership.spec.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { Agent } from './api/models';
  2. import { checkLeaderMembership, diagnoseLeader } from './leader-membership';
  3. /**
  4. * 组长是否在群里。
  5. *
  6. * 实测:组长 `lj` 报告「派活已发到群里」,但查群成员发现它压根不在,
  7. * 40 分钟组员没开工。它在自己的私聊房间里自言自语。
  8. */
  9. describe('leader-membership', () => {
  10. const LEADER = '@lj:fmode.cn';
  11. const MEMBER = '@lj-test:fmode.cn';
  12. const ME = '@me:fmode.cn';
  13. function agent(name: string, mxId?: string): Agent {
  14. return {
  15. id: name,
  16. name,
  17. status: 'idle',
  18. matrixReady: true,
  19. unread: 0,
  20. channels: mxId ? { matrix: true, matrixConfig: { userId: mxId } } : undefined,
  21. };
  22. }
  23. describe('checkLeaderMembership', () => {
  24. it('组长在成员列表里 → 正常', () => {
  25. const r = checkLeaderMembership(LEADER, [ME, MEMBER, LEADER], 'lj');
  26. expect(r.state).toBe('in-room');
  27. expect(r.warn).toBeFalse();
  28. });
  29. it('🔴 组长不在成员列表里 → 严重提示(实测踩过的坑)', () => {
  30. const r = checkLeaderMembership(LEADER, [ME, MEMBER], 'lj');
  31. expect(r.state).toBe('not-in-room');
  32. expect(r.warn).toBeTrue();
  33. expect(r.message).toContain('不在这个群里');
  34. expect(r.message).toContain('lj');
  35. });
  36. it('提示要说明「它说的已派活不会真发到群里」', () => {
  37. const r = checkLeaderMembership(LEADER, [ME], 'lj');
  38. expect(r.message).toContain('已派活');
  39. });
  40. it('成员列表为空 → unknown(启动瞬间不误报)', () => {
  41. const r = checkLeaderMembership(LEADER, [], 'lj');
  42. expect(r.state).toBe('unknown');
  43. expect(r.warn).toBeFalse();
  44. });
  45. it('没有组长 mxId → unknown(不误报)', () => {
  46. const r = checkLeaderMembership(undefined, [ME], 'lj');
  47. expect(r.state).toBe('unknown');
  48. expect(r.warn).toBeFalse();
  49. });
  50. });
  51. describe('diagnoseLeader', () => {
  52. it('🔴 不在群 → 直接判定,不看发言数', () => {
  53. const r = diagnoseLeader(agent('lj', LEADER), [ME, MEMBER], 0);
  54. expect(r.state).toBe('not-in-room');
  55. expect(r.warn).toBeTrue();
  56. });
  57. it('🔴 在群但一条没说过 → 提示可能没收到需求', () => {
  58. const r = diagnoseLeader(agent('lj', LEADER), [ME, LEADER], 0);
  59. expect(r.warn).toBeTrue();
  60. expect(r.message).toContain('还没说过话');
  61. });
  62. it('在群且说过话 → 正常', () => {
  63. const r = diagnoseLeader(agent('lj', LEADER), [ME, LEADER], 5);
  64. expect(r.warn).toBeFalse();
  65. });
  66. it('组长没有 Matrix 身份 → unknown', () => {
  67. const r = diagnoseLeader(agent('lj'), [ME], 0);
  68. expect(r.state).toBe('unknown');
  69. expect(r.warn).toBeFalse();
  70. });
  71. });
  72. describe('🔴 回归:解析 SDK 返回结构(曾误报「组长不在群」)', () => {
  73. // matrix-js-sdk 的 getJoinedRoomMembers 返回的是 { joined: {...} },
  74. // 直接 Object.keys 会拿到 ["joined"] —— 导致永远判「不在群」
  75. function parseLike(mx: unknown): string[] {
  76. const obj = mx as Record<string, unknown>;
  77. const inner = (obj && typeof obj['joined'] === 'object' ? obj['joined'] : obj) as
  78. | Record<string, unknown>
  79. | undefined;
  80. if (!inner || typeof inner !== 'object') return [];
  81. return Object.keys(inner);
  82. }
  83. it('SDK 的真实结构 {joined:{}} → 正确解析出成员', () => {
  84. const raw = {
  85. joined: {
  86. '@lj:fmode.cn': { display_name: 'lj' },
  87. '@me:fmode.cn': { display_name: 'me' },
  88. },
  89. };
  90. expect(parseLike(raw)).toEqual(['@lj:fmode.cn', '@me:fmode.cn']);
  91. });
  92. it('🔴 不能把 ["joined"] 当成成员列表', () => {
  93. const raw = { joined: { '@lj:fmode.cn': {} } };
  94. expect(parseLike(raw)).not.toContain('joined');
  95. });
  96. it('已经是直接 map 的形态也能处理(兼容)', () => {
  97. expect(parseLike({ '@lj:fmode.cn': {} })).toEqual(['@lj:fmode.cn']);
  98. });
  99. it('异常输入 → 空数组,不崩', () => {
  100. expect(parseLike(null)).toEqual([]);
  101. expect(parseLike({ joined: null })).toEqual([]);
  102. });
  103. it('解析正确 + 组长在里面 → 不误报', () => {
  104. const raw = { joined: { '@lj:fmode.cn': {}, '@me:fmode.cn': {} } };
  105. const ids = parseLike(raw);
  106. const r = checkLeaderMembership('@lj:fmode.cn', ids, 'lj');
  107. expect(r.state).toBe('in-room');
  108. expect(r.warn).toBeFalse();
  109. });
  110. });
  111. });