
































@@ -1,11 +1,11 @@
11import { describe, expect, it } from "vitest";
22import { evaluateChannelHealth, resolveChannelRestartReason } from "./channel-health-policy.js";
334-function evaluateDiscordHealth(
4+function evaluateHealth(
55account: Record<string, unknown>,
6-now = 100_000,
7-channelId = "discord",
6+opts: { now?: number; channelId?: string } = {},
87) {
8+const { now = 100_000, channelId = "discord" } = opts;
99return evaluateChannelHealth(account, {
1010 channelId,
1111 now,
@@ -14,284 +14,171 @@ function evaluateDiscordHealth(
1414});
1515}
161617+function runningAccount(overrides: Record<string, unknown> = {}) {
18+return {
19+running: true,
20+enabled: true,
21+configured: true,
22+ ...overrides,
23+};
24+}
25+26+function connectedAccount(overrides: Record<string, unknown> = {}) {
27+return runningAccount({ connected: true, ...overrides });
28+}
29+30+function activeRunAccount(lastRunActivityAt: number, overrides: Record<string, unknown> = {}) {
31+return runningAccount({
32+connected: false,
33+activeRuns: 1,
34+ lastRunActivityAt,
35+ ...overrides,
36+});
37+}
38+39+function staleTransportAccount(overrides: Record<string, unknown> = {}) {
40+return connectedAccount({
41+lastStartAt: 0,
42+lastTransportActivityAt: 0,
43+ ...overrides,
44+});
45+}
46+47+function inheritedTransportAccount() {
48+return connectedAccount({
49+lastStartAt: 50_000,
50+lastTransportActivityAt: 10_000,
51+});
52+}
53+1754describe("evaluateChannelHealth", () => {
1855it("treats disabled accounts as healthy unmanaged", () => {
19-const evaluation = evaluateChannelHealth(
20-{
21-running: false,
22-enabled: false,
23-configured: true,
24-},
25-{
26-channelId: "discord",
27-now: 100_000,
28-channelConnectGraceMs: 10_000,
29-staleEventThresholdMs: 30_000,
30-},
31-);
56+const evaluation = evaluateHealth({
57+running: false,
58+enabled: false,
59+configured: true,
60+});
3261expect(evaluation).toEqual({ healthy: true, reason: "unmanaged" });
3362});
34633564it("uses channel connect grace before flagging disconnected", () => {
36-const evaluation = evaluateChannelHealth(
37-{
38-running: true,
65+const evaluation = evaluateHealth(
66+runningAccount({
3967connected: false,
40-enabled: true,
41-configured: true,
4268lastStartAt: 95_000,
43-},
44-{
45-channelId: "discord",
46-now: 100_000,
47-channelConnectGraceMs: 10_000,
48-staleEventThresholdMs: 30_000,
49-},
69+}),
5070);
5171expect(evaluation).toEqual({ healthy: true, reason: "startup-connect-grace" });
5272});
53735474it("treats active runs as busy even when disconnected", () => {
5575const now = 100_000;
56-const evaluation = evaluateChannelHealth(
57-{
58-running: true,
59-connected: false,
60-enabled: true,
61-configured: true,
62-activeRuns: 1,
63-lastRunActivityAt: now - 30_000,
64-},
65-{
66-channelId: "discord",
67- now,
68-channelConnectGraceMs: 10_000,
69-staleEventThresholdMs: 30_000,
70-},
71-);
76+const evaluation = evaluateHealth(activeRunAccount(now - 30_000), { now });
7277expect(evaluation).toEqual({ healthy: true, reason: "busy" });
7378});
74797580it("flags stale busy channels as stuck when run activity is too old", () => {
7681const now = 100_000;
77-const evaluation = evaluateChannelHealth(
78-{
79-running: true,
80-connected: false,
81-enabled: true,
82-configured: true,
83-activeRuns: 1,
84-lastRunActivityAt: now - 26 * 60_000,
85-},
86-{
87-channelId: "discord",
88- now,
89-channelConnectGraceMs: 10_000,
90-staleEventThresholdMs: 30_000,
91-},
92-);
82+const evaluation = evaluateHealth(activeRunAccount(now - 26 * 60_000), { now });
9383expect(evaluation).toEqual({ healthy: false, reason: "stuck" });
9484});
95859686it("ignores inherited busy flags until current lifecycle reports run activity", () => {
9787const now = 100_000;
98-const evaluation = evaluateChannelHealth(
99-{
100-running: true,
88+const evaluation = evaluateHealth(
89+runningAccount({
10190connected: false,
102-enabled: true,
103-configured: true,
10491lastStartAt: now - 30_000,
10592busy: true,
10693activeRuns: 1,
10794lastRunActivityAt: now - 31_000,
108-},
109-{
110-channelId: "discord",
111- now,
112-channelConnectGraceMs: 10_000,
113-staleEventThresholdMs: 30_000,
114-},
95+}),
96+{ now },
11597);
11698expect(evaluation).toEqual({ healthy: false, reason: "disconnected" });
11799});
118100119101it("flags stale sockets when transport activity ages beyond threshold", () => {
120-const evaluation = evaluateChannelHealth(
121-{
122-running: true,
123-connected: true,
124-enabled: true,
125-configured: true,
126-lastStartAt: 0,
127-lastTransportActivityAt: 0,
128-},
129-{
130-channelId: "discord",
131-now: 100_000,
132-channelConnectGraceMs: 10_000,
133-staleEventThresholdMs: 30_000,
134-},
135-);
102+const evaluation = evaluateHealth(staleTransportAccount());
136103expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
137104});
138105139106it("ignores stale app events without transport activity", () => {
140-const evaluation = evaluateChannelHealth(
141-{
142-running: true,
143-connected: true,
144-enabled: true,
145-configured: true,
107+const evaluation = evaluateHealth(
108+connectedAccount({
146109lastStartAt: 0,
147110lastEventAt: 0,
148-},
149-{
150-channelId: "discord",
151-now: 100_000,
152-channelConnectGraceMs: 10_000,
153-staleEventThresholdMs: 30_000,
154-},
111+}),
155112);
156113expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
157114});
158115159116it("flags stale sockets for telegram polling channels with transport activity", () => {
160-const evaluation = evaluateChannelHealth(
161-{
162-running: true,
163-connected: true,
164-enabled: true,
165-configured: true,
166-lastStartAt: 0,
167-lastTransportActivityAt: 0,
168-mode: "polling",
169-},
170-{
171-channelId: "example",
172-now: 100_000,
173-channelConnectGraceMs: 10_000,
174-staleEventThresholdMs: 30_000,
175-},
176-);
117+const evaluation = evaluateHealth(staleTransportAccount({ mode: "polling" }), {
118+channelId: "example",
119+});
177120expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
178121});
179122180123it("does not special-case malformed channel mode when transport activity is explicit", () => {
181-const evaluation = evaluateChannelHealth(
182-{
183-running: true,
184-connected: true,
185-enabled: true,
186-configured: true,
187-lastStartAt: 0,
188-lastTransportActivityAt: 0,
189-mode: { polling: true } as unknown as string,
190-},
191-{
192-channelId: "example",
193-now: 100_000,
194-channelConnectGraceMs: 10_000,
195-staleEventThresholdMs: 30_000,
196-},
124+const evaluation = evaluateHealth(
125+staleTransportAccount({ mode: { polling: true } as unknown as string }),
126+{ channelId: "example" },
197127);
198128expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
199129});
200130201131it("trusts explicit transport activity instead of webhook mode heuristics", () => {
202-const evaluation = evaluateDiscordHealth({
203-running: true,
204-connected: true,
205-enabled: true,
206-configured: true,
207-lastStartAt: 0,
208-lastTransportActivityAt: 0,
209-mode: "webhook",
210-});
132+const evaluation = evaluateHealth(staleTransportAccount({ mode: "webhook" }));
211133expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
212134});
213135214136it("does not flag stale sockets for channels without transport tracking", () => {
215-const evaluation = evaluateDiscordHealth({
216-running: true,
217-connected: true,
218-enabled: true,
219-configured: true,
220-lastStartAt: 0,
221-lastTransportActivityAt: null,
222-});
137+const evaluation = evaluateHealth(
138+connectedAccount({
139+lastStartAt: 0,
140+lastTransportActivityAt: null,
141+}),
142+);
223143expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
224144});
225145226146it("keeps quiet telegram webhooks healthy when they do not publish transport tracking", () => {
227-const evaluation = evaluateChannelHealth(
228-{
229-running: true,
230-connected: true,
231-enabled: true,
232-configured: true,
147+const evaluation = evaluateHealth(
148+connectedAccount({
233149mode: "webhook",
234150lastStartAt: 0,
235151lastEventAt: 0,
236-},
237-{
238-channelId: "telegram",
239-now: 100_000,
240-channelConnectGraceMs: 10_000,
241-staleEventThresholdMs: 30_000,
242-},
152+}),
153+{ channelId: "telegram" },
243154);
244155expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
245156});
246157247158it("does not flag stale sockets without an active connected socket", () => {
248-const evaluation = evaluateDiscordHealth(
249-{
250-running: true,
251-enabled: true,
252-configured: true,
159+const evaluation = evaluateHealth(
160+runningAccount({
253161lastStartAt: 0,
254162lastTransportActivityAt: 0,
255-},
256-75_000,
257-"slack",
163+}),
164+{ now: 75_000, channelId: "slack" },
258165);
259166expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
260167});
261168262169it("ignores inherited transport timestamps from a previous lifecycle", () => {
263-const evaluation = evaluateDiscordHealth(
264-{
265-running: true,
266-connected: true,
267-enabled: true,
268-configured: true,
269-lastStartAt: 50_000,
270-lastTransportActivityAt: 10_000,
271-},
272-75_000,
273-"slack",
274-);
170+const evaluation = evaluateHealth(inheritedTransportAccount(), {
171+now: 75_000,
172+channelId: "slack",
173+});
275174expect(evaluation).toEqual({ healthy: true, reason: "healthy" });
276175});
277176278177it("flags inherited transport timestamps after the lifecycle exceeds the stale threshold", () => {
279-const evaluation = evaluateChannelHealth(
280-{
281-running: true,
282-connected: true,
283-enabled: true,
284-configured: true,
285-lastStartAt: 50_000,
286-lastTransportActivityAt: 10_000,
287-},
288-{
289-channelId: "slack",
290-now: 140_000,
291-channelConnectGraceMs: 10_000,
292-staleEventThresholdMs: 30_000,
293-},
294-);
178+const evaluation = evaluateHealth(inheritedTransportAccount(), {
179+now: 140_000,
180+channelId: "slack",
181+});
295182expect(evaluation).toEqual({ healthy: false, reason: "stale-socket" });
296183});
297184});
@@ -310,12 +197,9 @@ describe("resolveChannelRestartReason", () => {
310197311198it("maps disconnected to disconnected instead of stuck", () => {
312199const reason = resolveChannelRestartReason(
313-{
314-running: true,
200+runningAccount({
315201connected: false,
316-enabled: true,
317-configured: true,
318-},
202+}),
319203{ healthy: false, reason: "disconnected" },
320204);
321205expect(reason).toBe("disconnected");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。