kt-template-online-api/test/qqbot/account/qqbot-napcat-watchdog.service.spec.ts
sunlei bb6d0263e7 feat: NapCat 启用 -q 快速登录与离线看门狗
- 容器为已知 selfId 创建/重建时注入 ACCOUNT 环境变量启用 NapCat -q 快速登录,
  重启可从持久化会话免扫码自动重登;旧容器在「更新登录」原地重建补齐 ACCOUNT
  (docker inspect 已带则跳过、失败非阻断、保留 QQ 数据卷)。
- 新增 QqbotNapcatWatchdogService 定时巡检在线账号,复用既有 super 站内信离线告警,
  仅检测告警不重建容器,避免与 NapCat 自身重连竞争造成设备登录抖动。
- 新增 QQBOT_NAPCAT_WATCHDOG_ENABLED/INTERVAL_MS 配置及 README/.env.example 说明。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:50:08 +08:00

103 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ConfigService } from '@nestjs/config';
import { QqbotNapcatWatchdogService } from '@/qqbot/account/qqbot-napcat-watchdog.service';
function buildService(
configValues: Record<string, string | undefined>,
runOfflineWatchdog: jest.Mock,
) {
const configService = {
get: jest.fn((key: string) => configValues[key]),
} as unknown as ConfigService;
const accountService = { runOfflineWatchdog } as any;
return new QqbotNapcatWatchdogService(configService, accountService);
}
// 刷新微任务队列,让定时器回调里的 async tick含 finally 复位 running执行完。
async function flushMicrotasks() {
await Promise.resolve();
await Promise.resolve();
}
describe('QqbotNapcatWatchdogService', () => {
afterEach(() => {
jest.useRealTimers();
});
it('periodically triggers the offline watchdog when enabled', async () => {
jest.useFakeTimers();
const runOfflineWatchdog = jest.fn().mockResolvedValue({ checked: 1 });
const service = buildService(
{ QQBOT_NAPCAT_WATCHDOG_INTERVAL_MS: '30000' },
runOfflineWatchdog,
);
service.onModuleInit();
expect(runOfflineWatchdog).not.toHaveBeenCalled();
jest.advanceTimersByTime(30000);
await flushMicrotasks();
expect(runOfflineWatchdog).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(30000);
await flushMicrotasks();
expect(runOfflineWatchdog).toHaveBeenCalledTimes(2);
service.onModuleDestroy();
jest.advanceTimersByTime(60000);
await flushMicrotasks();
expect(runOfflineWatchdog).toHaveBeenCalledTimes(2);
});
it('does not start a timer when disabled', async () => {
jest.useFakeTimers();
const runOfflineWatchdog = jest.fn().mockResolvedValue({ checked: 0 });
const service = buildService(
{ QQBOT_NAPCAT_WATCHDOG_ENABLED: 'false' },
runOfflineWatchdog,
);
service.onModuleInit();
jest.advanceTimersByTime(600000);
await flushMicrotasks();
expect(runOfflineWatchdog).not.toHaveBeenCalled();
});
it('clamps an unreasonably small interval to the safe default', () => {
const service = buildService(
{ QQBOT_NAPCAT_WATCHDOG_INTERVAL_MS: '1000' },
jest.fn(),
) as any;
expect(service.getIntervalMs()).toBe(120_000);
});
it('skips overlapping ticks while a previous run is still pending', async () => {
jest.useFakeTimers();
let resolvePending: (() => void) | undefined;
const runOfflineWatchdog = jest.fn(
() =>
new Promise<{ checked: number }>((resolve) => {
resolvePending = () => resolve({ checked: 1 });
}),
);
const service = buildService(
{ QQBOT_NAPCAT_WATCHDOG_INTERVAL_MS: '30000' },
runOfflineWatchdog,
);
service.onModuleInit();
jest.advanceTimersByTime(30000); // first tick starts, stays pending
await flushMicrotasks();
jest.advanceTimersByTime(30000); // second tick should be skipped
await flushMicrotasks();
expect(runOfflineWatchdog).toHaveBeenCalledTimes(1);
resolvePending?.();
await flushMicrotasks();
jest.advanceTimersByTime(30000); // running reset, a new tick can run
await flushMicrotasks();
expect(runOfflineWatchdog).toHaveBeenCalledTimes(2);
service.onModuleDestroy();
});
});