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
119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
import { EventEmitter } from 'node:events';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { subscribeLogs } from '@snowluma/common/logger';
|
|
import type { DatabaseMigrationCallbacks } from '../src/manager';
|
|
import {
|
|
createMessageStoreMigrationTask,
|
|
type MessageStoreMigrationWorkerHandle,
|
|
} from '../src/message-store-migration-task';
|
|
|
|
function fakeWorker(): EventEmitter & MessageStoreMigrationWorkerHandle {
|
|
const worker = new EventEmitter() as EventEmitter & MessageStoreMigrationWorkerHandle;
|
|
worker.postMessage = vi.fn();
|
|
worker.unref = vi.fn();
|
|
return worker;
|
|
}
|
|
|
|
function callbacks(): DatabaseMigrationCallbacks {
|
|
return {
|
|
onReady: vi.fn(),
|
|
onProgress: vi.fn(),
|
|
onFailed: vi.fn(),
|
|
};
|
|
}
|
|
|
|
describe('message-store migration task lifecycle', () => {
|
|
it('records worker preparation through the parent logger with account context', () => {
|
|
const worker = fakeWorker();
|
|
const handlers = callbacks();
|
|
const entries: Array<{ scope: string; uin?: number; message: string }> = [];
|
|
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
|
|
const task = createMessageStoreMigrationTask('10001', () => worker);
|
|
|
|
try {
|
|
task.start(handlers);
|
|
worker.emit('message', { kind: 'preparation', progress: { stage: 'started' } });
|
|
worker.emit('message', {
|
|
kind: 'preparation',
|
|
progress: { stage: 'indexed', elapsedMs: 12.5 },
|
|
});
|
|
worker.emit('message', {
|
|
kind: 'preparation',
|
|
progress: { stage: 'counted', total: 3, elapsedMs: 2.1 },
|
|
});
|
|
expect(
|
|
entries.map((entry) => ({
|
|
scope: entry.scope,
|
|
uin: entry.uin,
|
|
message: entry.message,
|
|
})),
|
|
).toEqual([
|
|
{
|
|
scope: 'OneBot.MessageMigration',
|
|
uin: 10001,
|
|
message: 'preparing message history migration',
|
|
},
|
|
{
|
|
scope: 'OneBot.MessageMigration',
|
|
uin: 10001,
|
|
message: 'message history migration preparation finished in 13ms',
|
|
},
|
|
{
|
|
scope: 'OneBot.MessageMigration',
|
|
uin: 10001,
|
|
message: 'message history migration: pending=3 checked in 2ms',
|
|
},
|
|
]);
|
|
expect(handlers.onProgress).not.toHaveBeenCalled();
|
|
expect(handlers.onFailed).not.toHaveBeenCalled();
|
|
task.cancel();
|
|
worker.emit('message', { kind: 'preparation', progress: { stage: 'started' } });
|
|
expect(entries).toHaveLength(3);
|
|
} finally {
|
|
unsubscribe();
|
|
}
|
|
});
|
|
|
|
it('reports an exit before completion even when its code is zero', () => {
|
|
const worker = fakeWorker();
|
|
const handlers = callbacks();
|
|
const task = createMessageStoreMigrationTask('10001', () => worker);
|
|
|
|
task.start(handlers);
|
|
worker.emit('exit', 0);
|
|
|
|
expect(handlers.onFailed).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('accepts a normal exit after completion', () => {
|
|
const worker = fakeWorker();
|
|
const handlers = callbacks();
|
|
const task = createMessageStoreMigrationTask('10001', () => worker);
|
|
|
|
task.start(handlers);
|
|
worker.emit('message', {
|
|
kind: 'progress',
|
|
status: { phase: 'complete', processed: 8, total: 8 },
|
|
elapsedMs: 25,
|
|
});
|
|
worker.emit('exit', 0);
|
|
|
|
expect(handlers.onProgress).toHaveBeenCalledOnce();
|
|
expect(handlers.onFailed).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('ignores worker exit after cancellation', () => {
|
|
const worker = fakeWorker();
|
|
const handlers = callbacks();
|
|
const task = createMessageStoreMigrationTask('10001', () => worker);
|
|
|
|
task.start(handlers);
|
|
task.cancel();
|
|
worker.emit('exit', 1);
|
|
|
|
expect(worker.postMessage).toHaveBeenCalledWith('cancel');
|
|
expect(worker.unref).toHaveBeenCalledOnce();
|
|
expect(handlers.onFailed).not.toHaveBeenCalled();
|
|
});
|
|
});
|