Compile canonical names, aliases, stream kinds, documentation, and raw reservations into one immutable registry. Reject duplicate, undeclared, mismatched, or late handler registration before requests can be dispatched.
86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { OneBotNetworkManager } from '../src/network/network-manager';
|
|
import type { ApiActionContext } from '../src/api-handler';
|
|
import { createCompiledTestHandler, testAction } from './helpers/compiled-action-handler';
|
|
|
|
// ApiHandler's register* only define handlers (closures) at construction — they
|
|
// don't touch ctx — so a throwing proxy is a safe minimal context.
|
|
function fakeCtx(): ApiActionContext {
|
|
return new Proxy({} as ApiActionContext, {
|
|
get(_t, prop) { throw new Error(`ctx.${String(prop)} not stubbed`); },
|
|
});
|
|
}
|
|
|
|
describe('OneBotNetworkManager.subscribeDebug', () => {
|
|
it('notifies debug subscribers on emitEvent even with no active adapters', async () => {
|
|
const nm = new OneBotNetworkManager();
|
|
const seen: unknown[] = [];
|
|
nm.subscribeDebug((e) => seen.push(e));
|
|
await nm.emitEvent({ post_type: 'meta_event', x: 1 });
|
|
expect(seen).toEqual([{ post_type: 'meta_event', x: 1 }]);
|
|
});
|
|
|
|
it('stops notifying after unsubscribe', async () => {
|
|
const nm = new OneBotNetworkManager();
|
|
const cb = vi.fn();
|
|
const off = nm.subscribeDebug(cb);
|
|
await nm.emitEvent({ a: 1 });
|
|
off();
|
|
await nm.emitEvent({ a: 2 });
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenCalledWith({ a: 1 });
|
|
});
|
|
|
|
it('a throwing subscriber does not break emitEvent', async () => {
|
|
const nm = new OneBotNetworkManager();
|
|
nm.subscribeDebug(() => { throw new Error('boom'); });
|
|
await expect(nm.emitEvent({ a: 1 })).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('ApiHandler.setObserver', () => {
|
|
it('fires after a successful action with {action, params, response, ms}', async () => {
|
|
const h = createCompiledTestHandler(fakeCtx(), [
|
|
testAction('ping', async () => ({ status: 'ok', retcode: 0, data: 'pong' })),
|
|
]);
|
|
const recs: Array<{ action: string; response: { status: string } }> = [];
|
|
h.setObserver((r) => recs.push(r as any));
|
|
const res = await h.handle('ping', { x: 1 });
|
|
expect(res).toMatchObject({ status: 'ok', data: 'pong' });
|
|
expect(recs).toHaveLength(1);
|
|
expect(recs[0]).toMatchObject({ action: 'ping', params: { x: 1 }, response: { status: 'ok' } });
|
|
expect(typeof (recs[0] as any).ms).toBe('number');
|
|
});
|
|
|
|
it('fires on a thrown action with the failure response', async () => {
|
|
const h = createCompiledTestHandler(fakeCtx(), [
|
|
testAction('boom', async () => { throw new Error('kaboom'); }),
|
|
]);
|
|
const recs: any[] = [];
|
|
h.setObserver((r) => recs.push(r));
|
|
await h.handle('boom', {});
|
|
expect(recs).toHaveLength(1);
|
|
expect(recs[0].response.status).toBe('failed');
|
|
});
|
|
|
|
it('unsubscribe stops observation', async () => {
|
|
const h = createCompiledTestHandler(fakeCtx(), [
|
|
testAction('ping', async () => ({ status: 'ok', retcode: 0 })),
|
|
]);
|
|
const cb = vi.fn();
|
|
const off = h.setObserver(cb);
|
|
await h.handle('ping', {});
|
|
off();
|
|
await h.handle('ping', {});
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('a throwing observer does not break handle', async () => {
|
|
const h = createCompiledTestHandler(fakeCtx(), [
|
|
testAction('ping', async () => ({ status: 'ok', retcode: 0 })),
|
|
]);
|
|
h.setObserver(() => { throw new Error('observer boom'); });
|
|
await expect(h.handle('ping', {})).resolves.toMatchObject({ status: 'ok' });
|
|
});
|
|
});
|