import { describe, expect, it, vi } from "vitest" ;
import type { ChannelId } from "../../channels/plugins/index.js" ;
import type { ChannelAccountSnapshot } from "../../channels/plugins/types.js" ;
import type { ChannelManager, ChannelRuntimeSnapshot } from "../server-channels.js" ;
import { createReadinessChecker } from "./readiness.js" ;
function snapshotWith(
accounts: Record<string, Partial<ChannelAccountSnapshot>>,
): ChannelRuntimeSnapshot {
const channels: ChannelRuntimeSnapshot["channels" ] = {};
const channelAccounts: ChannelRuntimeSnapshot["channelAccounts" ] = {};
for (const [channelId, accountSnapshot] of Object.entries(accounts)) {
const resolved = { accountId: "default" , ...accountSnapshot } as ChannelAccountSnapshot;
channels[channelId as ChannelId] = resolved;
channelAccounts[channelId as ChannelId] = { default : resolved };
}
return { channels, channelAccounts };
}
function createManager(snapshot: ChannelRuntimeSnapshot): ChannelManager {
return {
getRuntimeSnapshot: vi.fn(() => snapshot),
startChannels: vi.fn(),
startChannel: vi.fn(),
stopChannel: vi.fn(),
markChannelLoggedOut: vi.fn(),
isHealthMonitorEnabled: vi.fn(() => true ),
isManuallyStopped: vi.fn(() => false ),
resetRestartAttempts: vi.fn(),
};
}
function createHealthyDiscordManager(
startedAt: number,
lastTransportActivityAt: number,
): ChannelManager {
return createManager(
snapshotWith({
discord: {
running: true ,
connected: true ,
enabled: true ,
configured: true ,
lastStartAt: startedAt,
lastTransportActivityAt,
},
}),
);
}
function withReadinessClock(run: () => void ) {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-06T12:00:00Z" ));
try {
run();
} finally {
vi.useRealTimers();
}
}
function createReadinessHarness(params: {
startedAgoMs: number;
accounts: Record<string, Partial<ChannelAccountSnapshot>>;
getStartupPending?: () => boolean ;
cacheTtlMs?: number;
}) {
const startedAt = Date.now() - params.startedAgoMs;
const manager = createManager(snapshotWith(params.accounts));
return {
manager,
readiness: createReadinessChecker({
channelManager: manager,
startedAt,
getStartupPending: params.getStartupPending,
cacheTtlMs: params.cacheTtlMs,
}),
};
}
describe("createReadinessChecker" , () => {
it("reports ready when all managed channels are healthy" , () => {
withReadinessClock(() => {
const startedAt = Date.now() - 5 * 60 _000 ;
const manager = createHealthyDiscordManager(startedAt, Date.now() - 1 _000 );
const readiness = createReadinessChecker({ channelManager: manager, startedAt });
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 300 _000 });
});
});
it("keeps readiness red while startup sidecars are pending" , () => {
withReadinessClock(() => {
const { readiness } = createReadinessHarness({
startedAgoMs: 5 * 60 _000 ,
accounts: {},
getStartupPending: () => true ,
});
expect(readiness()).toEqual({
ready: false ,
failing: ["startup-sidecars" ],
uptimeMs: 300 _000 ,
});
});
});
it("does not cache startup-pending readiness" , () => {
withReadinessClock(() => {
let startupPending = true ;
const { manager, readiness } = createReadinessHarness({
startedAgoMs: 5 * 60 _000 ,
accounts: {},
getStartupPending: () => startupPending,
cacheTtlMs: 1 _000 ,
});
expect(readiness()).toEqual({
ready: false ,
failing: ["startup-sidecars" ],
uptimeMs: 300 _000 ,
});
expect(manager.getRuntimeSnapshot).not.toHaveBeenCalled();
startupPending = false ;
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 300 _000 });
expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(1 );
});
});
it("ignores disabled and unconfigured channels" , () => {
withReadinessClock(() => {
const { readiness } = createReadinessHarness({
startedAgoMs: 5 * 60 _000 ,
accounts: {
discord: {
running: false ,
enabled: false ,
configured: true ,
lastStartAt: Date.now() - 5 * 60 _000 ,
},
telegram: {
running: false ,
enabled: true ,
configured: false ,
lastStartAt: Date.now() - 5 * 60 _000 ,
},
},
});
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 300 _000 });
});
});
it("uses startup grace before marking disconnected channels not ready" , () => {
withReadinessClock(() => {
const { readiness } = createReadinessHarness({
startedAgoMs: 30 _000 ,
accounts: {
discord: {
running: true ,
connected: false ,
enabled: true ,
configured: true ,
lastStartAt: Date.now() - 30 _000 ,
},
},
});
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 30 _000 });
});
});
it("reports disconnected managed channels after startup grace" , () => {
withReadinessClock(() => {
const { readiness } = createReadinessHarness({
startedAgoMs: 5 * 60 _000 ,
accounts: {
discord: {
running: true ,
connected: false ,
enabled: true ,
configured: true ,
lastStartAt: Date.now() - 5 * 60 _000 ,
},
},
});
expect(readiness()).toEqual({ ready: false , failing: ["discord" ], uptimeMs: 300 _000 });
});
});
it("keeps restart-pending channels ready during reconnect backoff" , () => {
withReadinessClock(() => {
const startedAt = Date.now() - 5 * 60 _000 ;
const { readiness } = createReadinessHarness({
startedAgoMs: 5 * 60 _000 ,
accounts: {
discord: {
running: false ,
restartPending: true ,
reconnectAttempts: 3 ,
enabled: true ,
configured: true ,
lastStartAt: startedAt - 30 _000 ,
lastStopAt: Date.now() - 5 _000 ,
},
},
});
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 300 _000 });
});
});
it("treats stale-socket channels as ready to avoid pulling healthy idle pods" , () => {
withReadinessClock(() => {
const startedAt = Date.now() - 31 * 60 _000 ;
const { readiness } = createReadinessHarness({
startedAgoMs: 31 * 60 _000 ,
accounts: {
discord: {
running: true ,
connected: true ,
enabled: true ,
configured: true ,
lastStartAt: startedAt,
lastTransportActivityAt: Date.now() - 31 * 60 _000 ,
},
},
});
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 1 _860 _000 });
});
});
it("keeps telegram long-polling channels ready without stale-socket classification" , () => {
withReadinessClock(() => {
const startedAt = Date.now() - 31 * 60 _000 ;
const { readiness } = createReadinessHarness({
startedAgoMs: 31 * 60 _000 ,
accounts: {
telegram: {
running: true ,
connected: true ,
enabled: true ,
configured: true ,
lastStartAt: startedAt,
lastTransportActivityAt: null ,
},
},
});
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 1 _860 _000 });
});
});
it("caches readiness snapshots briefly to keep repeated probes cheap" , () => {
withReadinessClock(() => {
const { manager, readiness } = createReadinessHarness({
startedAgoMs: 5 * 60 _000 ,
accounts: {
discord: {
running: true ,
connected: true ,
enabled: true ,
configured: true ,
lastStartAt: Date.now() - 5 * 60 _000 ,
lastTransportActivityAt: Date.now() - 1 _000 ,
},
},
cacheTtlMs: 1 _000 ,
});
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 300 _000 });
vi.advanceTimersByTime(500 );
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 300 _500 });
expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(1 );
vi.advanceTimersByTime(600 );
expect(readiness()).toEqual({ ready: true , failing: [], uptimeMs: 301 _100 });
expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(2 );
});
});
});
Messung V0.5 in Prozent C=100 H=98 G=98
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland