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,102 @@
|
||||
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 {
|
||||
OidbGroupFileFolderReq,
|
||||
OidbGroupFileFolderResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { CreateGroupFolder } from '../../../src/oidb-services/group-file/create-group-folder';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileFolderResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileFolderResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('CreateGroupFolder namespace', () => {
|
||||
it('declares 0x6D7_0 with uinForm=true', () => {
|
||||
expect(CreateGroupFolder.command).toBe(0x6d7);
|
||||
expect(CreateGroupFolder.subCommand).toBe(0);
|
||||
expect(CreateGroupFolder.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages create.{groupUin, rootDirectory, folderName}', async () => {
|
||||
const deps = makeDeps({ create: {} as any });
|
||||
await CreateGroupFolder.invoke(deps, { groupId: 12345, parentId: '/', folderName: 'docs' });
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileFolderReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.create).toMatchObject({
|
||||
groupUin: 12345,
|
||||
rootDirectory: '/',
|
||||
folderName: 'docs',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on retcode != 0 (note: lower-case retcode in this proto)', async () => {
|
||||
const deps = makeDeps({ create: { retcode: 7, retMsg: 'denied' } as any });
|
||||
await expect(
|
||||
CreateGroupFolder.invoke(deps, { groupId: 1, parentId: '/', folderName: 'f' }),
|
||||
).rejects.toThrow(/code=7/);
|
||||
});
|
||||
|
||||
it('returns the new folder info from the response folderInfo (#195)', async () => {
|
||||
const deps = makeDeps({
|
||||
create: {
|
||||
retcode: 0,
|
||||
folderInfo: {
|
||||
folderId: '/0f1e2d',
|
||||
folderPath: '/docs',
|
||||
folderName: 'docs',
|
||||
createTime: 100,
|
||||
modifyTime: 200,
|
||||
createUin: 111,
|
||||
modifyUin: 222,
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
const r = await CreateGroupFolder.invoke(deps, {
|
||||
groupId: 1,
|
||||
parentId: '/',
|
||||
folderName: 'docs',
|
||||
});
|
||||
expect(r).toEqual({
|
||||
folderId: '/0f1e2d',
|
||||
folderName: 'docs',
|
||||
folderPath: '/docs',
|
||||
createTime: 100,
|
||||
modifyTime: 200,
|
||||
createUin: 111,
|
||||
modifyUin: 222,
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults every field when the response omits folderInfo (#195)', async () => {
|
||||
const deps = makeDeps({ create: { retcode: 0 } as any });
|
||||
const r = await CreateGroupFolder.invoke(deps, {
|
||||
groupId: 1,
|
||||
parentId: '/',
|
||||
folderName: 'f',
|
||||
});
|
||||
expect(r).toEqual({
|
||||
folderId: '',
|
||||
folderName: '',
|
||||
folderPath: '',
|
||||
createTime: 0,
|
||||
modifyTime: 0,
|
||||
createUin: 0,
|
||||
modifyUin: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
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 {
|
||||
OidbGroupFileReq,
|
||||
OidbGroupFileResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { DeleteGroupFile } from '../../../src/oidb-services/group-file/delete-group-file';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('DeleteGroupFile namespace', () => {
|
||||
it('declares 0x6D6_3 with uinForm=true', () => {
|
||||
expect(DeleteGroupFile.command).toBe(0x6d6);
|
||||
expect(DeleteGroupFile.subCommand).toBe(3);
|
||||
expect(DeleteGroupFile.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages busId=102 and routes to 0x6d6_3', async () => {
|
||||
const deps = makeDeps({ delete: {} as any });
|
||||
await DeleteGroupFile.invoke(deps, { groupId: 12345, fileId: 'fid' });
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d6_3');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileReq>>(bytes);
|
||||
expect(env.body?.delete).toMatchObject({ groupUin: 12345, busId: 102, fileId: 'fid' });
|
||||
});
|
||||
|
||||
it('throws on missing delete sub-message', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(DeleteGroupFile.invoke(deps, { groupId: 1, fileId: 'f' })).rejects.toThrow(
|
||||
/delete response missing/,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws on non-zero retCode', async () => {
|
||||
const deps = makeDeps({ delete: { retCode: 5, retMsg: 'no perms' } as any });
|
||||
await expect(DeleteGroupFile.invoke(deps, { groupId: 1, fileId: 'f' })).rejects.toThrow(
|
||||
/code=5/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
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 {
|
||||
OidbGroupFileFolderReq,
|
||||
OidbGroupFileFolderResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { DeleteGroupFolder } from '../../../src/oidb-services/group-file/delete-group-folder';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileFolderResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileFolderResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('DeleteGroupFolder namespace', () => {
|
||||
it('declares 0x6D7_1 with uinForm=true', () => {
|
||||
expect(DeleteGroupFolder.command).toBe(0x6d7);
|
||||
expect(DeleteGroupFolder.subCommand).toBe(1);
|
||||
expect(DeleteGroupFolder.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages delete.{groupUin, folderId}', async () => {
|
||||
const deps = makeDeps({ delete: {} as any });
|
||||
await DeleteGroupFolder.invoke(deps, { groupId: 12345, folderId: 'fid' });
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileFolderReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.delete).toMatchObject({ groupUin: 12345, folderId: 'fid' });
|
||||
});
|
||||
|
||||
it('throws on missing delete sub-message', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(DeleteGroupFolder.invoke(deps, { groupId: 1, folderId: 'f' })).rejects.toThrow(
|
||||
/folder delete response missing/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
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 {
|
||||
OidbGroupFileCountViewReq,
|
||||
OidbGroupFileCountViewResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetGroupFileCount } from '../../../src/oidb-services/group-file/get-group-file-count';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileCountViewResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileCountViewResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetGroupFileCount namespace', () => {
|
||||
it('declares 0x6D8_2 (Count subcommand)', () => {
|
||||
expect(GetGroupFileCount.command).toBe(0x6d8);
|
||||
expect(GetGroupFileCount.subCommand).toBe(2);
|
||||
});
|
||||
|
||||
it('routes to 0x6d8_2 with groupUin + appId=7 + busId=6 (#196)', async () => {
|
||||
const deps = makeDeps({ count: { fileCount: 1, maxCount: 100 } });
|
||||
await GetGroupFileCount.invoke(deps, { groupId: 12345 });
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d8_2');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileCountViewReq>>(bytes);
|
||||
expect(env.body?.count).toMatchObject({ groupUin: 12345, appId: 7, busId: 6 });
|
||||
});
|
||||
|
||||
it('decodes fileCount (field 4) + maxCount (field 6) as plain numbers', async () => {
|
||||
const deps = makeDeps({ count: { fileCount: 42, maxCount: 1000 } });
|
||||
const out = await GetGroupFileCount.invoke(deps, { groupId: 1 });
|
||||
expect(out).toEqual({ fileCount: 42, maxCount: 1000 });
|
||||
});
|
||||
|
||||
it('falls back to maxCount=10000 when the server elides it', async () => {
|
||||
const deps = makeDeps({ count: {} });
|
||||
const out = await GetGroupFileCount.invoke(deps, { groupId: 1 });
|
||||
expect(out).toEqual({ fileCount: 0, maxCount: 10000 });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
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 {
|
||||
OidbGroupFileCountViewReq,
|
||||
OidbGroupFileCountViewResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetGroupFileSpace } from '../../../src/oidb-services/group-file/get-group-file-space';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileCountViewResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileCountViewResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetGroupFileSpace namespace (#196)', () => {
|
||||
it('declares 0x6D8_3 (Space subcommand)', () => {
|
||||
expect(GetGroupFileSpace.command).toBe(0x6d8);
|
||||
expect(GetGroupFileSpace.subCommand).toBe(3);
|
||||
});
|
||||
|
||||
it('routes to 0x6d8_3 with a space request (groupUin + appId=7)', async () => {
|
||||
const deps = makeDeps({ space: {} });
|
||||
await GetGroupFileSpace.invoke(deps, { groupId: 12345 });
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d8_3');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileCountViewReq>>(bytes);
|
||||
expect(env.body?.space).toMatchObject({ groupUin: 12345, appId: 7 });
|
||||
expect(env.body?.count).toBeNull();
|
||||
});
|
||||
|
||||
it('decodes usedSpace (field 5) + totalSpace (field 4) as plain numbers', async () => {
|
||||
const deps = makeDeps({ space: { usedSpace: 20103729n, totalSpace: 10737418240n } });
|
||||
const out = await GetGroupFileSpace.invoke(deps, { groupId: 1 });
|
||||
expect(out).toEqual({ usedSpace: 20103729, totalSpace: 10737418240 });
|
||||
});
|
||||
|
||||
it('defaults to 0 when the server elides the space block', async () => {
|
||||
const deps = makeDeps({ space: {} });
|
||||
const out = await GetGroupFileSpace.invoke(deps, { groupId: 1 });
|
||||
expect(out).toEqual({ usedSpace: 0, totalSpace: 0 });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
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 {
|
||||
OidbGroupFileReq,
|
||||
OidbGroupFileResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetGroupFileUrl } from '../../../src/oidb-services/group-file/get-group-file-url';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetGroupFileUrl namespace', () => {
|
||||
it('declares 0x6D6_2 with uinForm=true', () => {
|
||||
expect(GetGroupFileUrl.command).toBe(0x6d6);
|
||||
expect(GetGroupFileUrl.subCommand).toBe(2);
|
||||
expect(GetGroupFileUrl.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages the download slot with the supplied busId', async () => {
|
||||
const deps = makeDeps({
|
||||
download: { downloadDns: 'cdn', downloadUrl: new Uint8Array([1, 2]) } as any,
|
||||
});
|
||||
await GetGroupFileUrl.invoke(deps, { groupId: 12345, fileId: 'fid', busId: 102 });
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d6_2');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileReq>>(bytes);
|
||||
expect(env.body?.download).toMatchObject({
|
||||
groupUin: 12345,
|
||||
appId: 7,
|
||||
busId: 102,
|
||||
fileId: 'fid',
|
||||
});
|
||||
});
|
||||
|
||||
it('composes the https ftn_handler URL and checks download.retCode', async () => {
|
||||
const deps = makeDeps({
|
||||
download: { downloadDns: 'cdn', downloadUrl: new Uint8Array([0x01, 0x02]) } as any,
|
||||
});
|
||||
await expect(
|
||||
GetGroupFileUrl.invoke(deps, { groupId: 1, fileId: 'fid-xyz', busId: 102 }),
|
||||
).resolves.toBe('https://cdn/ftn_handler/0102/?fname=fid-xyz');
|
||||
});
|
||||
|
||||
it('throws when download.retCode is non-zero', async () => {
|
||||
const deps = makeDeps({
|
||||
download: {
|
||||
retCode: 1,
|
||||
retMsg: 'denied',
|
||||
downloadDns: 'cdn',
|
||||
downloadUrl: new Uint8Array([1]),
|
||||
} as any,
|
||||
});
|
||||
await expect(
|
||||
GetGroupFileUrl.invoke(deps, { groupId: 1, fileId: 'f', busId: 102 }),
|
||||
).rejects.toThrow(/group file url failed: code=1/);
|
||||
});
|
||||
|
||||
it('throws when the download sub-message is missing', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(
|
||||
GetGroupFileUrl.invoke(deps, { groupId: 1, fileId: 'f', busId: 102 }),
|
||||
).rejects.toThrow(/url response missing/);
|
||||
});
|
||||
|
||||
it('falls back to downloadIp when downloadDns is empty', async () => {
|
||||
const deps = makeDeps({
|
||||
download: { downloadIp: '1.2.3.4', downloadUrl: new Uint8Array([0xab]) } as any,
|
||||
});
|
||||
await expect(
|
||||
GetGroupFileUrl.invoke(deps, { groupId: 1, fileId: 'fid', busId: 102 }),
|
||||
).resolves.toBe('https://1.2.3.4/ftn_handler/AB/?fname=fid');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
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 { NTV2RichMediaReq, NTV2RichMediaResp } from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetGroupPttUrl } from '../../../src/oidb-services/group-file/get-group-ptt-url';
|
||||
|
||||
function makeDeps(body?: NTV2RichMediaResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<NTV2RichMediaResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetGroupPttUrl namespace', () => {
|
||||
it('declares 0x126E_200 with uinForm=true', () => {
|
||||
expect(GetGroupPttUrl.command).toBe(0x126e);
|
||||
expect(GetGroupPttUrl.subCommand).toBe(200);
|
||||
expect(GetGroupPttUrl.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('emits scene{requestType=1, businessType=3, sceneType=2, group.groupUin}', async () => {
|
||||
const deps = makeDeps({
|
||||
respHead: { retCode: 0 },
|
||||
download: { info: { domain: 'd', urlPath: '/p' }, rKeyParam: '?rk' },
|
||||
} as any);
|
||||
await GetGroupPttUrl.invoke(deps, { groupId: 12345, node: { fileUuid: 'uuid' } });
|
||||
const env = protobuf_decode<OidbBase<NTV2RichMediaReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.reqHead?.scene).toMatchObject({
|
||||
requestType: 1,
|
||||
businessType: 3,
|
||||
sceneType: 2,
|
||||
group: { groupUin: 12345 },
|
||||
});
|
||||
});
|
||||
|
||||
it('composes the https://domain<path><rkey> URL', async () => {
|
||||
const deps = makeDeps({
|
||||
respHead: { retCode: 0 },
|
||||
download: { info: { domain: 'cdn.qq', urlPath: '/x' }, rKeyParam: '?r=1' },
|
||||
} as any);
|
||||
const out = await GetGroupPttUrl.invoke(deps, { groupId: 1, node: { fileUuid: 'uuid' } });
|
||||
expect(out).toBe('https://cdn.qq/x?r=1');
|
||||
});
|
||||
|
||||
it('throws when fileUuid is missing on the node', async () => {
|
||||
const deps = makeDeps({} as any);
|
||||
await expect(GetGroupPttUrl.invoke(deps, { groupId: 1, node: {} })).rejects.toThrow(
|
||||
/fileUuid is required/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
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 { NTV2RichMediaReq, NTV2RichMediaResp } from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetGroupVideoUrl } from '../../../src/oidb-services/group-file/get-group-video-url';
|
||||
|
||||
function makeDeps(body?: NTV2RichMediaResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<NTV2RichMediaResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetGroupVideoUrl namespace', () => {
|
||||
it('declares 0x11EA_200 with uinForm=true', () => {
|
||||
expect(GetGroupVideoUrl.command).toBe(0x11ea);
|
||||
expect(GetGroupVideoUrl.subCommand).toBe(200);
|
||||
expect(GetGroupVideoUrl.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('emits scene{requestType=2, businessType=2, sceneType=2, group}', async () => {
|
||||
const deps = makeDeps({
|
||||
respHead: { retCode: 0 },
|
||||
download: { info: { domain: 'd', urlPath: '/v' }, rKeyParam: '' },
|
||||
} as any);
|
||||
await GetGroupVideoUrl.invoke(deps, { groupId: 12345, node: { fileUuid: 'uuid' } });
|
||||
const env = protobuf_decode<OidbBase<NTV2RichMediaReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.reqHead?.scene).toMatchObject({
|
||||
requestType: 2,
|
||||
businessType: 2,
|
||||
sceneType: 2,
|
||||
group: { groupUin: 12345 },
|
||||
});
|
||||
});
|
||||
|
||||
it('bubbles up retCode != 0 via the shared ntv2 deserializer', async () => {
|
||||
const deps = makeDeps({
|
||||
respHead: { retCode: 99, message: 'gone' },
|
||||
download: { info: { domain: 'd', urlPath: '/p' }, rKeyParam: '' },
|
||||
} as any);
|
||||
await expect(
|
||||
GetGroupVideoUrl.invoke(deps, { groupId: 1, node: { fileUuid: 'u' } }),
|
||||
).rejects.toThrow(/code=99/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
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 {
|
||||
OidbPrivateFileDownloadReq,
|
||||
OidbPrivateFileDownloadResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetPrivateFileUrl } from '../../../src/oidb-services/group-file/get-private-file-url';
|
||||
|
||||
function makeDeps(body?: OidbPrivateFileDownloadResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbPrivateFileDownloadResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetPrivateFileUrl namespace', () => {
|
||||
it('declares 0xE37_1200', () => {
|
||||
expect(GetPrivateFileUrl.command).toBe(0xe37);
|
||||
expect(GetPrivateFileUrl.subCommand).toBe(1200);
|
||||
});
|
||||
|
||||
it('packages the magic field99999 cookie + body fields', async () => {
|
||||
const deps = makeDeps({
|
||||
body: { result: { server: 'host', port: 80, url: '/u' } } as any,
|
||||
});
|
||||
await GetPrivateFileUrl.invoke(deps, { selfUid: 'self', fileId: 'fid', fileHash: 'h' });
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0xe37_1200');
|
||||
const env = protobuf_decode<OidbBase<OidbPrivateFileDownloadReq>>(bytes);
|
||||
expect(env.body).toMatchObject({
|
||||
subCommand: 1200,
|
||||
field2: 1,
|
||||
field101: 3,
|
||||
field102: 103,
|
||||
field200: 1,
|
||||
});
|
||||
expect(env.body?.body).toMatchObject({
|
||||
receiverUid: 'self',
|
||||
fileUuid: 'fid',
|
||||
type: 2,
|
||||
fileHash: 'h',
|
||||
});
|
||||
expect(env.body?.field99999).toEqual(new Uint8Array([0xc0, 0x85, 0x2c, 0x01]));
|
||||
});
|
||||
|
||||
it('composes the http download URL', async () => {
|
||||
const deps = makeDeps({
|
||||
body: { result: { server: 'srv', port: 8080, url: '/path' } } as any,
|
||||
});
|
||||
await expect(
|
||||
GetPrivateFileUrl.invoke(deps, { selfUid: 's', fileId: 'f', fileHash: 'h' }),
|
||||
).resolves.toBe('http://srv:8080/path&isthumb=0');
|
||||
});
|
||||
|
||||
it('throws when result is missing', async () => {
|
||||
const deps = makeDeps({ body: {} });
|
||||
await expect(
|
||||
GetPrivateFileUrl.invoke(deps, { selfUid: 's', fileId: 'f', fileHash: 'h' }),
|
||||
).rejects.toThrow(/private file url response invalid/);
|
||||
});
|
||||
|
||||
it('throws when server/port/url is incomplete', async () => {
|
||||
const deps = makeDeps({
|
||||
body: { result: { server: 'srv', port: 0, url: '/path' } } as any,
|
||||
});
|
||||
await expect(
|
||||
GetPrivateFileUrl.invoke(deps, { selfUid: 's', fileId: 'f', fileHash: 'h' }),
|
||||
).rejects.toThrow(/private file url response invalid/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
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 { NTV2RichMediaReq, NTV2RichMediaResp } from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetPrivatePttUrl } from '../../../src/oidb-services/group-file/get-private-ptt-url';
|
||||
|
||||
function makeDeps(body?: NTV2RichMediaResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<NTV2RichMediaResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetPrivatePttUrl namespace', () => {
|
||||
it('declares 0x126D_200 with uinForm=true', () => {
|
||||
expect(GetPrivatePttUrl.command).toBe(0x126d);
|
||||
expect(GetPrivatePttUrl.subCommand).toBe(200);
|
||||
expect(GetPrivatePttUrl.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('emits c2c scene with self uid as targetUid', async () => {
|
||||
const deps = makeDeps({
|
||||
respHead: { retCode: 0 },
|
||||
download: { info: { domain: 'd', urlPath: '/p' }, rKeyParam: '' },
|
||||
} as any);
|
||||
await GetPrivatePttUrl.invoke(deps, { selfUid: 'self', node: { fileUuid: 'uuid' } });
|
||||
const env = protobuf_decode<OidbBase<NTV2RichMediaReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.reqHead?.scene).toMatchObject({
|
||||
requestType: 1,
|
||||
businessType: 3,
|
||||
sceneType: 1,
|
||||
c2c: { accountType: 2, targetUid: 'self' },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
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 { NTV2RichMediaReq, NTV2RichMediaResp } from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { GetPrivateVideoUrl } from '../../../src/oidb-services/group-file/get-private-video-url';
|
||||
|
||||
function makeDeps(body?: NTV2RichMediaResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<NTV2RichMediaResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('GetPrivateVideoUrl namespace', () => {
|
||||
it('declares 0x11E9_200 with uinForm=true', () => {
|
||||
expect(GetPrivateVideoUrl.command).toBe(0x11e9);
|
||||
expect(GetPrivateVideoUrl.subCommand).toBe(200);
|
||||
expect(GetPrivateVideoUrl.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('emits c2c scene with self uid as targetUid', async () => {
|
||||
const deps = makeDeps({
|
||||
respHead: { retCode: 0 },
|
||||
download: { info: { domain: 'd', urlPath: '/v' }, rKeyParam: '' },
|
||||
} as any);
|
||||
await GetPrivateVideoUrl.invoke(deps, { selfUid: 'self', node: { fileUuid: 'uuid' } });
|
||||
const env = protobuf_decode<OidbBase<NTV2RichMediaReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.reqHead?.scene).toMatchObject({
|
||||
requestType: 2,
|
||||
businessType: 2,
|
||||
sceneType: 1,
|
||||
c2c: { accountType: 2, targetUid: 'self' },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
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 {
|
||||
OidbGroupFileViewReq,
|
||||
OidbGroupFileViewResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { ListGroupFilesPage } from '../../../src/oidb-services/group-file/list-group-files-page';
|
||||
|
||||
function wireVarint(value: bigint): number[] {
|
||||
const out: number[] = [];
|
||||
let remaining = value;
|
||||
|
||||
do {
|
||||
const byte = Number(remaining & 0x7fn);
|
||||
remaining >>= 7n;
|
||||
out.push(remaining === 0n ? byte : byte | 0x80);
|
||||
} while (remaining !== 0n);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function varintField(fieldNumber: number, value: bigint): number[] {
|
||||
return [...wireVarint(BigInt(fieldNumber << 3)), ...wireVarint(value)];
|
||||
}
|
||||
|
||||
function bytesField(fieldNumber: number, value: Uint8Array): number[] {
|
||||
return [
|
||||
...wireVarint(BigInt((fieldNumber << 3) | 2)),
|
||||
...wireVarint(BigInt(value.length)),
|
||||
...value,
|
||||
];
|
||||
}
|
||||
|
||||
function stringField(fieldNumber: number, value: string): number[] {
|
||||
return bytesField(fieldNumber, new TextEncoder().encode(value));
|
||||
}
|
||||
|
||||
function messageField(fieldNumber: number, value: number[]): number[] {
|
||||
return bytesField(fieldNumber, Uint8Array.from(value));
|
||||
}
|
||||
|
||||
function makeDeps(body?: OidbGroupFileViewResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileViewResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('ListGroupFilesPage namespace', () => {
|
||||
it('declares 0x6D8_1 with uinForm=true', () => {
|
||||
expect(ListGroupFilesPage.command).toBe(0x6d8);
|
||||
expect(ListGroupFilesPage.subCommand).toBe(1);
|
||||
expect(ListGroupFilesPage.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages the page parameters (sortBy=1, field17=2, field18=0)', async () => {
|
||||
const deps = makeDeps({ list: { isEnd: true, items: [] } as any });
|
||||
await ListGroupFilesPage.invoke(deps, {
|
||||
groupId: 12345,
|
||||
targetDirectory: '/a',
|
||||
startIndex: 20,
|
||||
pageSize: 50,
|
||||
});
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileViewReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.list).toMatchObject({
|
||||
groupUin: 12345,
|
||||
appId: 7,
|
||||
targetDirectory: '/a',
|
||||
fileCount: 50,
|
||||
sortBy: 1,
|
||||
startIndex: 20,
|
||||
field17: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns items and isEnd after checking list.retCode', async () => {
|
||||
const deps = makeDeps({
|
||||
list: { isEnd: false, items: [{ type: 1 }] } as any,
|
||||
});
|
||||
const out = await ListGroupFilesPage.invoke(deps, {
|
||||
groupId: 1,
|
||||
targetDirectory: '/',
|
||||
startIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
expect(out).toEqual({ items: [expect.objectContaining({ type: 1 })], isEnd: false });
|
||||
});
|
||||
|
||||
it('throws when list.retCode is non-zero', async () => {
|
||||
const deps = makeDeps({
|
||||
list: { retCode: 2, retMsg: 'busy', isEnd: true, items: [] } as any,
|
||||
});
|
||||
await expect(
|
||||
ListGroupFilesPage.invoke(deps, {
|
||||
groupId: 1,
|
||||
targetDirectory: '/',
|
||||
startIndex: 0,
|
||||
pageSize: 10,
|
||||
}),
|
||||
).rejects.toThrow(/group file list failed: code=2/);
|
||||
});
|
||||
|
||||
it('decodes the folder last-upload metadata from the QQ wire fields', async () => {
|
||||
// Independent wire fixture confirmed against QQ's group-file list decoder:
|
||||
// folder fields 5/9/10 are modify time, modifier UIN, and modifier name.
|
||||
const folder = [
|
||||
...stringField(1, 'd1'),
|
||||
...stringField(3, 'dir'),
|
||||
...varintField(4, 100n),
|
||||
...varintField(5, 200n),
|
||||
...varintField(6, 123n),
|
||||
...stringField(7, 'creator'),
|
||||
...varintField(8, 2n),
|
||||
...varintField(9, 5_000_000_001n),
|
||||
...stringField(10, 'uploader'),
|
||||
];
|
||||
const item = [...varintField(1, 2n), ...messageField(2, folder)];
|
||||
const list = [...varintField(4, 1n), ...messageField(5, item)];
|
||||
const body = messageField(2, list);
|
||||
const responseData = Buffer.from(messageField(4, body));
|
||||
const deps = {
|
||||
sendRawPacket: vi.fn(async (): Promise<SendPacketResult> => ({
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
})),
|
||||
};
|
||||
|
||||
const out = await ListGroupFilesPage.invoke(deps, {
|
||||
groupId: 1,
|
||||
targetDirectory: '/',
|
||||
startIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const decoded = out?.items?.[0]?.folderInfo as Record<string, unknown> | undefined;
|
||||
|
||||
expect(decoded).toMatchObject({
|
||||
modifiedTime: 200,
|
||||
modifierUin: 5_000_000_001n,
|
||||
modifierName: 'uploader',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null when the server elides the list slot', async () => {
|
||||
const deps = makeDeps({});
|
||||
const out = await ListGroupFilesPage.invoke(deps, {
|
||||
groupId: 1,
|
||||
targetDirectory: '/',
|
||||
startIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
expect(out).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
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 {
|
||||
OidbGroupFileReq,
|
||||
OidbGroupFileResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { MoveGroupFile } from '../../../src/oidb-services/group-file/move-group-file';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('MoveGroupFile namespace', () => {
|
||||
it('declares 0x6D6_5 with uinForm=true', () => {
|
||||
expect(MoveGroupFile.command).toBe(0x6d6);
|
||||
expect(MoveGroupFile.subCommand).toBe(5);
|
||||
expect(MoveGroupFile.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages parent + target directories under the move slot', async () => {
|
||||
const deps = makeDeps({ move: {} as any });
|
||||
await MoveGroupFile.invoke(deps, {
|
||||
groupId: 12345,
|
||||
fileId: 'fid',
|
||||
parentDirectory: '/a',
|
||||
targetDirectory: '/b',
|
||||
});
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.move).toMatchObject({
|
||||
groupUin: 12345,
|
||||
appId: 7,
|
||||
busId: 102,
|
||||
fileId: 'fid',
|
||||
parentDirectory: '/a',
|
||||
targetDirectory: '/b',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on missing move sub-message', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(
|
||||
MoveGroupFile.invoke(deps, {
|
||||
groupId: 1,
|
||||
fileId: 'f',
|
||||
parentDirectory: '/',
|
||||
targetDirectory: '/',
|
||||
}),
|
||||
).rejects.toThrow(/move response missing/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { protobuf_decode } from '@snowluma/proton';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type { OidbGroupSendFileReq } from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { PublishGroupFile } from '../../../src/oidb-services/group-file/publish-group-file';
|
||||
|
||||
function makeDeps() {
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData: Buffer.alloc(0),
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('PublishGroupFile namespace', () => {
|
||||
it('declares 0x6D9_4', () => {
|
||||
expect(PublishGroupFile.command).toBe(0x6d9);
|
||||
expect(PublishGroupFile.subCommand).toBe(4);
|
||||
});
|
||||
|
||||
it('routes to 0x6d9_4 with busiType=102, type=2, field5=true', async () => {
|
||||
const deps = makeDeps();
|
||||
await PublishGroupFile.invoke(deps, { groupId: 12345, fileId: 'fid' });
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d9_4');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupSendFileReq>>(bytes);
|
||||
expect(env.body?.body).toMatchObject({
|
||||
groupUin: 12345,
|
||||
type: 2,
|
||||
info: expect.objectContaining({ busiType: 102, fileId: 'fid', field5: true }),
|
||||
});
|
||||
});
|
||||
|
||||
it('emits a non-zero `info.field3` random discriminator', async () => {
|
||||
// Lagrange-V2 sets this to `Random.Shared.Next()`; our serialize uses
|
||||
// Math.random() — both produce a 31-bit unsigned int, never 0.
|
||||
const deps = makeDeps();
|
||||
await PublishGroupFile.invoke(deps, { groupId: 1, fileId: 'f' });
|
||||
const env = protobuf_decode<OidbBase<OidbGroupSendFileReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
const field3 = env.body?.body?.info?.field3 ?? 0;
|
||||
expect(typeof field3).toBe('number');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
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 {
|
||||
OidbGroupFileReq,
|
||||
OidbGroupFileResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { RenameGroupFile } from '../../../src/oidb-services/group-file/rename-group-file';
|
||||
|
||||
// A success response the real server would send: command/sub echoed, rename
|
||||
// ret slot with retCode 0. Lets `invoke`'s deserialize pass so we can assert
|
||||
// the request wire (cmd name + body) without it throwing on an empty body.
|
||||
function okRenameResponse(): Buffer {
|
||||
const env: OidbBase<OidbGroupFileResp> = {
|
||||
command: 0x6d6,
|
||||
subCommand: 4,
|
||||
body: { rename: { retCode: 0 } },
|
||||
};
|
||||
return Buffer.from(protobuf_encode<OidbBase<OidbGroupFileResp>>(env));
|
||||
}
|
||||
|
||||
function makeSender() {
|
||||
const defaultResp: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData: okRenameResponse(),
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => defaultResp) };
|
||||
}
|
||||
|
||||
describe('RenameGroupFile namespace', () => {
|
||||
it('declares command 0x6D6 sub 4 (rename slot), uin form', () => {
|
||||
expect(RenameGroupFile.command).toBe(0x6d6);
|
||||
expect(RenameGroupFile.subCommand).toBe(4);
|
||||
expect(RenameGroupFile.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('serializes into the rename slot with busId 102 and the current parent folder', () => {
|
||||
const out = RenameGroupFile.serialize({} as any, {
|
||||
groupId: 12345,
|
||||
fileId: '/abc',
|
||||
parentDirectory: '/sub',
|
||||
newFileName: 'new.txt',
|
||||
});
|
||||
expect(out).toEqual({
|
||||
rename: {
|
||||
groupUin: 12345,
|
||||
busId: 102,
|
||||
fileId: '/abc',
|
||||
parentFolder: '/sub',
|
||||
newFileName: 'new.txt',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('round-trips the rename body through the OIDB envelope (locks the pb field tags)', async () => {
|
||||
const sender = makeSender();
|
||||
await RenameGroupFile.invoke(sender, {
|
||||
groupId: 12345,
|
||||
fileId: '/abc',
|
||||
parentDirectory: '/',
|
||||
newFileName: 'new.txt',
|
||||
});
|
||||
expect(sender.sendRawPacket).toHaveBeenCalledOnce();
|
||||
const [cmd, bytes] = sender.sendRawPacket.mock.calls[0]!;
|
||||
expect(cmd).toBe('OidbSvcTrpcTcp.0x6d6_4');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileReq>>(bytes);
|
||||
expect(env.command).toBe(0x6d6);
|
||||
expect(env.subCommand).toBe(4);
|
||||
expect(env.body.rename).toMatchObject({
|
||||
groupUin: 12345,
|
||||
busId: 102,
|
||||
fileId: '/abc',
|
||||
parentFolder: '/',
|
||||
newFileName: 'new.txt',
|
||||
});
|
||||
});
|
||||
|
||||
it('deserialize throws when the rename slot is missing from the response', () => {
|
||||
expect(() => RenameGroupFile.deserialize({} as any, {})).toThrow(/rename response missing/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
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 {
|
||||
OidbGroupFileFolderReq,
|
||||
OidbGroupFileFolderResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { RenameGroupFolder } from '../../../src/oidb-services/group-file/rename-group-folder';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileFolderResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileFolderResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('RenameGroupFolder namespace', () => {
|
||||
it('declares 0x6D7_2 with uinForm=true', () => {
|
||||
expect(RenameGroupFolder.command).toBe(0x6d7);
|
||||
expect(RenameGroupFolder.subCommand).toBe(2);
|
||||
expect(RenameGroupFolder.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages rename.{groupUin, folderId, newFolderName}', async () => {
|
||||
const deps = makeDeps({ rename: {} as any });
|
||||
await RenameGroupFolder.invoke(deps, {
|
||||
groupId: 12345,
|
||||
folderId: 'fid',
|
||||
newFolderName: 'newname',
|
||||
});
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileFolderReq>>(
|
||||
deps.sendRawPacket.mock.calls[0]![1],
|
||||
);
|
||||
expect(env.body?.rename).toMatchObject({
|
||||
groupUin: 12345,
|
||||
folderId: 'fid',
|
||||
newFolderName: 'newname',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on missing rename sub-message', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(
|
||||
RenameGroupFolder.invoke(deps, {
|
||||
groupId: 1,
|
||||
folderId: 'f',
|
||||
newFolderName: 'n',
|
||||
}),
|
||||
).rejects.toThrow(/folder rename response missing/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
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 {
|
||||
OidbGroupSendFileReq,
|
||||
OidbGroupSendFileResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { TransGroupFile } from '../../../src/oidb-services/group-file/trans-group-file';
|
||||
|
||||
function makeDeps(body?: OidbGroupSendFileResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupSendFileResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('TransGroupFile namespace', () => {
|
||||
it('declares 0x6D9_0', () => {
|
||||
expect(TransGroupFile.command).toBe(0x6d9);
|
||||
expect(TransGroupFile.subCommand).toBe(0);
|
||||
});
|
||||
|
||||
it('packages group + file id under the transFile slot', async () => {
|
||||
const deps = makeDeps({ transFile: { saveBusId: 102, saveFilePath: '/abc' } });
|
||||
const out = await TransGroupFile.invoke(deps, { groupId: 12345, fileId: 'fid' });
|
||||
expect(out).toEqual({ saveBusId: 102, saveFilePath: '/abc' });
|
||||
expect(deps.sendRawPacket).toHaveBeenCalledOnce();
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d9_0');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupSendFileReq>>(bytes);
|
||||
expect(env.body?.transFile).toEqual({
|
||||
groupUin: 12345n,
|
||||
appId: 7,
|
||||
busId: 102,
|
||||
fileId: 'fid',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on missing transFile sub-message', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(TransGroupFile.invoke(deps, { groupId: 1, fileId: 'f' })).rejects.toThrow(
|
||||
/transfer response missing/,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws on non-zero retCode', async () => {
|
||||
const deps = makeDeps({ transFile: { retCode: 7, retMsg: 'denied' } });
|
||||
await expect(TransGroupFile.invoke(deps, { groupId: 1, fileId: 'f' })).rejects.toThrow(
|
||||
/code=7/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
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 {
|
||||
OidbGroupFileReq,
|
||||
OidbGroupFileResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-file';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { UploadGroupFileRequest } from '../../../src/oidb-services/group-file/upload-group-file-request';
|
||||
|
||||
function makeDeps(body?: OidbGroupFileResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbGroupFileResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('UploadGroupFileRequest namespace', () => {
|
||||
it('declares 0x6D6_0 with uinForm=true', () => {
|
||||
expect(UploadGroupFileRequest.command).toBe(0x6d6);
|
||||
expect(UploadGroupFileRequest.subCommand).toBe(0);
|
||||
expect(UploadGroupFileRequest.uinForm).toBe(true);
|
||||
});
|
||||
|
||||
it('packages the file pre-flight slot (entrance=6, appId=4, busId=102)', async () => {
|
||||
const deps = makeDeps({ upload: { fileId: 'fid', boolFileExist: true } as any });
|
||||
await UploadGroupFileRequest.invoke(deps, {
|
||||
groupId: 12345,
|
||||
fileName: 'doc.pdf',
|
||||
folderId: '/',
|
||||
fileSize: 1024,
|
||||
fileSha1: new Uint8Array(20),
|
||||
fileMd5: new Uint8Array(16),
|
||||
});
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0x6d6_0');
|
||||
const env = protobuf_decode<OidbBase<OidbGroupFileReq>>(bytes);
|
||||
expect(env.reserved).toBe(1); // uinForm
|
||||
expect(env.body?.file).toMatchObject({
|
||||
groupUin: 12345,
|
||||
appId: 4,
|
||||
busId: 102,
|
||||
entrance: 6,
|
||||
targetDirectory: '/',
|
||||
fileName: 'doc.pdf',
|
||||
localDirectory: '/doc.pdf',
|
||||
fileSize: 1024n,
|
||||
field15: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the upload-info body for the facade orchestration', async () => {
|
||||
const deps = makeDeps({ upload: { fileId: 'fid-X', boolFileExist: true } as any });
|
||||
const out = await UploadGroupFileRequest.invoke(deps, {
|
||||
groupId: 1,
|
||||
fileName: 'f',
|
||||
folderId: '/',
|
||||
fileSize: 0,
|
||||
fileSha1: new Uint8Array(0),
|
||||
fileMd5: new Uint8Array(0),
|
||||
});
|
||||
expect(out.fileId).toBe('fid-X');
|
||||
expect(out.boolFileExist).toBe(true);
|
||||
});
|
||||
|
||||
it('throws when the upload sub-message is missing', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(
|
||||
UploadGroupFileRequest.invoke(deps, {
|
||||
groupId: 1,
|
||||
fileName: 'f',
|
||||
folderId: '/',
|
||||
fileSize: 0,
|
||||
fileSha1: new Uint8Array(0),
|
||||
fileMd5: new Uint8Array(0),
|
||||
}),
|
||||
).rejects.toThrow(/upload response missing/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
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 {
|
||||
OidbPrivateFileUploadReq,
|
||||
OidbPrivateFileUploadResp,
|
||||
} from '@snowluma/proto-defs/oidb-actions/media';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
|
||||
import { UploadPrivateFileRequest } from '../../../src/oidb-services/group-file/upload-private-file-request';
|
||||
|
||||
function makeDeps(body?: OidbPrivateFileUploadResp) {
|
||||
const responseData =
|
||||
body !== undefined
|
||||
? Buffer.from(protobuf_encode<OidbBase<OidbPrivateFileUploadResp>>({ body }))
|
||||
: Buffer.alloc(0);
|
||||
const r: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData,
|
||||
};
|
||||
return { sendRawPacket: vi.fn(async () => r) };
|
||||
}
|
||||
|
||||
describe('UploadPrivateFileRequest namespace', () => {
|
||||
it('declares 0xE37_1700', () => {
|
||||
expect(UploadPrivateFileRequest.command).toBe(0xe37);
|
||||
expect(UploadPrivateFileRequest.subCommand).toBe(1700);
|
||||
});
|
||||
|
||||
it('packages businessId=3, clientType=1, flagSupportMediaPlatform=1 alongside the upload body', async () => {
|
||||
const deps = makeDeps({ upload: { uuid: 'fid', boolFileExist: true } as any });
|
||||
await UploadPrivateFileRequest.invoke(deps, {
|
||||
senderUid: 's',
|
||||
receiverUid: 'r',
|
||||
fileName: 'f.pdf',
|
||||
fileSize: 4096,
|
||||
fileSha1: new Uint8Array(20),
|
||||
fileMd5: new Uint8Array(16),
|
||||
md510MCheckSum: new Uint8Array(16),
|
||||
});
|
||||
const [wire, bytes] = deps.sendRawPacket.mock.calls[0]!;
|
||||
expect(wire).toBe('OidbSvcTrpcTcp.0xe37_1700');
|
||||
const env = protobuf_decode<OidbBase<OidbPrivateFileUploadReq>>(bytes);
|
||||
expect(env.body).toMatchObject({
|
||||
command: 1700,
|
||||
businessId: 3,
|
||||
clientType: 1,
|
||||
flagSupportMediaPlatform: 1,
|
||||
});
|
||||
expect(env.body?.upload).toMatchObject({
|
||||
senderUid: 's',
|
||||
receiverUid: 'r',
|
||||
fileSize: 4096,
|
||||
fileName: 'f.pdf',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the upload body for the facade host-selection step', async () => {
|
||||
const deps = makeDeps({
|
||||
upload: { uuid: 'fid-Y', fileAddon: 'hash', boolFileExist: true } as any,
|
||||
});
|
||||
const out = await UploadPrivateFileRequest.invoke(deps, {
|
||||
senderUid: 's',
|
||||
receiverUid: 'r',
|
||||
fileName: 'f',
|
||||
fileSize: 0,
|
||||
fileSha1: new Uint8Array(0),
|
||||
fileMd5: new Uint8Array(0),
|
||||
md510MCheckSum: new Uint8Array(0),
|
||||
});
|
||||
expect(out.uuid).toBe('fid-Y');
|
||||
expect(out.fileAddon).toBe('hash');
|
||||
});
|
||||
|
||||
it('throws when the upload sub-message is missing', async () => {
|
||||
const deps = makeDeps({});
|
||||
await expect(
|
||||
UploadPrivateFileRequest.invoke(deps, {
|
||||
senderUid: 's',
|
||||
receiverUid: 'r',
|
||||
fileName: 'f',
|
||||
fileSize: 0,
|
||||
fileSha1: new Uint8Array(0),
|
||||
fileMd5: new Uint8Array(0),
|
||||
md510MCheckSum: new Uint8Array(0),
|
||||
}),
|
||||
).rejects.toThrow(/upload response missing/);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user