Files
SnowLuma/packages/protocol/tests/oidb-services/group-file/finalize-offline-file.test.ts
T
TanTan fa9c73363d
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
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

159 lines
5.7 KiB
TypeScript

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 { OidbOfflineFileFinalizeResp } from '@snowluma/proto-defs/oidb-actions/media';
import type { FileExtra } from '@snowluma/proto-defs/message';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import { FinalizeOfflineFile } from '../../../src/oidb-services/group-file/finalize-offline-file';
import { env, m, s, v } from '../_pb-oracle';
// `bytes` fields are wire type 2 — identical framing to a sub-message, so the
// oracle's `m()` (tag + length + payload) doubles as the bytes emitter.
const b = m;
function makeDeps(resp?: OidbOfflineFileFinalizeResp) {
const responseData =
resp !== undefined
? Buffer.from(protobuf_encode<OidbBase<OidbOfflineFileFinalizeResp>>({ body: resp }))
: Buffer.alloc(0);
const r: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData,
};
return { sendRawPacket: vi.fn(async () => r) };
}
describe('FinalizeOfflineFile (0xE37_800)', () => {
it('declares 0xE37_800', () => {
expect(FinalizeOfflineFile.command).toBe(0xe37);
expect(FinalizeOfflineFile.subCommand).toBe(800);
});
it('serializes the request body byte-for-byte (oracle locks every tag)', async () => {
const deps = makeDeps({});
await FinalizeOfflineFile.invoke(deps, {
senderUid: 'su',
receiverUid: 'ru',
fileUuid: 'uuid',
fileHash: 'hh',
});
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
expect(wire).toBe('OidbSvcTrpcTcp.0xe37_800');
// body(10) { senderUid(10) receiverUid(20) fileUuid(30) fileHash(40) }
const innerBody = [...s(10, 'su'), ...s(20, 'ru'), ...s(30, 'uuid'), ...s(40, 'hh')];
// req: subCommand(1)=800, field2(2)=0 omitted, body(10), field101(101)=3,
// field102(102)=1, field200(200)=1
const req = [...v(1, 800), ...m(10, innerBody), ...v(101, 3), ...v(102, 1), ...v(200, 1)];
const expected = env(0xe37, 800, req, false);
expect(Buffer.from(bytes).toString('hex')).toBe(expected);
});
it('extracts the download-routing metadata from field30 of the response', async () => {
const deps = makeDeps({
body: {
metadata: {
field3: 5,
field100: new Uint8Array([9, 9]),
field101: new Uint8Array([8]),
field110: 7,
timestamp1: 42,
},
},
});
const meta = await FinalizeOfflineFile.invoke(deps, {
senderUid: 'su',
receiverUid: 'ru',
fileUuid: 'uuid',
fileHash: 'hh',
});
expect(meta.field3).toBe(5);
expect(meta.field110).toBe(7);
expect(meta.timestamp1).toBe(42);
expect(Array.from(meta.field100 ?? [])).toEqual([9, 9]);
expect(Array.from(meta.field101 ?? [])).toEqual([8]);
});
it('returns {} when the response carries no metadata', async () => {
const deps = makeDeps({});
const meta = await FinalizeOfflineFile.invoke(deps, {
senderUid: 'su',
receiverUid: 'ru',
fileUuid: 'uuid',
fileHash: 'hh',
});
expect(meta).toEqual({});
});
});
describe('FileExtra.field6 (c2c download routing — issue #157)', () => {
it('encodes field6.field2 with the exact NapCat tag layout', () => {
const extra: FileExtra = {
field6: {
field2: {
field1: 7,
fileUuid: 'uu',
fileName: 'n.bin',
field6: 3,
field7: new Uint8Array([1, 2]),
field8: new Uint8Array([3]),
timestamp1: 99,
fileHash: 'hh',
selfUid: 'su',
destUid: 'du',
},
},
};
const hex = Buffer.from(protobuf_encode<FileExtra>(extra)).toString('hex');
// PrivateFileExtraField2 in ascending tag order:
// field1(1) fileUuid(4) fileName(5) field6(6) field7(7,bytes)
// field8(8,bytes) timestamp1(9) fileHash(14) selfUid(15) destUid(16)
const f2 = [
...v(1, 7),
...s(4, 'uu'),
...s(5, 'n.bin'),
...v(6, 3),
...b(7, [1, 2]),
...b(8, [3]),
...v(9, 99),
...s(14, 'hh'),
...s(15, 'su'),
...s(16, 'du'),
];
// PrivateFileExtra { field2(2) } → FileExtra { field6(6) }
const expected = Buffer.from(m(6, m(2, f2))).toString('hex');
expect(hex).toBe(expected);
});
it('round-trips field6 through encode/decode', () => {
const extra: FileExtra = {
file: { fileUuid: 'u', fileName: 'f', subcmd: 1 },
field6: {
field2: {
field1: 1,
fileUuid: 'u',
fileName: 'f',
timestamp1: 5,
selfUid: 's',
destUid: 'd',
},
},
};
const back = protobuf_decode<FileExtra>(protobuf_encode<FileExtra>(extra));
expect(back.field6?.field2).toMatchObject({
field1: 1,
fileUuid: 'u',
fileName: 'f',
timestamp1: 5,
selfUid: 's',
destUid: 'd',
});
expect(back.file?.subcmd).toBe(1);
});
});