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
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
deliverPttTransText,
|
|
pttTransKey,
|
|
requestPttTransText,
|
|
} from '../src/modules/ptt-trans-waiter';
|
|
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
describe('PTT transcription requests', () => {
|
|
it.each([Number(18446744073335783674n), 1.5, NaN, Infinity])(
|
|
'rejects an inexact numeric message id: %s',
|
|
(msgId) => {
|
|
expect(() => pttTransKey(10001, msgId)).toThrow(RangeError);
|
|
},
|
|
);
|
|
|
|
it('keeps accounts isolated for the same message id', async () => {
|
|
const first = requestPttTransText(pttTransKey(10001, 1), 20_000, async () => '');
|
|
const second = requestPttTransText(pttTransKey(10002, 1), 20_000, async () => '');
|
|
deliverPttTransText(pttTransKey(10001, 1), 'first');
|
|
deliverPttTransText(pttTransKey(10002, 1), 'second');
|
|
await expect(first).resolves.toBe('first');
|
|
await expect(second).resolves.toBe('second');
|
|
});
|
|
|
|
it.each(['text', ''])('accepts a pushed transcription: %j', async (text) => {
|
|
const result = requestPttTransText('account:push', 20_000, async () => '');
|
|
expect(deliverPttTransText('account:push', text)).toBe(true);
|
|
await expect(result).resolves.toBe(text);
|
|
expect(deliverPttTransText('account:push', text)).toBe(false);
|
|
});
|
|
|
|
it('registers before triggering so an immediate push is not lost', async () => {
|
|
const result = requestPttTransText('account:fast', 20_000, async () => {
|
|
deliverPttTransText('account:fast', 'pushed');
|
|
return 'inline';
|
|
});
|
|
await expect(result).resolves.toBe('pushed');
|
|
});
|
|
|
|
it('accepts an inline transcription', async () => {
|
|
await expect(
|
|
requestPttTransText('account:inline', 20_000, async () => 'inline'),
|
|
).resolves.toBe('inline');
|
|
});
|
|
|
|
it.each([new Error('failed'), 'failed'])(
|
|
'propagates a trigger failure: %s',
|
|
async (failure) => {
|
|
await expect(
|
|
requestPttTransText('account:failure', 20_000, async () => {
|
|
throw failure;
|
|
}),
|
|
).rejects.toThrow('failed');
|
|
},
|
|
);
|
|
|
|
it('handles a synchronous trigger failure', async () => {
|
|
await expect(
|
|
requestPttTransText('account:sync-failure', 20_000, () => {
|
|
throw new Error('sync');
|
|
}),
|
|
).rejects.toThrow('sync');
|
|
});
|
|
|
|
it.each(['resolve', 'reject'] as const)(
|
|
'ignores a superseded trigger that later calls %s',
|
|
async (settle) => {
|
|
const oldTrigger = Promise.withResolvers<string>();
|
|
const old = requestPttTransText('account:replace', 20_000, () => oldTrigger.promise);
|
|
const rejected = expect(old).rejects.toThrow('被新的请求取代');
|
|
const current = requestPttTransText('account:replace', 20_000, async () => '');
|
|
await rejected;
|
|
|
|
if (settle === 'resolve') {
|
|
oldTrigger.resolve('stale');
|
|
} else {
|
|
oldTrigger.reject(new Error('stale'));
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
deliverPttTransText('account:replace', 'current');
|
|
await expect(current).resolves.toBe('current');
|
|
},
|
|
);
|
|
|
|
it('times out while the trigger is pending and ignores its eventual failure', async () => {
|
|
vi.useFakeTimers();
|
|
const trigger = Promise.withResolvers<string>();
|
|
const result = requestPttTransText('account:timeout', 1_000, () => trigger.promise);
|
|
const rejected = expect(result).rejects.toThrow('超时');
|
|
await vi.advanceTimersByTimeAsync(1_000);
|
|
await rejected;
|
|
const next = requestPttTransText('account:timeout', 1_000, async () => '');
|
|
trigger.reject(new Error('late failure'));
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
deliverPttTransText('account:timeout', 'next');
|
|
await expect(next).resolves.toBe('next');
|
|
expect(vi.getTimerCount()).toBe(0);
|
|
});
|
|
|
|
it('clears the timeout after a successful result', async () => {
|
|
vi.useFakeTimers();
|
|
const result = requestPttTransText('account:done', 1_000, async () => 'done');
|
|
await expect(result).resolves.toBe('done');
|
|
expect(vi.getTimerCount()).toBe(0);
|
|
expect(() => deliverPttTransText('account:done', 'late')).not.toThrow();
|
|
});
|
|
});
|