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,125 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { StateBus } from '../src/webui/state-bus';
|
||||
import { createStateWiring } from '../src/webui/state-wiring';
|
||||
import { BridgeManager } from '../src/bridge/manager';
|
||||
|
||||
describe('createStateWiring', () => {
|
||||
it('keeps a replacement wiring independent of its disposed predecessor', async () => {
|
||||
const old = new BridgeManager();
|
||||
const current = new BridgeManager();
|
||||
const listener = vi.fn();
|
||||
const previousWiring = createStateWiring(old);
|
||||
previousWiring.bus.subscribe(listener);
|
||||
previousWiring.dispose();
|
||||
const wiring = createStateWiring(current);
|
||||
wiring.bus.subscribe(listener);
|
||||
|
||||
old.onHookLogin(4100, '12345', {} as never);
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
current.onHookLogin(4200, '23456', {} as never);
|
||||
expect(listener.mock.calls).toEqual([['qq-list'], ['connections']]);
|
||||
|
||||
wiring.dispose();
|
||||
await old.dispose();
|
||||
await current.dispose();
|
||||
});
|
||||
|
||||
it('unsubscribes on disposal before another account starts', async () => {
|
||||
const bm = new BridgeManager();
|
||||
const subscribe = vi.spyOn(bm, 'subscribeSessions');
|
||||
const wiring = createStateWiring(bm);
|
||||
const publish = vi.spyOn(wiring.bus, 'publish');
|
||||
wiring.dispose();
|
||||
wiring.dispose();
|
||||
|
||||
bm.onHookLogin(4300, '34567', {} as never);
|
||||
expect(publish).not.toHaveBeenCalled();
|
||||
expect(subscribe).toHaveBeenCalledTimes(1);
|
||||
await bm.dispose();
|
||||
});
|
||||
|
||||
it('releases its session subscription when disposed', () => {
|
||||
const unsubscribe = vi.fn();
|
||||
const source = { subscribeSessions: vi.fn(() => unsubscribe) };
|
||||
const wiring = createStateWiring(source);
|
||||
|
||||
wiring.dispose();
|
||||
|
||||
expect(source.subscribeSessions).toHaveBeenCalledOnce();
|
||||
expect(unsubscribe).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('provides a fresh StateBus and an onSessionsChanged that publishes "processes"', () => {
|
||||
const wiring = createStateWiring(new BridgeManager());
|
||||
expect(wiring.bus).toBeInstanceOf(StateBus);
|
||||
const sub = vi.fn();
|
||||
wiring.bus.subscribe(sub);
|
||||
wiring.onSessionsChanged();
|
||||
expect(sub).toHaveBeenCalledExactlyOnceWith('processes');
|
||||
});
|
||||
|
||||
it('binds a BridgeManager so session-started publishes both "qq-list" and "connections"', () => {
|
||||
const bm = new BridgeManager();
|
||||
const wiring = createStateWiring(bm);
|
||||
const seen: string[] = [];
|
||||
wiring.bus.subscribe((r) => seen.push(r));
|
||||
|
||||
// Real onHookLogin path — fires the session-started listener internally
|
||||
// exactly once (per uin).
|
||||
bm.onHookLogin(4242, '12345', {} as never);
|
||||
expect(seen).toEqual(['qq-list', 'connections']);
|
||||
});
|
||||
|
||||
it('publishes "qq-list" + "connections" on session-closed', () => {
|
||||
const bm = new BridgeManager();
|
||||
const wiring = createStateWiring(bm);
|
||||
bm.onHookLogin(4242, '12345', {} as never);
|
||||
const sub = vi.fn();
|
||||
wiring.bus.subscribe(sub);
|
||||
|
||||
bm.onPidDisconnected(4242);
|
||||
|
||||
// Order is qq-list first (account list shrinks) then connections
|
||||
// (adapter status list shrinks). Two distinct resource invalidations.
|
||||
expect(sub).toHaveBeenCalledTimes(2);
|
||||
expect(sub).toHaveBeenNthCalledWith(1, 'qq-list');
|
||||
expect(sub).toHaveBeenNthCalledWith(2, 'connections');
|
||||
});
|
||||
|
||||
it('dispose() latches so later Hook/Bridge edges publish nothing', () => {
|
||||
const bm = new BridgeManager();
|
||||
const wiring = createStateWiring(bm);
|
||||
const sub = vi.fn();
|
||||
wiring.bus.subscribe(sub);
|
||||
|
||||
wiring.dispose();
|
||||
|
||||
// onSessionsChanged becomes a no-op.
|
||||
wiring.onSessionsChanged();
|
||||
expect(sub).not.toHaveBeenCalled();
|
||||
|
||||
// BridgeManager listener fires nothing into a disposed bus.
|
||||
bm.onHookLogin(4242, '12345', {} as never);
|
||||
expect(sub).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('survives a BridgeManager listener throw — the OTHER listeners on the same BridgeManager keep working', () => {
|
||||
const bm = new BridgeManager();
|
||||
const wiring = createStateWiring(bm);
|
||||
|
||||
// A peer subscriber (e.g. OneBotManager) added AFTER state-wiring throws.
|
||||
const peer = vi.fn(() => {
|
||||
throw new Error('peer down');
|
||||
});
|
||||
bm.subscribeSessions({ started: peer });
|
||||
|
||||
const sub = vi.fn();
|
||||
wiring.bus.subscribe(sub);
|
||||
|
||||
expect(() => bm.onHookLogin(4242, '12345', {} as never)).not.toThrow();
|
||||
// The state-wiring listener still ran (BridgeManager isolates exceptions
|
||||
// per listener).
|
||||
expect(sub).toHaveBeenCalledWith('qq-list');
|
||||
expect(sub).toHaveBeenCalledWith('connections');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user