Files
spoon/apps/agent-worker/tests/unit/heartbeat-controller.test.ts
T

61 lines
1.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { createHeartbeatController } from '../../src/heartbeat-controller';
describe('heartbeat controller', () => {
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
test('does not overlap ticks while a heartbeat is in flight', async () => {
let resolveHeartbeat!: (value: { cancelRequested: boolean }) => void;
const heartbeat = vi.fn(
() =>
new Promise<{ cancelRequested: boolean }>((resolve) => {
resolveHeartbeat = resolve;
}),
);
const controller = createHeartbeatController({
intervalMs: 30_000,
heartbeat,
onCancel: vi.fn(),
onError: vi.fn(),
});
controller.start('job-1');
await vi.advanceTimersByTimeAsync(90_000);
expect(heartbeat).toHaveBeenCalledOnce();
resolveHeartbeat({ cancelRequested: false });
await Promise.resolve();
await vi.advanceTimersByTimeAsync(30_000);
expect(heartbeat).toHaveBeenCalledTimes(2);
controller.stop('job-1');
});
test('removes the timer before awaiting cancellation teardown', async () => {
let finishCancel!: () => void;
const onCancel = vi.fn(
() =>
new Promise<void>((resolve) => {
finishCancel = resolve;
}),
);
const heartbeat = vi.fn().mockResolvedValue({ cancelRequested: true });
const controller = createHeartbeatController({
intervalMs: 30_000,
heartbeat,
onCancel,
onError: vi.fn(),
});
controller.start('job-1');
await vi.advanceTimersByTimeAsync(30_000);
expect(onCancel).toHaveBeenCalledOnce();
expect(controller.has('job-1')).toBe(false);
await vi.advanceTimersByTimeAsync(90_000);
expect(heartbeat).toHaveBeenCalledOnce();
finishCancel();
});
});