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
215 lines
8.4 KiB
TypeScript
215 lines
8.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
|
|
import type { pb, pb_repeated, uint_32, uint_64 } from '@snowluma/proton';
|
|
import type { OidbBase, OidbSvcTrpcTcp0x10C0Response } from '@snowluma/proto-defs/oidb';
|
|
import type { OidbGroupRequestList } from '@snowluma/proto-defs/oidb-actions/base';
|
|
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
|
|
|
import {
|
|
FetchGroupRequests,
|
|
FetchGroupRequestsByUid,
|
|
groupRequestNotifyType,
|
|
} from '../../../src/oidb-services/contacts/fetch-group-requests';
|
|
|
|
interface ObservedGroupRequestUserByUin {
|
|
uin?: pb<1, uint_32>;
|
|
name?: pb<2, string>;
|
|
}
|
|
|
|
interface ObservedGroupRequestGroup {
|
|
groupUin?: pb<1, uint_32>;
|
|
groupName?: pb<2, string>;
|
|
}
|
|
|
|
interface ObservedGroupRequestByUin {
|
|
sequence?: pb<1, uint_64>;
|
|
eventType?: pb<2, uint_32>;
|
|
state?: pb<3, uint_32>;
|
|
group?: pb<4, ObservedGroupRequestGroup>;
|
|
target?: pb<5, ObservedGroupRequestUserByUin>;
|
|
invitor?: pb<6, ObservedGroupRequestUserByUin>;
|
|
operatorUser?: pb<7, ObservedGroupRequestUserByUin>;
|
|
comment?: pb<10, string>;
|
|
}
|
|
|
|
interface ObservedGroupRequestResponseByUin {
|
|
requests?: pb_repeated<1, ObservedGroupRequestByUin>;
|
|
cursor?: pb<2, uint_64>;
|
|
}
|
|
|
|
function makeSender() {
|
|
const r: SendPacketResult = {
|
|
success: true,
|
|
gotResponse: true,
|
|
errorCode: 0,
|
|
errorMessage: '',
|
|
responseData: Buffer.alloc(0),
|
|
};
|
|
return { sendRawPacket: vi.fn(async () => r) };
|
|
}
|
|
|
|
describe('FetchGroupRequests namespace', () => {
|
|
it('interprets list discriminators in the wire-to-notification direction', () => {
|
|
expect(groupRequestNotifyType(1)).toBe(7);
|
|
expect(groupRequestNotifyType(2)).toBe(1);
|
|
expect(groupRequestNotifyType(7)).toBe(10);
|
|
expect(groupRequestNotifyType(10)).toBe(2);
|
|
expect(groupRequestNotifyType(22)).toBe(5);
|
|
expect(groupRequestNotifyType(100)).toBe(17);
|
|
expect(groupRequestNotifyType(5)).toBeUndefined();
|
|
expect(groupRequestNotifyType(99)).toBeUndefined();
|
|
});
|
|
|
|
it('declares 0x10C0', () => {
|
|
expect(FetchGroupRequests.command).toBe(0x10c0);
|
|
expect(FetchGroupRequests.uinForm).toBe(true);
|
|
expect((FetchGroupRequestsByUid as { uinForm?: boolean }).uinForm).toBeUndefined();
|
|
});
|
|
|
|
describe('resolveSubCommand', () => {
|
|
it('returns 1 when filtered=false (main inbox)', () => {
|
|
expect(FetchGroupRequests.resolveSubCommand({ filtered: false })).toBe(1);
|
|
});
|
|
it('returns 2 when filtered=true (low-priority inbox)', () => {
|
|
expect(FetchGroupRequests.resolveSubCommand({ filtered: true })).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('serialize', () => {
|
|
it('defaults to one native 50-item screen at cursor zero', () => {
|
|
expect(FetchGroupRequests.serialize({} as any, { filtered: false })).toEqual({
|
|
count: 50,
|
|
field2: 0n,
|
|
});
|
|
expect(FetchGroupRequestsByUid.serialize({} as any, { filtered: true })).toEqual({
|
|
count: 50,
|
|
field2: 0n,
|
|
});
|
|
});
|
|
|
|
it('preserves a caller-provided count and 64-bit cursor', () => {
|
|
const cursor = 1_785_406_628_779_279n;
|
|
expect(
|
|
FetchGroupRequests.serialize({} as any, {
|
|
filtered: false,
|
|
count: 80,
|
|
cursor,
|
|
}),
|
|
).toEqual({ count: 80, field2: cursor });
|
|
});
|
|
});
|
|
|
|
describe('invoke (e2e)', () => {
|
|
it('routes to 0x10c0_1 for the main inbox', async () => {
|
|
const sender = makeSender();
|
|
await FetchGroupRequests.invoke(sender, { filtered: false });
|
|
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x10c0_1');
|
|
});
|
|
|
|
it('routes to 0x10c0_2 for the filtered inbox', async () => {
|
|
const sender = makeSender();
|
|
await FetchGroupRequests.invoke(sender, { filtered: true });
|
|
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x10c0_2');
|
|
});
|
|
|
|
it('encodes the native envelope and requested screen', async () => {
|
|
const sender = makeSender();
|
|
await FetchGroupRequests.invoke(sender, {
|
|
filtered: false,
|
|
count: 80,
|
|
cursor: 1_785_406_628_779_279n,
|
|
});
|
|
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
|
const env = protobuf_decode<OidbBase<OidbGroupRequestList>>(bytes);
|
|
expect(env.reserved).toBe(1);
|
|
expect(env.body).toEqual({ count: 80, field2: 1_785_406_628_779_279n });
|
|
});
|
|
|
|
it('decodes numeric account identifiers from the native response', async () => {
|
|
const responseData = Buffer.from(
|
|
protobuf_encode<OidbBase<ObservedGroupRequestResponseByUin>>({
|
|
body: {
|
|
requests: [
|
|
{
|
|
sequence: 1_785_525_232_784_291n,
|
|
eventType: 1,
|
|
state: 1,
|
|
group: { groupUin: 1_095_186_374, groupName: 'group' },
|
|
target: { uin: 1_234_567_890, name: 'requester' },
|
|
invitor: { uin: 2_345_678_901, name: 'inviter' },
|
|
operatorUser: { uin: 3_456_789_012, name: 'operator' },
|
|
comment: 'please',
|
|
},
|
|
],
|
|
cursor: 1_785_406_628_779_279n,
|
|
},
|
|
}),
|
|
);
|
|
const result: SendPacketResult = {
|
|
success: true,
|
|
gotResponse: true,
|
|
errorCode: 0,
|
|
errorMessage: '',
|
|
responseData,
|
|
};
|
|
const sender = { sendRawPacket: vi.fn(async () => result) };
|
|
|
|
const response = await FetchGroupRequests.invoke(sender, { filtered: false });
|
|
|
|
expect(response.requests?.[0]).toMatchObject({
|
|
eventType: 1,
|
|
target: { uin: 1_234_567_890, uid: '', name: 'requester' },
|
|
invitor: { uin: 2_345_678_901, uid: '', name: 'inviter' },
|
|
operatorUser: { uin: 3_456_789_012, uid: '', name: 'operator' },
|
|
});
|
|
expect(response.field2).toBe(1_785_406_628_779_279n);
|
|
});
|
|
|
|
it('keeps string account identifiers from the native reply', async () => {
|
|
const responseData = Buffer.from(
|
|
protobuf_encode<OidbBase<OidbSvcTrpcTcp0x10C0Response>>({
|
|
body: {
|
|
requests: [
|
|
{
|
|
sequence: 99n,
|
|
eventType: 22,
|
|
state: 1,
|
|
group: { groupUin: 1001, groupName: 'group' },
|
|
target: { uid: 'u_target', name: 'invitee' },
|
|
invitor: { uid: 'u_inviter', name: 'inviter' },
|
|
comment: 'join us',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
);
|
|
const sender = {
|
|
sendRawPacket: vi.fn(async () => ({
|
|
success: true,
|
|
gotResponse: true,
|
|
errorCode: 0,
|
|
errorMessage: '',
|
|
responseData,
|
|
})),
|
|
};
|
|
|
|
const response = await FetchGroupRequests.invoke(sender, { filtered: false });
|
|
|
|
expect(response.requests?.[0]).toMatchObject({
|
|
eventType: 22,
|
|
target: { uid: 'u_target', uin: 0, name: 'invitee' },
|
|
invitor: { uid: 'u_inviter', uin: 0, name: 'inviter' },
|
|
comment: 'join us',
|
|
});
|
|
});
|
|
|
|
it('keeps the UID response path on the non-native envelope', async () => {
|
|
const sender = makeSender();
|
|
await FetchGroupRequestsByUid.invoke(sender, { filtered: false });
|
|
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
|
const env = protobuf_decode<OidbBase<OidbGroupRequestList>>(bytes);
|
|
expect(env.reserved ?? 0).toBe(0);
|
|
});
|
|
});
|
|
});
|