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
153 lines
6.0 KiB
TypeScript
153 lines
6.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
|
import { GetAdminReceiveVerify } from '../../../src/oidb-services/group-admin/get-admin-receive-verify';
|
|
import { env, m, s, v } from '../_pb-oracle';
|
|
|
|
const target = { groupId: 12345, userId: 67890 };
|
|
const uid = 'test-member-uid';
|
|
|
|
function member(flag?: number, memberUid = uid, uin: number | undefined = target.userId) {
|
|
return m(2, [
|
|
...m(1, [...s(2, memberUid), ...(uin === undefined ? [] : v(4, uin))]),
|
|
...(flag === undefined ? [] : v(104, flag)),
|
|
]);
|
|
}
|
|
|
|
function response(members: number[], group: number | undefined = target.groupId) {
|
|
return m(4, [...(group === undefined ? [] : v(1, group)), ...members]);
|
|
}
|
|
|
|
function sender(body = response(member(0))) {
|
|
const result: SendPacketResult = {
|
|
success: true,
|
|
gotResponse: true,
|
|
errorCode: 0,
|
|
errorMessage: '',
|
|
responseData: Buffer.from(body),
|
|
};
|
|
return {
|
|
sendRawPacket: vi.fn(async (_command: string, _bytes: Uint8Array) => result),
|
|
resolveUserUid: vi.fn(async (_userId: number, _groupId?: number) => uid),
|
|
};
|
|
}
|
|
|
|
describe('administrator receive-verification read', () => {
|
|
it('resolves the member once in its group and requests only the verification state', async () => {
|
|
const deps = sender();
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(true);
|
|
expect(deps.resolveUserUid).toHaveBeenCalledExactlyOnceWith(target.userId, target.groupId);
|
|
expect(deps.sendRawPacket).toHaveBeenCalledTimes(1);
|
|
const [command, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
|
expect(command).toBe('OidbSvcTrpcTcp.0xfe7_4');
|
|
expect(Buffer.from(bytes).toString('hex')).toBe(
|
|
env(
|
|
0xfe7,
|
|
4,
|
|
[
|
|
...v(1, target.groupId),
|
|
...v(2, 3),
|
|
...v(3, 0),
|
|
...m(4, v(104, 1)),
|
|
...m(5, s(2, uid)),
|
|
],
|
|
false,
|
|
),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
[0, true],
|
|
[64, false],
|
|
[32, true],
|
|
[96, false],
|
|
[0x80000000, true],
|
|
[0xffffffff, false],
|
|
[undefined, null],
|
|
] as const)('decodes flag %s as %s while preserving missing data', async (flag, enabled) => {
|
|
const deps = sender(response(member(flag)));
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(enabled);
|
|
});
|
|
|
|
it('selects the requested member even when unrelated members are returned first', async () => {
|
|
const deps = sender(response([...member(64, 'unrelated-member', 10001), ...member(0)]));
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(true);
|
|
});
|
|
|
|
it('accepts UID-bound responses without an optional numeric identity', async () => {
|
|
const deps = sender(response(m(2, [...m(1, s(2, uid)), ...v(104, 64)])));
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(false);
|
|
});
|
|
|
|
it('preserves large numeric group and member identifiers', async () => {
|
|
const large = { groupId: 2 ** 40 + 17, userId: 2 ** 41 + 19 };
|
|
const deps = sender(response(member(0, uid, large.userId), large.groupId));
|
|
await expect(GetAdminReceiveVerify.invoke(deps, large)).resolves.toBe(true);
|
|
expect(Buffer.from(deps.sendRawPacket.mock.calls[0]![1]).toString('hex')).toBe(
|
|
env(
|
|
0xfe7,
|
|
4,
|
|
[
|
|
...v(1, large.groupId),
|
|
...v(2, 3),
|
|
...v(3, 0),
|
|
...m(4, v(104, 1)),
|
|
...m(5, s(2, uid)),
|
|
],
|
|
false,
|
|
),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
['missing group', m(4, member(0)), 'missing or mismatched group'],
|
|
['wrong group', response(member(0), 999), 'missing or mismatched group'],
|
|
['missing member', response([]), 'exactly one matching member'],
|
|
['wrong member', response(member(0, 'other-uid')), 'exactly one matching member'],
|
|
[
|
|
'missing UID',
|
|
response(m(2, [...m(1, v(4, target.userId)), ...v(104, 0)])),
|
|
'exactly one matching member',
|
|
],
|
|
[
|
|
'duplicate member',
|
|
response([...member(0), ...member(64)]),
|
|
'exactly one matching member',
|
|
],
|
|
['conflicting numeric identity', response(member(0, uid, 999)), 'mismatched member UIN'],
|
|
] as const)(
|
|
'rejects %s rather than reporting a successful state',
|
|
async (_label, body, error) => {
|
|
const deps = sender([...body]);
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow(error);
|
|
},
|
|
);
|
|
|
|
it('does not send when identity resolution fails', async () => {
|
|
const deps = sender();
|
|
deps.resolveUserUid.mockRejectedValueOnce(new Error('identity unavailable'));
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow(
|
|
'identity unavailable',
|
|
);
|
|
expect(deps.sendRawPacket).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('propagates server rejection even if the body contains a valid state', async () => {
|
|
const deps = sender([...v(3, 1287), ...response(member(0))]);
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow('OIDB error 1287');
|
|
});
|
|
|
|
it('propagates transport failures without retrying', async () => {
|
|
const deps = sender();
|
|
deps.sendRawPacket.mockRejectedValueOnce(new Error('transport disconnected'));
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow(
|
|
'transport disconnected',
|
|
);
|
|
expect(deps.sendRawPacket).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('rejects truncated responses', async () => {
|
|
const deps = sender([0x22, 0x80]);
|
|
await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow();
|
|
});
|
|
});
|