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
147 lines
5.8 KiB
TypeScript
147 lines
5.8 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import type { AddressInfo } from 'node:net';
|
|
import { Readable } from 'node:stream';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
createReverseWebSocketServer,
|
|
type SnowLumaReverseWebSocketServer,
|
|
} from '../../sdk/src/node/index';
|
|
import { WsClientAdapter } from '../src/network/ws-client-adapter';
|
|
import { buildDispatchPayload } from '../src/event-filter';
|
|
import { okResponse, type JsonObject, type WsRole } from '../src/types';
|
|
import type { ApiActionContext } from '../src/api-handler';
|
|
import { actions as uploadActions, __test as uploadTest } from '../src/actions/stream-file';
|
|
import { actions as downloadActions } from '../src/actions/stream-download';
|
|
import { createCompiledTestHandler, testAction } from './helpers/compiled-action-handler';
|
|
|
|
describe('SDK reverse server against the real OneBot reverse adapter', () => {
|
|
const adapters: WsClientAdapter[] = [];
|
|
let server: SnowLumaReverseWebSocketServer | undefined;
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(adapters.splice(0).map((adapter) => adapter.close()));
|
|
await server?.close();
|
|
|
|
for (const key of uploadTest.uploads.keys()) {
|
|
uploadTest.cleanupUpload(key, true);
|
|
}
|
|
});
|
|
|
|
it.each(['universal', 'split'] as const)(
|
|
'%s carries events, actions and complete binary streams',
|
|
async (mode) => {
|
|
server = createReverseWebSocketServer({
|
|
port: 0,
|
|
accessToken: 'integration-test',
|
|
selfIds: ['10001'],
|
|
});
|
|
const errors = vi.fn();
|
|
server.on('error', errors);
|
|
await server.listen();
|
|
const { port } = server.address as AddressInfo;
|
|
const api = createCompiledTestHandler({} as ApiActionContext, [
|
|
...uploadActions,
|
|
...downloadActions,
|
|
testAction('sdk_ping', () => okResponse({ pong: true })),
|
|
]);
|
|
const roles: WsRole[] = mode === 'universal' ? ['Universal'] : ['Api', 'Event'];
|
|
|
|
for (const role of roles) {
|
|
const adapter = new WsClientAdapter(
|
|
`reverse-${role}`,
|
|
{
|
|
name: `reverse-${role}`,
|
|
url: `ws://127.0.0.1:${port}/onebot/v11/ws`,
|
|
accessToken: 'integration-test',
|
|
role,
|
|
messageFormat: 'array',
|
|
reportSelfMessage: false,
|
|
},
|
|
{
|
|
uin: '10001',
|
|
api,
|
|
buildLifecycleEvent: (subType) => ({
|
|
self_id: 10001,
|
|
post_type: 'meta_event',
|
|
meta_event_type: 'lifecycle',
|
|
sub_type: subType,
|
|
}),
|
|
buildHeartbeatEvent: () => ({
|
|
self_id: 10001,
|
|
post_type: 'meta_event',
|
|
meta_event_type: 'heartbeat',
|
|
}),
|
|
},
|
|
);
|
|
adapters.push(adapter);
|
|
adapter.open();
|
|
}
|
|
|
|
await vi.waitFor(() => {
|
|
expect(server!.getClient('10001')?.canCallActions).toBe(true);
|
|
expect(server!.getClient('10001')?.canReceiveEvents).toBe(true);
|
|
expect(adapters.every((adapter) => adapter.describeStatus().status === 'ok')).toBe(
|
|
true,
|
|
);
|
|
});
|
|
const client = server.getClient('10001')!;
|
|
expect(await client.raw('sdk_ping')).toEqual({ pong: true });
|
|
const seen = vi.fn();
|
|
client.on('event', seen);
|
|
const received = client.waitForMessage((event) => event.message_id === -42, {
|
|
timeoutMs: 1000,
|
|
});
|
|
const event: JsonObject = {
|
|
self_id: 10001,
|
|
time: 1,
|
|
post_type: 'message',
|
|
message_type: 'private',
|
|
sub_type: 'friend',
|
|
user_id: 20001,
|
|
message_id: -42,
|
|
message: [{ type: 'text', data: { text: 'wire test' } }],
|
|
raw_message: 'wire test',
|
|
};
|
|
|
|
for (const adapter of adapters) {
|
|
const own = { ...event, post_type: 'message_sent' };
|
|
adapter.onEvent(own, buildDispatchPayload(own));
|
|
adapter.onEvent(event, buildDispatchPayload(event));
|
|
}
|
|
|
|
expect((await received).message_id).toBe(-42);
|
|
expect(seen.mock.calls.some(([event]) => event.post_type === 'message_sent')).toBe(
|
|
false,
|
|
);
|
|
const bytes = Buffer.from('reverse transport binary stream\n'.repeat(3000));
|
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
const uploaded = await client.uploadFileStream(Readable.from([bytes]), {
|
|
fileSize: bytes.length,
|
|
chunkSize: 8192,
|
|
filename: 'reverse.bin',
|
|
expectedSha256: sha256,
|
|
fileRetention: 0,
|
|
});
|
|
const chunks: Uint8Array[] = [];
|
|
const stream = client.downloadFileStream({
|
|
file: uploaded.file_path,
|
|
chunk_size: 4096,
|
|
});
|
|
|
|
while (true) {
|
|
const next = await stream.next();
|
|
|
|
if (next.done) {
|
|
expect(next.value.total_bytes).toBe(bytes.length);
|
|
break;
|
|
}
|
|
|
|
chunks.push(next.value);
|
|
}
|
|
|
|
expect(Buffer.concat(chunks)).toEqual(bytes);
|
|
expect(errors).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
});
|