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,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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user