Files
SnowLuma/packages/onebot/tests/manager-bot-status.test.ts
T
TanTan fa9c73363d
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
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

158 lines
5.6 KiB
TypeScript

import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { describe, expect, it, vi } from 'vitest';
import type { OneBotInstance } from '../src/instance';
import { OneBotManager, type DatabaseMigrationCallbacks } from '../src/manager';
function fakeBridge() {
return {
activePid: null,
identity: { nickname: 'test' },
whenRosterWarmupSettled: () =>
Promise.resolve({ friendsLoaded: false, groupsLoaded: false }),
};
}
describe('OneBotManager bot_status', () => {
async function withTempCwd(run: () => Promise<void>): Promise<void> {
const originalCwd = process.cwd();
const root = mkdtempSync(path.join(tmpdir(), 'snowluma-manager-bot-status-'));
process.chdir(root);
try {
await run();
} finally {
process.chdir(originalCwd);
rmSync(root, { recursive: true, force: true });
}
}
function preparingManager(instance: OneBotInstance) {
let callbacks!: DatabaseMigrationCallbacks;
const manager = new OneBotManager({
createDatabaseMigrationTask: () => ({
beginMigration: vi.fn(),
cancel: vi.fn(),
start: (next) => {
callbacks = next;
},
}),
createInstance: () => instance,
});
return {
manager,
ready: () => callbacks.onReady(),
};
}
it('emits online after the instance network is ready', async () => {
await withTempCwd(async () => {
let releaseNetwork!: (value: { applied: boolean; statuses: []; errors: [] }) => void;
const networkReady = new Promise<{ applied: boolean; statuses: []; errors: [] }>(
(resolve) => {
releaseNetwork = resolve;
},
);
const emitBotStatus = vi.fn(async () => undefined);
const instance = {
uin: '10001',
nickname: 'test',
quiesce: vi.fn(),
dispose: vi.fn(async () => ({ closed: true, errors: [] })),
emitBotStatus,
waitUntilNetworkReady: vi.fn(() => networkReady),
startLoginHistorySync: vi.fn(),
getConnectionStatuses: () => [],
} as unknown as OneBotInstance;
const { manager, ready } = preparingManager(instance);
try {
(
manager as unknown as { onSessionStarted(uin: string, bridge: never): void }
).onSessionStarted('10001', fakeBridge() as never);
ready();
await Promise.resolve();
expect(emitBotStatus).not.toHaveBeenCalled();
releaseNetwork({ applied: true, statuses: [], errors: [] });
await vi.waitFor(() => {
expect(emitBotStatus).toHaveBeenCalledOnce();
});
expect(emitBotStatus).toHaveBeenCalledWith('online');
} finally {
await manager.dispose();
}
});
});
it('emits offline and awaits it before dispose', async () => {
let finishOffline!: () => void;
const offlineGate = new Promise<void>((resolve) => {
finishOffline = resolve;
});
const order: string[] = [];
const emitBotStatus = vi.fn(async (subType: 'online' | 'offline') => {
if (subType === 'offline') {
order.push('offline-start');
await offlineGate;
order.push('offline-done');
}
});
const dispose = vi.fn(async () => {
order.push('dispose');
return { closed: true, errors: [] };
});
const instance = {
uin: '10001',
nickname: 'test',
quiesce: vi.fn(),
dispose,
emitBotStatus,
getConnectionStatuses: () => [],
} as unknown as OneBotInstance;
const manager = new OneBotManager();
const internals = manager as unknown as {
instances: Map<string, OneBotInstance>;
onSessionClosed(uin: string): void;
};
internals.instances.set('10001', instance);
internals.onSessionClosed('10001');
await Promise.resolve();
expect(order).toEqual(['offline-start']);
expect(dispose).not.toHaveBeenCalled();
finishOffline();
await vi.waitFor(() => {
expect(order).toEqual(['offline-start', 'offline-done', 'dispose']);
});
expect(emitBotStatus).toHaveBeenCalledWith('offline');
await manager.dispose();
});
it('does not emit offline when the process is disposing', async () => {
const emitBotStatus = vi.fn(async () => undefined);
const dispose = vi.fn(async () => ({ closed: true, errors: [] }));
const instance = {
uin: '10001',
nickname: 'test',
quiesce: vi.fn(),
dispose,
emitBotStatus,
getConnectionStatuses: () => [],
} as unknown as OneBotInstance;
const manager = new OneBotManager();
(manager as unknown as { instances: Map<string, OneBotInstance> }).instances.set(
'10001',
instance,
);
await manager.dispose();
expect(emitBotStatus).not.toHaveBeenCalled();
expect(dispose).toHaveBeenCalledOnce();
(manager as unknown as { onSessionClosed(uin: string): void }).onSessionClosed('10001');
expect(emitBotStatus).not.toHaveBeenCalled();
});
});