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

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,410 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
import type { OidbBase, OidbEmpty } from '@snowluma/proto-defs/oidb';
import type { OidbGroupReaction } from '@snowluma/proto-defs/oidb-actions/base';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import {
buildOidbRequest,
invokeOidb,
OidbError,
parseOidbResponse,
type OidbCallSpec,
type OidbSender,
} from '../src/oidb-service';
// Reused minimal spec — encodes a group-reaction-shaped request and
// reads back a void result. Exercises every spec branch.
const reactionSpec: OidbCallSpec<
OidbSender,
OidbGroupReaction,
OidbEmpty,
{ groupId: number; sequence: number; code: string; isSet: boolean },
void
> = {
command: 0x9082,
resolveSubCommand: (p) => (p.isSet ? 1 : 2),
serialize: (_ctx, p) => ({
groupUin: p.groupId,
sequence: p.sequence,
code: p.code,
type: p.code.length > 3 ? 2 : 1,
field6: false,
field7: false,
}),
deserialize: () => {},
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbEmpty>>(bytes),
};
// Static-subCommand variant for the branch test.
const staticSubSpec: OidbCallSpec<
OidbSender,
OidbGroupReaction,
OidbEmpty,
{ groupId: number },
void
> = {
command: 0xeac,
subCommand: 1,
serialize: (_ctx, p) => ({
groupUin: p.groupId,
sequence: 0,
code: '',
type: 1,
field6: false,
field7: false,
}),
deserialize: () => {},
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbEmpty>>(bytes),
};
function makeSender(
resp: Partial<SendPacketResult> = {},
): OidbSender & { sendRawPacket: ReturnType<typeof vi.fn> } {
const defaultResp: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData: Buffer.alloc(0),
};
return {
sendRawPacket: vi.fn(async () => ({ ...defaultResp, ...resp })),
};
}
describe('invokeOidb', () => {
it('uses resolveSubCommand when present and computes the wire name', async () => {
const sender = makeSender();
await invokeOidb(sender, reactionSpec, {
groupId: 12345,
sequence: 99,
code: '76',
isSet: true,
});
expect(sender.sendRawPacket).toHaveBeenCalledOnce();
const [wireName] = sender.sendRawPacket.mock.calls[0]!;
expect(wireName).toBe('OidbSvcTrpcTcp.0x9082_1');
});
it('flips wire name when resolveSubCommand returns a different subcmd', async () => {
const sender = makeSender();
await invokeOidb(sender, reactionSpec, {
groupId: 1,
sequence: 1,
code: '76',
isSet: false,
});
const [wireName] = sender.sendRawPacket.mock.calls[0]!;
expect(wireName).toBe('OidbSvcTrpcTcp.0x9082_2');
});
it('uses static subCommand when resolveSubCommand is absent', async () => {
const sender = makeSender();
await invokeOidb(sender, staticSubSpec, { groupId: 12345 });
const [wireName] = sender.sendRawPacket.mock.calls[0]!;
expect(wireName).toBe('OidbSvcTrpcTcp.0xeac_1');
});
it('encodes serialize() output into an OIDB envelope (cmd + subcmd land in fields 1/2)', async () => {
const sender = makeSender();
await invokeOidb(sender, reactionSpec, {
groupId: 12345,
sequence: 99,
code: '76',
isSet: true,
});
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbGroupReaction>>(bytes);
expect(env.command).toBe(0x9082);
expect(env.subCommand).toBe(1);
expect(env.body).toMatchObject({
groupUin: 12345,
sequence: 99,
code: '76',
type: 1,
});
});
it('passes the body through deserialize for transformation', async () => {
type Echo = { tag: number };
const spec: OidbCallSpec<
OidbSender,
OidbGroupReaction,
OidbGroupReaction,
{ code: string },
Echo
> = {
command: 0x9082,
subCommand: 1,
serialize: (_ctx, p) => ({ code: p.code }),
deserialize: (_ctx, b) => ({ tag: b.type ?? 99 }),
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbGroupReaction>>(bytes),
};
const responseEnvelope = protobuf_encode<OidbBase<OidbGroupReaction>>({
command: 0x9082,
subCommand: 1,
body: { type: 5 } as OidbGroupReaction,
});
const sender = makeSender({ responseData: Buffer.from(responseEnvelope) });
const result = await invokeOidb(sender, spec, { code: 'x' });
expect(result).toEqual({ tag: 5 });
});
it('substitutes an empty body when the envelope has no field 4', async () => {
const envBytes = protobuf_encode<OidbBase<OidbEmpty>>({ command: 0x9082, subCommand: 1 });
const sender = makeSender({ responseData: Buffer.from(envBytes) });
const deserialize = vi.fn(() => undefined);
const spec: OidbCallSpec<OidbSender, OidbGroupReaction, OidbEmpty, { code: string }, void> =
{
command: 0x9082,
subCommand: 1,
serialize: (_ctx, p) => ({ code: p.code }),
deserialize,
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbEmpty>>(bytes),
};
await invokeOidb(sender, spec, { code: 'x' });
expect(deserialize).toHaveBeenCalledOnce();
// deserialize now receives (ctx, body) — body is the second arg.
expect(deserialize.mock.calls[0]![1]).toEqual({});
});
it('awaits async serialize before sending', async () => {
// ctx-aware serialize may need to resolve a uid first; invokeOidb
// must await it before encoding the envelope.
const sender = makeSender();
const spec: OidbCallSpec<OidbSender, OidbGroupReaction, OidbEmpty, { code: string }, void> =
{
command: 0x9082,
subCommand: 1,
serialize: async (_ctx, p) => {
await new Promise((r) => setTimeout(r, 5));
return { code: p.code, sequence: 42 };
},
deserialize: () => {},
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbEmpty>>(bytes),
};
await invokeOidb(sender, spec, { code: 'async' });
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbGroupReaction>>(bytes);
expect(env.body?.code).toBe('async');
expect(env.body?.sequence).toBe(42);
});
it('threads ctx into serialize so namespaces can read identity / resolve uids in place', async () => {
const sender = makeSender();
// Extend the ctx with an arbitrary capability the spec uses.
type Ctx = OidbSender & { tag: string };
const ctx: Ctx = { ...sender, tag: 'from-ctx' };
const spec: OidbCallSpec<Ctx, OidbGroupReaction, OidbEmpty, { code: string }, void> = {
command: 0x9082,
subCommand: 1,
serialize: (c, p) => ({ code: `${c.tag}/${p.code}` }),
deserialize: () => {},
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbEmpty>>(bytes),
};
await invokeOidb(ctx, spec, { code: 'x' });
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbGroupReaction>>(bytes);
expect(env.body?.code).toBe('from-ctx/x');
});
it('propagates timeoutMs to sendRawPacket', async () => {
const sender = makeSender();
await invokeOidb(sender, staticSubSpec, { groupId: 1 }, 5000);
expect(sender.sendRawPacket.mock.calls[0]![2]).toBe(5000);
});
it('rethrows the sender errorMessage when success=false', async () => {
const sender = makeSender({ success: false, errorMessage: 'boom' });
await expect(invokeOidb(sender, staticSubSpec, { groupId: 1 })).rejects.toThrow('boom');
});
it('falls back to "packet send failed" when success=false and errorMessage is empty', async () => {
const sender = makeSender({ success: false, errorMessage: '' });
await expect(invokeOidb(sender, staticSubSpec, { groupId: 1 })).rejects.toThrow(
'packet send failed',
);
});
it('rethrows when sender returns success=true but gotResponse=false', async () => {
const sender = makeSender({ success: true, gotResponse: false, errorMessage: 'timeout' });
await expect(invokeOidb(sender, staticSubSpec, { groupId: 1 })).rejects.toThrow('timeout');
});
it('falls back to "no response" when gotResponse=false and errorMessage is empty', async () => {
const sender = makeSender({ success: true, gotResponse: false, errorMessage: '' });
await expect(invokeOidb(sender, staticSubSpec, { groupId: 1 })).rejects.toThrow(
'no response',
);
});
it('raises OidbError when envelope errorCode != 0', async () => {
const errBytes = protobuf_encode<OidbBase<OidbEmpty>>({
command: 0x9082,
subCommand: 1,
errorCode: 42,
errorMsg: 'no privilege',
});
const sender = makeSender({ responseData: Buffer.from(errBytes) });
let caught: unknown;
try {
await invokeOidb(sender, staticSubSpec, { groupId: 1 });
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(OidbError);
expect((caught as OidbError).code).toBe(42);
expect((caught as OidbError).serverMsg).toBe('no privilege');
expect((caught as OidbError).command).toBe(0xeac);
expect((caught as OidbError).subCommand).toBe(1);
expect((caught as OidbError).message).toContain('OIDB error 42');
expect((caught as OidbError).message).toContain('0xeac_1');
});
it('skips OidbError check when response payload is empty', async () => {
const sender = makeSender({ responseData: Buffer.alloc(0) });
await expect(invokeOidb(sender, staticSubSpec, { groupId: 1 })).resolves.toBeUndefined();
});
it('honours uinForm=true by flipping OIDB envelope reserved to 1', async () => {
const spec: OidbCallSpec<
OidbSender,
OidbGroupReaction,
OidbEmpty,
{ groupId: number },
void
> = {
...staticSubSpec,
uinForm: true,
};
const sender = makeSender();
await invokeOidb(sender, spec, { groupId: 1 });
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbGroupReaction>>(bytes);
expect(env.reserved).toBe(1);
});
it('uses uinForm=false by default (envelope reserved=0)', async () => {
const sender = makeSender();
await invokeOidb(sender, staticSubSpec, { groupId: 1 });
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbGroupReaction>>(bytes);
expect(env.reserved ?? 0).toBe(0);
});
it('treats registered envelope codes as success on the packet and body', async () => {
const spec = { ...staticSubSpec, acceptedEnvelopeCodes: [2_001_002] };
const bodyBytes = protobuf_encode<OidbBase<OidbEmpty>>({
command: 0xeac,
subCommand: 1,
errorCode: 2_001_002,
errorMsg: 'already applied',
});
await expect(
invokeOidb(makeSender({ responseData: Buffer.from(bodyBytes) }), spec, { groupId: 1 }),
).resolves.toBeUndefined();
await expect(
invokeOidb(
makeSender({
success: false,
errorCode: 2_001_002,
errorMessage: 'already applied',
responseData: null,
}),
spec,
{ groupId: 1 },
),
).resolves.toBeUndefined();
});
it('uses resolveCommand for the wire name and envelope command', async () => {
const spec = {
...staticSubSpec,
command: 0,
resolveCommand: () => 0x11c5,
subCommand: 100,
};
const sender = makeSender();
await invokeOidb(sender, spec, { groupId: 1 });
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x11c5_100');
});
});
describe('buildOidbRequest', () => {
it('returns the same wire name + bytes that invokeOidb would send', async () => {
const sender = makeSender();
await invokeOidb(sender, reactionSpec, {
groupId: 12345,
sequence: 99,
code: '128516',
isSet: true,
});
const sentBytes = sender.sendRawPacket.mock.calls[0]![1];
const built = await buildOidbRequest(sender, reactionSpec, {
groupId: 12345,
sequence: 99,
code: '128516',
isSet: true,
});
expect(built.wireName).toBe('OidbSvcTrpcTcp.0x9082_1');
expect(Buffer.from(built.bytes).equals(Buffer.from(sentBytes))).toBe(true);
});
it('uses static subCommand when resolveSubCommand is absent', async () => {
const sender = makeSender();
const built = await buildOidbRequest(sender, staticSubSpec, { groupId: 1 });
expect(built.wireName).toBe('OidbSvcTrpcTcp.0xeac_1');
});
});
describe('parseOidbResponse', () => {
it('decodes wire bytes via spec.decode and runs spec.deserialize on the body', () => {
const sender = makeSender();
const envBytes = protobuf_encode<OidbBase<OidbGroupReaction>>({
command: 0x9082,
subCommand: 1,
body: { type: 7 } as OidbGroupReaction,
});
type Echo = { type: number };
const spec: OidbCallSpec<OidbSender, OidbGroupReaction, OidbGroupReaction, void, Echo> = {
command: 0x9082,
subCommand: 1,
serialize: () => ({}) as OidbGroupReaction,
deserialize: (_ctx, b) => ({ type: b.type ?? 0 }),
encode: (env) => protobuf_encode<OidbBase<OidbGroupReaction>>(env),
decode: (bytes) => protobuf_decode<OidbBase<OidbGroupReaction>>(bytes),
};
expect(parseOidbResponse(sender, spec, envBytes)).toEqual({ type: 7 });
});
it('substitutes empty body when the envelope lacks one', () => {
const sender = makeSender();
const envBytes = protobuf_encode<OidbBase<OidbEmpty>>({ command: 0x9082, subCommand: 1 });
const deserialize = vi.fn(() => ({ ok: true }));
parseOidbResponse(sender, { ...staticSubSpec, deserialize } as any, envBytes);
// deserialize now receives (ctx, body) — body is the second arg.
expect(deserialize.mock.calls[0]![1]).toEqual({});
});
});
describe('OidbError', () => {
it('formats message with hex cmd and decimal subcmd', () => {
const e = new OidbError(123, 'denied', 0x9084, 2);
expect(e.message).toBe('OIDB error 123 on 0x9084_2: denied');
expect(e.name).toBe('OidbError');
expect(e.code).toBe(123);
expect(e.serverMsg).toBe('denied');
expect(e.command).toBe(0x9084);
expect(e.subCommand).toBe(2);
});
});