SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
This commit is contained in:
commit
fa9c73363d
1367 files changed
+355735
No files matched your search
@@ -0,0 +1,300 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { Bridge } from '@snowluma/core/bridge';
|
||||
import { IdentityService } from '@snowluma/protocol/identity-service';
|
||||
import type { GroupInviteEvent } from '@snowluma/protocol/events';
|
||||
import { MSG_PUSH_CMD } from '@snowluma/protocol/msg-push';
|
||||
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
|
||||
import type { PushMsg } from '@snowluma/proto-defs/message';
|
||||
import type { GroupInvitation, GroupInvite, GroupJoin } from '@snowluma/proto-defs/notify';
|
||||
import type {
|
||||
OidbBase,
|
||||
OidbSvcTrpcTcp0x10C0Response,
|
||||
OidbSvcTrpcTcp0x10C0ResponseRequest,
|
||||
} from '@snowluma/proto-defs/oidb';
|
||||
import type { OidbGroupRequestAction } from '@snowluma/proto-defs/oidb-actions/base';
|
||||
import { convertEvent } from '../src/event-converter';
|
||||
import { getGroupSystemMessages } from '../src/modules/contact-actions';
|
||||
import { handleGroupAddRequest } from '../src/modules/request-actions';
|
||||
|
||||
const bridges: Bridge[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(bridges.splice(0).map((bridge) => bridge.dispose()));
|
||||
});
|
||||
|
||||
function setup(filtered = false, overrides: OidbSvcTrpcTcp0x10C0ResponseRequest = {}) {
|
||||
const bridge = new Bridge(IdentityService.memory('10001'));
|
||||
bridges.push(bridge);
|
||||
bridge.identity.setSelfProfile({
|
||||
uin: 10001,
|
||||
uid: 'u_self',
|
||||
nickname: 'bot',
|
||||
remark: '',
|
||||
qid: '',
|
||||
sex: 'unknown',
|
||||
age: 0,
|
||||
sign: '',
|
||||
avatar: '',
|
||||
level: 0,
|
||||
qidianMasterFlag: 0,
|
||||
qidianCrewFlag: 0,
|
||||
qidianCrewFlag2: 0,
|
||||
});
|
||||
bridge.identity.rememberRequestIdentity({ groupId: 999, uid: 'u_self', uin: 10001 });
|
||||
bridge.identity.rememberRequestIdentity({ groupId: 999, uid: 'u_inviter', uin: 456 });
|
||||
const record = {
|
||||
sequence: 42n,
|
||||
eventType: 2,
|
||||
state: 1,
|
||||
group: { groupUin: 999, groupName: 'group' },
|
||||
target: { uid: 'u_self', name: 'bot' },
|
||||
invitor: { uid: 'u_inviter', name: 'inviter' },
|
||||
operateTransInfo: Uint8Array.from([8, 42, 16, 231, 7, 24, 2]),
|
||||
...overrides,
|
||||
};
|
||||
const send = vi.spyOn(bridge, 'sendRawPacket').mockImplementation(async (cmd) => {
|
||||
let responseData = Buffer.alloc(0);
|
||||
|
||||
if (cmd.startsWith('OidbSvcTrpcTcp.0x10c0_')) {
|
||||
responseData = Buffer.from(
|
||||
protobuf_encode<OidbBase<OidbSvcTrpcTcp0x10C0Response>>({
|
||||
body: { requests: cmd.endsWith(filtered ? '_2' : '_1') ? [record] : [] },
|
||||
}),
|
||||
);
|
||||
} else if (!cmd.startsWith('OidbSvcTrpcTcp.0x10c8_')) {
|
||||
throw new Error(`unexpected request: ${cmd}`);
|
||||
}
|
||||
|
||||
return { success: true, gotResponse: true, errorCode: 0, errorMessage: '', responseData };
|
||||
});
|
||||
const events: GroupInviteEvent[] = [];
|
||||
bridge.events.on('group_invite', (event) => events.push(event));
|
||||
const notice = (
|
||||
msgType = 87,
|
||||
msgContent = protobuf_encode<GroupInvite>({ groupUin: 999, invitorUid: 'u_inviter' }),
|
||||
) =>
|
||||
bridge.onPacket({
|
||||
pid: 1,
|
||||
uin: '10001',
|
||||
serviceCmd: MSG_PUSH_CMD,
|
||||
seqId: 1,
|
||||
retCode: 0,
|
||||
fromClient: false,
|
||||
body: Buffer.from(
|
||||
protobuf_encode<PushMsg>({
|
||||
message: {
|
||||
contentHead: { msgType, timestamp: 1 },
|
||||
body: { msgContent },
|
||||
},
|
||||
status: 0,
|
||||
}),
|
||||
),
|
||||
});
|
||||
return { bridge, send, events, notice, operateTransInfo: record.operateTransInfo };
|
||||
}
|
||||
|
||||
describe('group join requests', () => {
|
||||
it.each([false, true])(
|
||||
'reports and handles a join from the inbox (filtered=%s)',
|
||||
async (filtered) => {
|
||||
const { bridge, send, events, notice, operateTransInfo } = setup(filtered, {
|
||||
eventType: 1,
|
||||
target: { uid: 'u_applicant', name: 'applicant' },
|
||||
invitor: undefined,
|
||||
comment: 'please let me in',
|
||||
operateTransInfo: Uint8Array.from([8, 42, 16, 231, 7, 24, 1]),
|
||||
});
|
||||
bridge.identity.rememberRequestIdentity({ groupId: 999, uid: 'u_applicant', uin: 123 });
|
||||
await notice(
|
||||
84,
|
||||
protobuf_encode<GroupJoin>({ groupUin: 999, targetUid: 'u_applicant' }),
|
||||
);
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
const flag = `slreq:1:42:999:1:${filtered ? 1 : 0}`;
|
||||
const event = await convertEvent(
|
||||
{
|
||||
selfId: 10001,
|
||||
imageUrlResolver: null,
|
||||
mediaUrlResolver: null,
|
||||
messageIdResolver: null,
|
||||
mediaSegmentSink: null,
|
||||
},
|
||||
events[0]!,
|
||||
);
|
||||
expect(event).toMatchObject({
|
||||
post_type: 'request',
|
||||
request_type: 'group',
|
||||
sub_type: 'add',
|
||||
group_id: 999,
|
||||
user_id: 123,
|
||||
comment: 'please let me in',
|
||||
flag,
|
||||
});
|
||||
expect(await getGroupSystemMessages(bridge, { onlyPending: true })).toEqual([
|
||||
expect.objectContaining({ requester_uin: 123, requester_nick: 'applicant', flag }),
|
||||
]);
|
||||
|
||||
await handleGroupAddRequest(bridge, events[0]!.flag, true, '');
|
||||
const approvals = send.mock.calls.filter(([cmd]) =>
|
||||
cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'),
|
||||
);
|
||||
expect(approvals).toHaveLength(1);
|
||||
const [cmd, bytes] = approvals[0]!;
|
||||
expect(cmd).toBe(`OidbSvcTrpcTcp.0x10c8_${filtered ? 2 : 1}`);
|
||||
expect(protobuf_decode<OidbBase<OidbGroupRequestAction>>(bytes).body).toMatchObject({
|
||||
accept: 1,
|
||||
body: { sequence: 42n, groupUin: 999, eventType: 1, operateTransInfo },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([7, 10, 5])('does not report another wire type (%i) as a join', async (eventType) => {
|
||||
const { bridge, events, notice } = setup(false, { eventType });
|
||||
await notice(84, protobuf_encode<GroupJoin>({ groupUin: 999, targetUid: 'u_self' }));
|
||||
expect(events).toEqual([]);
|
||||
expect(await getGroupSystemMessages(bridge)).toEqual([]);
|
||||
});
|
||||
|
||||
it.each(['42', 'slreq:1:42:999:1:0'])(
|
||||
'refuses an unknown inbox type for flag %s',
|
||||
async (flag) => {
|
||||
const { bridge, send } = setup(false, { eventType: 5 });
|
||||
await expect(handleGroupAddRequest(bridge, flag, true, '')).rejects.toThrow(
|
||||
'unsupported group request type',
|
||||
);
|
||||
expect(send.mock.calls.some(([cmd]) => cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'))).toBe(
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('non-friend group invitations', () => {
|
||||
it.each([
|
||||
{ approve: true, filtered: false },
|
||||
{ approve: false, filtered: true },
|
||||
])('handles an emitted flag through the real codecs: %j', async ({ approve, filtered }) => {
|
||||
const { bridge, send, events, notice, operateTransInfo } = setup(filtered);
|
||||
await notice();
|
||||
expect(events).toEqual([expect.objectContaining({ flag: 'invite:999:u_inviter' })]);
|
||||
await handleGroupAddRequest(bridge, events[0]!.flag, approve, '');
|
||||
const approval = send.mock.calls.find(([cmd]) => cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'))!;
|
||||
expect(approval[0]).toBe(`OidbSvcTrpcTcp.0x10c8_${filtered ? 2 : 1}`);
|
||||
const wire = protobuf_decode<OidbBase<OidbGroupRequestAction>>(approval[1]);
|
||||
expect(wire.body).toMatchObject({
|
||||
accept: approve ? 1 : 2,
|
||||
body: { sequence: 42n, groupUin: 999, eventType: 2, message: ' ', operateTransInfo },
|
||||
});
|
||||
expect(
|
||||
await bridge.apis.contacts.resolveGroupJoinRequest(
|
||||
999,
|
||||
'u_inviter',
|
||||
'invite',
|
||||
'u_self',
|
||||
),
|
||||
).toMatchObject({ notifyType: 1, eventType: 2 });
|
||||
});
|
||||
|
||||
it('lists the inviter and returns a flag that can approve the same invitation', async () => {
|
||||
const { bridge, send, operateTransInfo } = setup();
|
||||
const rows = await getGroupSystemMessages(bridge);
|
||||
expect(rows).toEqual([
|
||||
expect.objectContaining({
|
||||
group_id: 999,
|
||||
requester_uin: 456,
|
||||
requester_nick: 'inviter',
|
||||
checked: false,
|
||||
flag: 'slreq:1:42:999:2:0',
|
||||
}),
|
||||
]);
|
||||
await handleGroupAddRequest(bridge, String(rows[0]!.flag), true, '');
|
||||
const approval = send.mock.calls.find(([cmd]) => cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'))!;
|
||||
const wire = protobuf_decode<OidbBase<OidbGroupRequestAction>>(approval[1]);
|
||||
expect(wire.body?.body).toMatchObject({ eventType: 2, operateTransInfo });
|
||||
});
|
||||
|
||||
it('corrects the type in an older invitation flag from the current inbox', async () => {
|
||||
const { bridge, send, operateTransInfo } = setup(true);
|
||||
await handleGroupAddRequest(bridge, 'slreq:1:42:999:10:0', true, '');
|
||||
const approvals = send.mock.calls.filter(([cmd]) =>
|
||||
cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'),
|
||||
);
|
||||
expect(approvals).toHaveLength(1);
|
||||
const [cmd, bytes] = approvals[0]!;
|
||||
expect(cmd).toBe('OidbSvcTrpcTcp.0x10c8_2');
|
||||
expect(protobuf_decode<OidbBase<OidbGroupRequestAction>>(bytes).body?.body).toMatchObject({
|
||||
sequence: 42n,
|
||||
groupUin: 999,
|
||||
eventType: 2,
|
||||
operateTransInfo,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([{ target: { uid: 'u_other' } }, { state: 2 }, { eventType: 10 }])(
|
||||
'requires a pending invitation to this account: %j',
|
||||
async (overrides) => {
|
||||
const { bridge, send, events, notice } = setup(false, overrides);
|
||||
await notice();
|
||||
await expect(handleGroupAddRequest(bridge, events[0]!.flag, true, '')).rejects.toThrow(
|
||||
'matching group request not found',
|
||||
);
|
||||
expect(send.mock.calls.some(([cmd]) => cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'))).toBe(
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('invitations awaiting administrator approval', () => {
|
||||
it('keeps the applicant, inviter and approval type through the real codecs', async () => {
|
||||
const { bridge, send, events, notice, operateTransInfo } = setup(true, {
|
||||
eventType: 22,
|
||||
target: { uid: 'u_applicant', name: 'applicant' },
|
||||
comment: 'invited to join',
|
||||
operateTransInfo: Uint8Array.from([8, 42, 16, 231, 7, 24, 22]),
|
||||
});
|
||||
bridge.identity.rememberRequestIdentity({ groupId: 999, uid: 'u_applicant', uin: 123 });
|
||||
await notice(
|
||||
525,
|
||||
protobuf_encode<GroupInvitation>({
|
||||
cmd: 87,
|
||||
info: {
|
||||
inner: { groupUin: 999, invitorUid: 'u_inviter', targetUid: 'u_applicant' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(events).toEqual([
|
||||
expect.objectContaining({
|
||||
subType: 'invite',
|
||||
fromUin: 456,
|
||||
invitedUin: 123,
|
||||
flag: 'slreq:1:42:999:22:1',
|
||||
}),
|
||||
]);
|
||||
expect(await getGroupSystemMessages(bridge)).toEqual([
|
||||
expect.objectContaining({
|
||||
requester_uin: 123,
|
||||
invitor_uin: 456,
|
||||
flag: events[0]!.flag,
|
||||
}),
|
||||
]);
|
||||
await handleGroupAddRequest(bridge, events[0]!.flag, false, 'declined');
|
||||
const approvals = send.mock.calls.filter(([cmd]) =>
|
||||
cmd.startsWith('OidbSvcTrpcTcp.0x10c8_'),
|
||||
);
|
||||
expect(approvals).toHaveLength(1);
|
||||
const [cmd, bytes] = approvals[0]!;
|
||||
expect(cmd).toBe('OidbSvcTrpcTcp.0x10c8_2');
|
||||
expect(protobuf_decode<OidbBase<OidbGroupRequestAction>>(bytes).body).toMatchObject({
|
||||
accept: 2,
|
||||
body: {
|
||||
sequence: 42n,
|
||||
groupUin: 999,
|
||||
eventType: 22,
|
||||
message: 'declined',
|
||||
operateTransInfo,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user