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
164 lines
6.3 KiB
TypeScript
164 lines
6.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
|
import { SendTuwenArk } from '../../../src/oidb-services/contacts/send-tuwen-ark';
|
|
import { env, v, s, m } from '../_pb-oracle';
|
|
|
|
function makeSender(body = m(1, [...v(2, 10001), ...v(4, 0)])) {
|
|
const r: SendPacketResult = {
|
|
success: true,
|
|
gotResponse: true,
|
|
errorCode: 0,
|
|
errorMessage: '',
|
|
responseData: Buffer.from(env(0xdc2, 34, body, false), 'hex'),
|
|
};
|
|
return { sendRawPacket: vi.fn(async () => r) };
|
|
}
|
|
|
|
describe('SendTuwenArk namespace', () => {
|
|
it('declares command 0xdc2 sub 34, SSO: OidbSvcTrpcTcp.0xdc2_34', () => {
|
|
expect(SendTuwenArk.command).toBe(0xdc2);
|
|
expect(SendTuwenArk.subCommand).toBe(34);
|
|
expect(SendTuwenArk.uinForm).toBe(false);
|
|
});
|
|
|
|
it('serializes fixed appId=100446242, field2=1, field3=0, field5={1:1}', () => {
|
|
const out = SendTuwenArk.serialize({} as any, {
|
|
targetId: 12345,
|
|
peerType: 1,
|
|
title: 'T',
|
|
desc: 'D',
|
|
summary: 'S',
|
|
jumpUrl: 'https://example.com',
|
|
previewUrl: '',
|
|
});
|
|
expect(out.appInfo).toMatchObject({
|
|
appId: 100446242,
|
|
field2: 1,
|
|
field3: 0,
|
|
field5: { field1: 1 },
|
|
targetId: 12345,
|
|
});
|
|
});
|
|
|
|
it('byte-oracle: C2C (peerType=0) — peerType omitted on wire, field3=0 present, previewUrl present', async () => {
|
|
const sender = makeSender();
|
|
await SendTuwenArk.invoke(sender, {
|
|
targetId: 2863253201,
|
|
peerType: 0,
|
|
title: 'QQ开放平台',
|
|
desc: 'QQ小程序是连接广轻用户的新方式,覆盖8亿新生代活跃网民。轻便快捷的开发模式,将能在QQ内被轻松获取和传播',
|
|
summary: '[分享] QQ开放平台',
|
|
jumpUrl: 'https://q.qq.com/r',
|
|
previewUrl:
|
|
'https://tangram-1251316161.file.myqcloud.com/files/20210721/e50a8e37e08f29bf1ffc7466e1950690.png',
|
|
});
|
|
|
|
const [cmd, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
|
expect(cmd).toBe('OidbSvcTrpcTcp.0xdc2_34');
|
|
|
|
// AppInfo embedding: field5={1:1}, content={1:1, 10:title, 11:desc, 12:summary, 13:jumpUrl, 14:previewUrl}, then appId/field2/field3/targetId
|
|
const field5 = m(5, [...v(1, 1)]);
|
|
const content = m(12, [
|
|
...v(1, 1),
|
|
...s(10, 'QQ开放平台'),
|
|
...s(
|
|
11,
|
|
'QQ小程序是连接广轻用户的新方式,覆盖8亿新生代活跃网民。轻便快捷的开发模式,将能在QQ内被轻松获取和传播',
|
|
),
|
|
...s(12, '[分享] QQ开放平台'),
|
|
...s(13, 'https://q.qq.com/r'),
|
|
...s(
|
|
14,
|
|
'https://tangram-1251316161.file.myqcloud.com/files/20210721/e50a8e37e08f29bf1ffc7466e1950690.png',
|
|
),
|
|
]);
|
|
const appInfo = m(1, [
|
|
...v(1, 100446242),
|
|
...v(2, 1),
|
|
...v(3, 0), // field3=0 is present in wire (pb_optional preserves zero as expected)
|
|
...field5,
|
|
...v(11, 2863253201),
|
|
...content,
|
|
]);
|
|
// Meta: peerType=0 is present in wire (pb_optional preserves zero as expected)
|
|
const meta = m(2, [...v(1, 0), ...v(2, 2863253201)]);
|
|
const body = [...appInfo, ...meta];
|
|
expect(Buffer.from(bytes).toString('hex')).toBe(env(0xdc2, 34, body, false));
|
|
});
|
|
|
|
it('byte-oracle: group (peerType=1) — peerType present, field3=0 present, empty previewUrl present', async () => {
|
|
const sender = makeSender();
|
|
await SendTuwenArk.invoke(sender, {
|
|
targetId: 123456789,
|
|
peerType: 1,
|
|
title: '测试标题',
|
|
desc: '测试描述',
|
|
summary: '测试摘要',
|
|
jumpUrl: 'https://example.com/jump',
|
|
previewUrl: '',
|
|
});
|
|
|
|
const [cmd, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
|
expect(cmd).toBe('OidbSvcTrpcTcp.0xdc2_34');
|
|
|
|
const field5 = m(5, [...v(1, 1)]);
|
|
const content = m(12, [
|
|
...v(1, 1),
|
|
...s(10, '测试标题'),
|
|
...s(11, '测试描述'),
|
|
...s(12, '测试摘要'),
|
|
...s(13, 'https://example.com/jump'),
|
|
...s(14, ''), // previewUrl='' is present in wire (pb_optional preserves empty string as expected)
|
|
]);
|
|
const appInfo = m(1, [
|
|
...v(1, 100446242),
|
|
...v(2, 1),
|
|
...v(3, 0), // field3=0 is present
|
|
...field5,
|
|
...v(11, 123456789),
|
|
...content,
|
|
]);
|
|
// Meta: peerType=1 present
|
|
const meta = m(2, [...v(1, 1), ...v(2, 123456789)]);
|
|
const body = [...appInfo, ...meta];
|
|
expect(Buffer.from(bytes).toString('hex')).toBe(env(0xdc2, 34, body, false));
|
|
});
|
|
|
|
it('rejects a delivery refusal even when the outer envelope succeeds', async () => {
|
|
const sender = makeSender(
|
|
m(1, [
|
|
...v(1, 0),
|
|
...v(2, 10001),
|
|
...s(3, ''),
|
|
...v(4, 901501),
|
|
...s(5, 'imagent service_error:319_[oidb] rule type not match appid'),
|
|
]),
|
|
);
|
|
await expect(
|
|
SendTuwenArk.invoke(sender, {
|
|
targetId: 10001,
|
|
peerType: 0,
|
|
title: 'title',
|
|
desc: '',
|
|
summary: '',
|
|
jumpUrl: 'https://example.com',
|
|
previewUrl: '',
|
|
}),
|
|
).rejects.toThrow(/901501.*rule type not match appid/);
|
|
});
|
|
|
|
it('checks every delivery result and requires an acknowledgement', () => {
|
|
const sender = makeSender();
|
|
expect(() => SendTuwenArk.deserialize(sender, {})).toThrow('no delivery result');
|
|
expect(() => SendTuwenArk.deserialize(sender, { results: [] })).toThrow(
|
|
'no delivery result',
|
|
);
|
|
expect(() =>
|
|
SendTuwenArk.deserialize(sender, {
|
|
results: [{ errorCode: 0 }, { errorCode: 901501, errorMessage: 'refused' }],
|
|
}),
|
|
).toThrow('refused');
|
|
expect(SendTuwenArk.deserialize(sender, { results: [{}] })).toBeUndefined();
|
|
});
|
|
});
|