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,144 @@
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
import type {
|
||||
RoutingHead,
|
||||
SendMessageRequest,
|
||||
SendMessageResponse,
|
||||
} from '@snowluma/proto-defs/action';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type {
|
||||
NTV2C2CUserInfo,
|
||||
NTV2UploadRichMediaReq,
|
||||
NTV2UploadRichMediaResp,
|
||||
} from '@snowluma/proto-defs/highway';
|
||||
import { IdentityService } from '@snowluma/protocol/identity-service';
|
||||
import { buildSendElems } from '@snowluma/protocol/element-builder';
|
||||
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { Bridge } from '../src/bridge/bridge';
|
||||
|
||||
const png =
|
||||
'base64://iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jFZkAAAAASUVORK5CYII=';
|
||||
|
||||
function fixture(rejectUpload = false) {
|
||||
const requests: Array<{ command: string; body: Uint8Array }> = [];
|
||||
class TestBridge extends Bridge {
|
||||
override async resolveUserUid(): Promise<string> {
|
||||
return 'u_peer';
|
||||
}
|
||||
|
||||
override async sendRawPacket(command: string, body: Uint8Array): Promise<SendPacketResult> {
|
||||
requests.push({ command, body });
|
||||
let response: Uint8Array;
|
||||
|
||||
if (command === 'OidbSvcTrpcTcp.0x11c5_100') {
|
||||
response = protobuf_encode<OidbBase<NTV2UploadRichMediaResp>>({
|
||||
command: 0x11c5,
|
||||
subCommand: 100,
|
||||
errorCode: rejectUpload ? 170019003 : 0,
|
||||
errorMsg: rejectUpload ? 'verify identify fail' : '',
|
||||
body: {
|
||||
upload: {
|
||||
msgInfo: { msgInfoBody: [{ index: { fileUuid: 'cached-image' } }] },
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (command === 'MessageSvc.PbSendMsg') {
|
||||
response = protobuf_encode<SendMessageResponse>({ result: 0, privateSequence: 88 });
|
||||
} else {
|
||||
throw new Error(`unexpected packet: ${command}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData: Buffer.from(response),
|
||||
};
|
||||
}
|
||||
}
|
||||
return { bridge: new TestBridge(IdentityService.memory('10000')), requests };
|
||||
}
|
||||
|
||||
describe('passive temp-session image wire request (#471)', () => {
|
||||
it('uses the same existing-session route for image upload and the final message', async () => {
|
||||
const { bridge, requests } = fixture();
|
||||
await bridge.apis.message.sendGroupTempMessage(1001, 700, [
|
||||
{ type: 'text', text: 'reply' },
|
||||
{ type: 'image', url: png },
|
||||
]);
|
||||
|
||||
expect(requests.map((r) => r.command)).toEqual([
|
||||
'OidbSvcTrpcTcp.0x11c5_100',
|
||||
'MessageSvc.PbSendMsg',
|
||||
]);
|
||||
const upload = protobuf_decode<OidbBase<NTV2UploadRichMediaReq>>(requests[0]!.body).body!;
|
||||
expect(upload.reqHead?.scene).toMatchObject({
|
||||
sceneType: 1,
|
||||
businessType: 1,
|
||||
c2c: { accountType: 2, targetUid: 'u_peer' },
|
||||
});
|
||||
expect(upload.reqHead?.scene?.group ?? undefined).toBeUndefined();
|
||||
const route = upload.reqHead!.scene!.c2c!.routingHead!;
|
||||
expect(Array.from(route)).toEqual([
|
||||
0x1a, 0x0b, 0x18, 0xbc, 0x05, 0x22, 0x06, 0x75, 0x5f, 0x70, 0x65, 0x65, 0x72,
|
||||
]);
|
||||
expect(Array.from(protobuf_encode<NTV2C2CUserInfo>(upload.reqHead!.scene!.c2c!))).toEqual([
|
||||
0x08, 0x02, 0x12, 0x06, 0x75, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x1a, 0x0d, 0x1a, 0x0b,
|
||||
0x18, 0xbc, 0x05, 0x22, 0x06, 0x75, 0x5f, 0x70, 0x65, 0x65, 0x72,
|
||||
]);
|
||||
const sent = protobuf_decode<SendMessageRequest>(requests[1]!.body);
|
||||
expect(protobuf_decode<RoutingHead>(route)).toEqual(sent.routingHead);
|
||||
expect(sent.routingHead?.grpTmp).toEqual({ groupUin: 700n, toUid: 'u_peer' });
|
||||
expect(sent.messageBody?.richText?.elems).toEqual([
|
||||
expect.objectContaining({ text: expect.objectContaining({ str: 'reply' }) }),
|
||||
expect.objectContaining({
|
||||
commonElem: expect.objectContaining({ serviceType: 48, businessType: 10 }),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps normal private image uploads free of temp-session context', async () => {
|
||||
const { bridge, requests } = fixture();
|
||||
await bridge.apis.message.sendPrivate(1001, [{ type: 'image', url: png }]);
|
||||
const upload = protobuf_decode<OidbBase<NTV2UploadRichMediaReq>>(requests[0]!.body).body!;
|
||||
expect(upload.reqHead?.scene?.c2c?.routingHead ?? undefined).toBeUndefined();
|
||||
const sent = protobuf_decode<SendMessageRequest>(requests[1]!.body);
|
||||
expect(sent.routingHead?.grpTmp ?? undefined).toBeUndefined();
|
||||
});
|
||||
|
||||
it('propagates upload rejection without sending text or retrying as another conversation', async () => {
|
||||
const { bridge, requests } = fixture(true);
|
||||
await expect(
|
||||
bridge.apis.message.sendGroupTempMessage(1001, 700, [
|
||||
{ type: 'text', text: 'must not be sent separately' },
|
||||
{ type: 'image', url: png },
|
||||
]),
|
||||
).rejects.toThrow('verify identify fail');
|
||||
expect(requests.map((r) => r.command)).toEqual(['OidbSvcTrpcTcp.0x11c5_100']);
|
||||
});
|
||||
|
||||
it('rejects missing source context before reading the image or sending any packet', async () => {
|
||||
const { bridge, requests } = fixture();
|
||||
const fetch = vi
|
||||
.spyOn(globalThis, 'fetch')
|
||||
.mockRejectedValue(new Error('must not download image'));
|
||||
|
||||
try {
|
||||
await expect(
|
||||
buildSendElems(
|
||||
[{ type: 'image', url: 'https://must-not-fetch.invalid/image.png' }],
|
||||
{
|
||||
bridge,
|
||||
scene: 'group-temp',
|
||||
userUid: 'u_peer',
|
||||
},
|
||||
),
|
||||
).rejects.toThrow('temp-session image source group is missing');
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
expect(requests).toEqual([]);
|
||||
} finally {
|
||||
fetch.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user