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,84 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type {
|
||||
OidbAiVoiceListReq,
|
||||
OidbAiVoiceListResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { FetchAiVoiceList } from '../../../src/oidb-services/extras/fetch-ai-voice-list';
|
||||
|
||||
function makeSender(body?: OidbAiVoiceListResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbAiVoiceListResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('FetchAiVoiceList namespace', () => {
|
||||
it('declares 0x929D_0', () => {
|
||||
expect(FetchAiVoiceList.command).toBe(0x929d);
|
||||
expect(FetchAiVoiceList.subCommand).toBe(0);
|
||||
});
|
||||
|
||||
describe('serialize', () => {
|
||||
it('passes groupUin / chatType verbatim', () => {
|
||||
expect(FetchAiVoiceList.serialize({} as any, { groupId: 4242, chatType: 1 })).toEqual({
|
||||
groupUin: 4242,
|
||||
chatType: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deserialize', () => {
|
||||
it('returns content array verbatim', () => {
|
||||
const out = FetchAiVoiceList.deserialize({} as any, {
|
||||
content: [
|
||||
{
|
||||
category: 'cute',
|
||||
voices: [
|
||||
{ voiceId: 'v1', voiceDisplayName: 'V1', voiceExampleUrl: 'http://x' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(out).toEqual([
|
||||
{
|
||||
category: 'cute',
|
||||
voices: [
|
||||
{ voiceId: 'v1', voiceDisplayName: 'V1', voiceExampleUrl: 'http://x' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns [] when content is omitted', () => {
|
||||
expect(FetchAiVoiceList.deserialize({} as any, {})).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invoke (e2e)', () => {
|
||||
it('routes to OidbSvcTrpcTcp.0x929d_0', async () => {
|
||||
const sender = makeSender({ content: [] });
|
||||
await FetchAiVoiceList.invoke(sender, { groupId: 100, chatType: 1 });
|
||||
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x929d_0');
|
||||
});
|
||||
|
||||
it('encodes envelope body correctly', async () => {
|
||||
const sender = makeSender({ content: [] });
|
||||
await FetchAiVoiceList.invoke(sender, { groupId: 4242, chatType: 2 });
|
||||
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
||||
const env = protobuf_decode<OidbBase<OidbAiVoiceListReq>>(bytes);
|
||||
expect(env.body).toMatchObject({ groupUin: 4242, chatType: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type { OidbAiVoiceReq, OidbAiVoiceResp } from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { FetchAiVoice } from '../../../src/oidb-services/extras/fetch-ai-voice';
|
||||
|
||||
function makeSender(body?: OidbAiVoiceResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbAiVoiceResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('FetchAiVoice namespace', () => {
|
||||
it('declares 0x929B_0', () => {
|
||||
expect(FetchAiVoice.command).toBe(0x929b);
|
||||
expect(FetchAiVoice.subCommand).toBe(0);
|
||||
});
|
||||
|
||||
describe('serialize', () => {
|
||||
it('packages every field including sessionId into session sub-message', () => {
|
||||
const out = FetchAiVoice.serialize({} as any, {
|
||||
groupId: 100,
|
||||
voiceId: 'v',
|
||||
text: 'hi',
|
||||
chatType: 1,
|
||||
sessionId: 0xcafe,
|
||||
});
|
||||
expect(out).toEqual({
|
||||
groupUin: 100,
|
||||
voiceId: 'v',
|
||||
text: 'hi',
|
||||
chatType: 1,
|
||||
session: { sessionId: 0xcafe },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deserialize', () => {
|
||||
it('returns the first msgInfoBody index', () => {
|
||||
const node = { fileUuid: 'uuid', subType: 1 };
|
||||
expect(
|
||||
FetchAiVoice.deserialize(
|
||||
{} as any,
|
||||
{
|
||||
field2: 319,
|
||||
field3: 20,
|
||||
msgInfo: { msgInfoBody: [{ index: node }] },
|
||||
} as any,
|
||||
),
|
||||
).toEqual(node);
|
||||
});
|
||||
|
||||
it('returns null when msgInfo is empty / missing', () => {
|
||||
expect(FetchAiVoice.deserialize({} as any, {} as any)).toBeNull();
|
||||
expect(FetchAiVoice.deserialize({} as any, { msgInfo: {} } as any)).toBeNull();
|
||||
expect(
|
||||
FetchAiVoice.deserialize({} as any, { msgInfo: { msgInfoBody: [] } } as any),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('invoke (e2e)', () => {
|
||||
it('routes to OidbSvcTrpcTcp.0x929b_0', async () => {
|
||||
const sender = makeSender({
|
||||
msgInfo: { msgInfoBody: [{ index: { fileUuid: 'u' } }] },
|
||||
} as any);
|
||||
await FetchAiVoice.invoke(sender, {
|
||||
groupId: 1,
|
||||
voiceId: 'v',
|
||||
text: 't',
|
||||
chatType: 1,
|
||||
sessionId: 1,
|
||||
});
|
||||
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x929b_0');
|
||||
});
|
||||
|
||||
it('returns the parsed media node', async () => {
|
||||
const node = { fileUuid: 'uuid', subType: 1 };
|
||||
const sender = makeSender({
|
||||
field2: 319,
|
||||
field3: 20,
|
||||
msgInfo: { msgInfoBody: [{ index: node }] },
|
||||
} as any);
|
||||
const out = await FetchAiVoice.invoke(sender, {
|
||||
groupId: 1,
|
||||
voiceId: 'v',
|
||||
text: 't',
|
||||
chatType: 1,
|
||||
sessionId: 1,
|
||||
});
|
||||
expect(out).toMatchObject(node);
|
||||
});
|
||||
|
||||
it('returns null when server polls back empty msgInfo (in-flight)', async () => {
|
||||
const sender = makeSender({} as any);
|
||||
const out = await FetchAiVoice.invoke(sender, {
|
||||
groupId: 1,
|
||||
voiceId: 'v',
|
||||
text: 't',
|
||||
chatType: 1,
|
||||
sessionId: 1,
|
||||
});
|
||||
expect(out).toBeNull();
|
||||
});
|
||||
|
||||
it('passes the sessionId through to the wire envelope verbatim', async () => {
|
||||
const sender = makeSender({} as any);
|
||||
await FetchAiVoice.invoke(sender, {
|
||||
groupId: 1,
|
||||
voiceId: 'v',
|
||||
text: 't',
|
||||
chatType: 1,
|
||||
sessionId: 0xcafebabe,
|
||||
});
|
||||
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
||||
const env = protobuf_decode<OidbBase<OidbAiVoiceReq>>(bytes);
|
||||
expect(env.body?.session?.sessionId).toBe(0xcafebabe);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { protobuf_decode } from '@snowluma/proton';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type { OidbQueryGroupTopBannersReq } from '@snowluma/proto-defs/oidb-actions/base';
|
||||
|
||||
import { GetGroupTodoList } from '../../../src/oidb-services/extras/get-group-todo-list';
|
||||
|
||||
// Fixed wire fixture shaped after a live QQ 3.2.32-51802 0x9474_0 response.
|
||||
// It deliberately uses the current common-banner representation rather than
|
||||
// constructing the response with the production protobuf encoder.
|
||||
const ACTIVE_TODO_RESPONSE = Buffer.from(
|
||||
'08f4a802100018002291010a8e01080f10021a06353633315f3040d09ba8bb06' +
|
||||
'a2016a0a3f0a1d68747470733a2f2f6578616d706c652e746573742f746f646f' +
|
||||
'2e706e67120ce7bea4e5be85e58a9eefbd9c1a0ce6b58be8af95e5be85e58a9e' +
|
||||
'28023003121b08031a177b22736571223a353633312c2272616e646f6d223a307d' +
|
||||
'18c0ffa7bb0620c1ffa7bb06c802a29c01d002c801e80201f002012a006000',
|
||||
'hex',
|
||||
);
|
||||
|
||||
function makeSender(responseData = Buffer.alloc(0)) {
|
||||
const result: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => result) };
|
||||
}
|
||||
|
||||
describe('GetGroupTodoList namespace', () => {
|
||||
it('queries 0x9474_0 with the todo-only banner flag', async () => {
|
||||
const sender = makeSender();
|
||||
|
||||
await GetGroupTodoList.invoke(sender, { groupId: 941657197 });
|
||||
|
||||
const [wireName, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
||||
expect(wireName).toBe('OidbSvcTrpcTcp.0x9474_0');
|
||||
expect(GetGroupTodoList.subCommand).toBe(0);
|
||||
const envelope = protobuf_decode<OidbBase<OidbQueryGroupTopBannersReq>>(bytes);
|
||||
expect(envelope).toMatchObject({
|
||||
command: 0x9474,
|
||||
body: { groupId: 941657197n, bannerFlag: 1 },
|
||||
});
|
||||
});
|
||||
|
||||
it('decodes the current common-banner todo and validates both identity sources', async () => {
|
||||
const sender = makeSender(ACTIVE_TODO_RESPONSE);
|
||||
|
||||
await expect(GetGroupTodoList.invoke(sender, { groupId: 941657197 })).resolves.toEqual([
|
||||
{
|
||||
sourceId: '5631_0',
|
||||
sequence: 5631,
|
||||
random: 0,
|
||||
text: '测试待办',
|
||||
createdAt: 1735000000,
|
||||
updatedAt: 1735000001,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters disappeared todo tombstones', () => {
|
||||
expect(
|
||||
GetGroupTodoList.deserialize({} as never, {
|
||||
banners: [{ bizType: 15, bannerType: 2, isDisappear: true, bizId: 20002 }],
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('supports the legacy todo text shape without inventing timestamps', () => {
|
||||
expect(
|
||||
GetGroupTodoList.deserialize({} as never, {
|
||||
banners: [
|
||||
{
|
||||
msgId: Buffer.from('88_0'),
|
||||
isDisappear: false,
|
||||
todoBanner: { text: '旧版待办' },
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
sourceId: '88_0',
|
||||
sequence: 88,
|
||||
random: 0,
|
||||
text: '旧版待办',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects an active todo whose message identity cannot be mapped', () => {
|
||||
expect(() =>
|
||||
GetGroupTodoList.deserialize({} as never, {
|
||||
banners: [
|
||||
{
|
||||
bizType: 15,
|
||||
bannerType: 2,
|
||||
msgId: Buffer.from('not-a-message-id'),
|
||||
isDisappear: false,
|
||||
bizId: 20002,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toThrow(/invalid group todo message identity/);
|
||||
});
|
||||
|
||||
it('rejects disagreement between msgId and the jump payload', () => {
|
||||
expect(() =>
|
||||
GetGroupTodoList.deserialize({} as never, {
|
||||
banners: [
|
||||
{
|
||||
bizType: 15,
|
||||
bannerType: 2,
|
||||
msgId: Buffer.from('5631_0'),
|
||||
isDisappear: false,
|
||||
bizId: 20002,
|
||||
commonBanner: {
|
||||
jumpInfo: { jumpParam: Buffer.from('{"seq":5632,"random":0}') },
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toThrow(/identity mismatch/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
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 } from '@snowluma/proto-defs/oidb';
|
||||
import type {
|
||||
OidbStrangerStatusReq,
|
||||
OidbStrangerStatusResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/base';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetStrangerStatus } from '../../../src/oidb-services/extras/get-stranger-status';
|
||||
|
||||
interface ObservedStatusProperty {
|
||||
key?: pb<1, uint_32>;
|
||||
value?: pb<2, uint_64>;
|
||||
}
|
||||
|
||||
interface ObservedStatusProperties {
|
||||
entries?: pb_repeated<1, ObservedStatusProperty>;
|
||||
}
|
||||
|
||||
interface ObservedStatusData {
|
||||
targetUin?: pb<1, uint_32>;
|
||||
properties?: pb<2, ObservedStatusProperties>;
|
||||
uin?: pb<3, uint_32>;
|
||||
}
|
||||
|
||||
interface ObservedStatusResponse {
|
||||
data?: pb<1, ObservedStatusData>;
|
||||
}
|
||||
|
||||
function makeSender(body?: OidbStrangerStatusResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbStrangerStatusResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetStrangerStatus namespace', () => {
|
||||
it('declares 0xFE1_2 with uinForm=true', () => {
|
||||
expect(GetStrangerStatus.command).toBe(0xfe1);
|
||||
expect(GetStrangerStatus.subCommand).toBe(2);
|
||||
expect(GetStrangerStatus.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
describe('serialize', () => {
|
||||
it('always queries only key 27372 (status property)', () => {
|
||||
const out = GetStrangerStatus.serialize({} as any, { uin: 100200 });
|
||||
expect(out).toEqual({ uin: 100200, key: [{ key: 27372 }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('deserialize', () => {
|
||||
it('low-band (≤10) values map to (value*10, 0)', () => {
|
||||
expect(
|
||||
GetStrangerStatus.deserialize({} as any, {
|
||||
data: { properties: { entries: [{ key: 27372, value: 7n }] } },
|
||||
}),
|
||||
).toEqual({ status: 70, ext_status: 0 });
|
||||
expect(
|
||||
GetStrangerStatus.deserialize({} as any, {
|
||||
data: { properties: { entries: [{ key: 27372, value: 10n }] } },
|
||||
}),
|
||||
).toEqual({ status: 100, ext_status: 0 });
|
||||
});
|
||||
|
||||
it('high-band values decompose into (0xff00 + (>>16 & 0xff)) ext_status', () => {
|
||||
// 0x42F100 → bits 8..15 = 0xF1<<8 = 0xF100, bits 16..23 = 0x42 → 0xF142
|
||||
expect(
|
||||
GetStrangerStatus.deserialize({} as any, {
|
||||
data: { properties: { entries: [{ key: 27372, value: 0x42f100n }] } },
|
||||
}),
|
||||
).toEqual({ status: 10, ext_status: 0xf142 });
|
||||
});
|
||||
|
||||
it('returns null when the requested status property is missing', () => {
|
||||
expect(GetStrangerStatus.deserialize({} as any, {})).toBeNull();
|
||||
expect(GetStrangerStatus.deserialize({} as any, { data: {} })).toBeNull();
|
||||
expect(
|
||||
GetStrangerStatus.deserialize({} as any, { data: { properties: {} } }),
|
||||
).toBeNull();
|
||||
expect(
|
||||
GetStrangerStatus.deserialize({} as any, {
|
||||
data: { properties: { entries: [{ key: 10000, value: 7n }] } },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('invoke (e2e)', () => {
|
||||
it('decodes the property-list hierarchy returned by 0xFE1_2', async () => {
|
||||
const responseData = Buffer.from(
|
||||
protobuf_encode<OidbBase<ObservedStatusResponse>>({
|
||||
body: {
|
||||
data: {
|
||||
targetUin: 100200,
|
||||
properties: {
|
||||
entries: [
|
||||
{ key: 10000, value: 1n },
|
||||
{ key: 27372, value: 4n },
|
||||
],
|
||||
},
|
||||
uin: 100200,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const result: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
const sender = { sendRawPacket: vi.fn(async () => result) };
|
||||
|
||||
await expect(GetStrangerStatus.invoke(sender, { uin: 100200 })).resolves.toEqual({
|
||||
status: 40,
|
||||
ext_status: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('routes to OidbSvcTrpcTcp.0xfe1_2 with uinForm=true (envelope reserved=1)', async () => {
|
||||
const sender = makeSender({
|
||||
data: { properties: { entries: [{ key: 27372, value: 5n }] } },
|
||||
});
|
||||
await GetStrangerStatus.invoke(sender, { uin: 100200 });
|
||||
const [wireName, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
||||
expect(wireName).toBe('OidbSvcTrpcTcp.0xfe1_2');
|
||||
const env = protobuf_decode<OidbBase<OidbStrangerStatusReq>>(bytes);
|
||||
expect(env.reserved).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the decoded status object', async () => {
|
||||
const sender = makeSender({
|
||||
data: { properties: { entries: [{ key: 27372, value: 5n }] } },
|
||||
});
|
||||
const out = await GetStrangerStatus.invoke(sender, { uin: 1 });
|
||||
expect(out).toEqual({ status: 50, ext_status: 0 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { protobuf_decode } from '@snowluma/proton';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type { OidbGroupTodo } from '@snowluma/proto-defs/oidb-actions/base';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GroupTodo } from '../../../src/oidb-services/extras/group-todo';
|
||||
|
||||
function makeSender() {
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData: Buffer.alloc(0),
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GroupTodo namespace', () => {
|
||||
it('declares 0xF90', () => {
|
||||
expect(GroupTodo.command).toBe(0xf90);
|
||||
});
|
||||
|
||||
describe('resolveSubCommand', () => {
|
||||
it('maps set → 1', () => {
|
||||
expect(GroupTodo.resolveSubCommand({ groupId: 1, msgSeq: 1n, action: 'set' })).toBe(1);
|
||||
});
|
||||
it('maps complete → 2', () => {
|
||||
expect(
|
||||
GroupTodo.resolveSubCommand({ groupId: 1, msgSeq: 1n, action: 'complete' }),
|
||||
).toBe(2);
|
||||
});
|
||||
it('maps cancel → 3', () => {
|
||||
expect(GroupTodo.resolveSubCommand({ groupId: 1, msgSeq: 1n, action: 'cancel' })).toBe(
|
||||
3,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('serialize', () => {
|
||||
it('packages groupUin + msgSeq verbatim regardless of action', () => {
|
||||
for (const action of ['set', 'complete', 'cancel'] as const) {
|
||||
const out = GroupTodo.serialize({} as any, {
|
||||
groupId: 12345,
|
||||
msgSeq: 9876543210n,
|
||||
action,
|
||||
});
|
||||
expect(out).toEqual({ groupUin: 12345, msgSeq: 9876543210n });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('invoke (e2e)', () => {
|
||||
it.each([
|
||||
['set', 'OidbSvcTrpcTcp.0xf90_1'] as const,
|
||||
['complete', 'OidbSvcTrpcTcp.0xf90_2'] as const,
|
||||
['cancel', 'OidbSvcTrpcTcp.0xf90_3'] as const,
|
||||
])('action %s routes to %s', async (action, expectedWire) => {
|
||||
const sender = makeSender();
|
||||
await GroupTodo.invoke(sender, { groupId: 12345, msgSeq: 100n, action });
|
||||
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe(expectedWire);
|
||||
});
|
||||
|
||||
it('encodes envelope body correctly', async () => {
|
||||
const sender = makeSender();
|
||||
await GroupTodo.invoke(sender, { groupId: 100, msgSeq: 999n, action: 'set' });
|
||||
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
||||
const env = protobuf_decode<OidbBase<OidbGroupTodo>>(bytes);
|
||||
expect(env.body).toEqual({ groupUin: 100, msgSeq: 999n });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user