Files
NapCatQQ/packages/napcat-test/offlineRecovery.test.ts
T
TanTan f954252a34
Build NapCat Artifacts / Type Check & Test (push) Waiting to run
Release Tag / validate-version (push) Waiting to run
NapCatQQ source snapshot
2026-11-13 09:01:47 +08:00

99 lines
3.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { createOfflineRecovery } from '../napcat-core/helper/offline-recovery';
function setup (restartResult: boolean | Error = true) {
const state = { online: false };
const onOffline = vi.fn();
const onRecovered = vi.fn();
const restart = vi.fn(async () => {
if (restartResult instanceof Error) throw restartResult;
return restartResult;
});
const handleOffline = createOfflineRecovery({
isOnline: () => state.online,
onOffline,
onRecovered,
restart,
});
return { state, onOffline, onRecovered, restart, handleOffline };
}
describe('offline recovery', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test('kicked offline restarts after a short delay', async () => {
const { restart, onOffline, handleOffline } = setup();
handleOffline('kicked', false);
expect(onOffline).toHaveBeenCalledWith('kicked');
await vi.advanceTimersByTimeAsync(3_000);
expect(restart).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(500);
expect(restart).toHaveBeenCalledTimes(1);
expect(restart).toHaveBeenCalledWith('kicked');
});
test('silent offline waits for the account to reconnect before restarting', async () => {
const { restart, handleOffline } = setup();
handleOffline('silent', true);
await vi.advanceTimersByTimeAsync(3_500);
expect(restart).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(60_000);
expect(restart).toHaveBeenCalledTimes(1);
});
test('silent offline that recovers on its own does not restart', async () => {
const { state, restart, onRecovered, handleOffline } = setup();
handleOffline('silent', true);
await vi.advanceTimersByTimeAsync(10_000);
state.online = true;
await vi.advanceTimersByTimeAsync(60_000);
expect(restart).not.toHaveBeenCalled();
expect(onRecovered).toHaveBeenCalledTimes(1);
});
test('a kick during the silent-offline wait restarts without waiting the full minute', async () => {
const { restart, handleOffline } = setup();
handleOffline('silent', true);
await vi.advanceTimersByTimeAsync(1_000);
handleOffline('kicked', false);
await vi.advanceTimersByTimeAsync(3_500);
expect(restart).toHaveBeenCalledTimes(1);
expect(restart).toHaveBeenCalledWith('kicked');
await vi.advanceTimersByTimeAsync(120_000);
expect(restart).toHaveBeenCalledTimes(1);
});
test('repeated offline events schedule only one restart', async () => {
const { restart, onOffline, handleOffline } = setup();
handleOffline('kicked', false);
handleOffline('kicked again', false);
handleOffline('silent', true);
await vi.advanceTimersByTimeAsync(120_000);
expect(restart).toHaveBeenCalledTimes(1);
expect(onOffline).toHaveBeenCalledTimes(3);
});
test.each([false, new Error('boom')])('a failed restart (%s) can be retried by the next offline event', async (result) => {
const { restart, handleOffline } = setup(result);
handleOffline('kicked', false);
await vi.advanceTimersByTimeAsync(3_500);
expect(restart).toHaveBeenCalledTimes(1);
handleOffline('kicked', false);
await vi.advanceTimersByTimeAsync(3_500);
expect(restart).toHaveBeenCalledTimes(2);
});
});