Archived
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
/** 轮询工作者:每 tick 调 pollDue;上一 tick 没跑完就跳过这一拍;出错的账号写 stderr;stop 后不再跑 */
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import type { MailPollOutcome } from '@contracts';
|
|
import { pollMailOnce, startMailPoller } from '../workers/mail-poller.ts';
|
|
|
|
function fakeMail(outcomes: MailPollOutcome[], holdMs = 0) {
|
|
const calls: Date[] = [];
|
|
return {
|
|
calls,
|
|
mail: {
|
|
pollDue: async (now: Date) => {
|
|
calls.push(now);
|
|
if (holdMs > 0) await new Promise((r) => setTimeout(r, holdMs));
|
|
return outcomes;
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('pollMailOnce', () => {
|
|
it('回用例的结果;出错的账号写一行 stderr', async () => {
|
|
const write = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
const { mail } = fakeMail([{ accountId: 'a', pulled: 1, stored: 1, dispatched: 1, more: false }, { accountId: 'b', pulled: 0, stored: 0, dispatched: 0, more: false, error: '连不上' }]);
|
|
const out = await pollMailOnce({ container: { mail: mail as never } });
|
|
expect(out).toHaveLength(2);
|
|
expect(write).toHaveBeenCalledWith(expect.stringContaining('[mail-poller] b:连不上'));
|
|
write.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('startMailPoller', () => {
|
|
beforeEach(() => vi.useFakeTimers());
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it('按 tick 调;在跑时跳过;stop 后停', async () => {
|
|
const { mail, calls } = fakeMail([], 1000);
|
|
const poller = startMailPoller({ container: { mail: mail as never } }, 100);
|
|
await vi.advanceTimersByTimeAsync(350);
|
|
expect(calls).toHaveLength(1); // 第一拍 1000ms 没跑完,后面两拍跳过
|
|
await vi.advanceTimersByTimeAsync(1000);
|
|
expect(calls.length).toBeGreaterThanOrEqual(2);
|
|
poller.stop();
|
|
const before = calls.length;
|
|
await vi.advanceTimersByTimeAsync(2000);
|
|
expect(calls).toHaveLength(before);
|
|
});
|
|
});
|