Files
SnowLuma/packages/core/tests/msg-push-handler.test.ts
T
DrSmoothl dd02535a73 chore(core): flatten single-file dirs + drop dead writeFailed surface
Two small cleanups that came out of the round-2 architecture review
and are sub-five-minutes each — bundled because each one is too
small to justify its own commit.

#5 — flatten single-file directories
  packages/core/src/bridge/builders/element-builder.ts
    -> packages/core/src/bridge/element-builder.ts
  packages/core/src/bridge/handlers/msg-push-handler.ts
    -> packages/core/src/bridge/msg-push-handler.ts

  Both dirs had exactly one file in them — a smell that says
  "planned a sibling that never landed". Moving up one level
  removes the wrapper directory.

  Touch list:
    - bridge.ts:                  2 import paths
    - bridge/actions/forward.ts:  2 import paths
    - msg-push-handler.ts:        internal ../X -> ./X for events,
                                  proto/element, proto/message (the
                                  dynamic-import-typeof references too)
    - element-builder.ts:         internal ../X -> ./X for bridge,
                                  events, highway/*
    - 4 test files:               import paths (2 regular, 2 vi.mock)

#6 — drop IdentityService.hasWriteFailure
  identity-service.ts had a `private writeFailed = false` field set
  by the runWrite catch block and exposed via `get hasWriteFailure()`,
  but a full-repo grep showed zero callers of the getter. It was a
  public API stub for a feature that hadn't been wired (a webui
  health indicator, presumably).

  Removed:
    - the field
    - the getter
    - the assignment in runWrite's catch block (the error log on
      the next line is the actual observable behavior; nothing else
      reads the flag)

  If we want a "did any write fail this session" surface later,
  it's trivial to add back with an explicit consumer in mind.

Behavior preserved end-to-end. No public API change outside the
moved files. 369 tests pass (unchanged). 3 stability runs.
2026-05-12 20:59:25 +08:00

96 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { parseMsgPush, MSG_PUSH_CMD } from '../src/bridge/msg-push-handler';
import { QQInfo, type GroupMemberInfo, type QQGroupInfo } from '../src/bridge/qq-info';
import { PushMsgSchema } from '../src/bridge/proto/message';
import { GroupChangeSchema, OperatorInfoSchema } from '../src/bridge/proto/notify';
import type { GroupMemberJoin } from '../src/bridge/events';
import type { PacketInfo } from '../src/protocol/types';
import { protoEncode } from '../src/protobuf/decode';
const SELF_UIN = '10001';
const GROUP_ID = 123456789;
function makeGroupMember(uin: number, uid: string): GroupMemberInfo {
return {
uin,
uid,
nickname: '',
card: '',
role: 'member',
level: 0,
title: '',
joinTime: 0,
lastSentTime: 0,
shutUpTime: 0,
};
}
function makeGroup(members: GroupMemberInfo[] = []): QQGroupInfo {
return {
groupId: GROUP_ID,
groupName: '',
remark: '',
memberCount: members.length,
memberMax: 500,
members: new Map(members.map((member) => [member.uin, member])),
};
}
function makeQqInfo(members: GroupMemberInfo[] = []): QQInfo {
const qqInfo = new QQInfo(SELF_UIN);
qqInfo.setGroups([makeGroup(members)]);
return qqInfo;
}
function makeGroupIncreasePacket(memberUid: string, operatorUid = '', fromUin = GROUP_ID): PacketInfo {
const operatorBytes = operatorUid
? protoEncode({ operatorField: { uid: operatorUid } } as any, OperatorInfoSchema)
: new Uint8Array(0);
const content = protoEncode({
groupUin: GROUP_ID,
memberUid,
operatorBytes,
} as any, GroupChangeSchema);
const body = protoEncode({
message: {
responseHead: { fromUin },
contentHead: { msgType: 33, timestamp: 1710000000 },
body: { msgContent: content },
},
status: 0,
} as any, PushMsgSchema);
return {
pid: 1,
uin: SELF_UIN,
serviceCmd: MSG_PUSH_CMD,
seqId: 1,
retCode: 0,
fromClient: false,
body,
};
}
describe('parseMsgPush group member increase', () => {
it('does not fall back to the group id when a joining uid is unresolved', () => {
const [event] = parseMsgPush(makeGroupIncreasePacket('u_new_member'), makeQqInfo()) as GroupMemberJoin[];
expect(event.kind).toBe('group_member_join');
expect(event.groupId).toBe(GROUP_ID);
expect(event.userUin).toBe(0);
expect(event.operatorUin).toBe(0);
expect(event.userUid).toBe('u_new_member');
});
it('resolves joining uid and operator uid from the member cache when available', () => {
const member = makeGroupMember(22222, 'u_member');
const operator = makeGroupMember(33333, 'u_operator');
const [event] = parseMsgPush(makeGroupIncreasePacket(member.uid, operator.uid), makeQqInfo([member, operator])) as GroupMemberJoin[];
expect(event.userUin).toBe(member.uin);
expect(event.operatorUin).toBe(operator.uin);
expect(event.userUid).toBe(member.uid);
expect(event.operatorUid).toBe(operator.uid);
});
});