@checkstack/healthcheck-http-backend 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/package.json +1 -1
- package/src/index.ts +7 -5
- package/src/request-collector.test.ts +212 -0
- package/src/request-collector.ts +186 -0
- package/src/strategy.test.ts +174 -326
- package/src/strategy.ts +97 -419
- package/src/transport-client.ts +29 -0
package/src/strategy.test.ts
CHANGED
|
@@ -1,398 +1,246 @@
|
|
|
1
1
|
import { describe, expect, it, spyOn, afterEach } from "bun:test";
|
|
2
|
-
import { HttpHealthCheckStrategy
|
|
2
|
+
import { HttpHealthCheckStrategy } from "./strategy";
|
|
3
3
|
|
|
4
4
|
describe("HttpHealthCheckStrategy", () => {
|
|
5
5
|
const strategy = new HttpHealthCheckStrategy();
|
|
6
|
-
const defaultConfig: HttpHealthCheckConfig = {
|
|
7
|
-
url: "https://example.com/api",
|
|
8
|
-
method: "GET",
|
|
9
|
-
timeout: 5000,
|
|
10
|
-
};
|
|
11
6
|
|
|
12
7
|
afterEach(() => {
|
|
13
8
|
spyOn(globalThis, "fetch").mockRestore();
|
|
14
9
|
});
|
|
15
10
|
|
|
16
|
-
describe("
|
|
17
|
-
it("should return
|
|
18
|
-
|
|
19
|
-
new Response(null, { status: 200 })
|
|
20
|
-
);
|
|
21
|
-
const result = await strategy.execute(defaultConfig);
|
|
22
|
-
expect(result.status).toBe("healthy");
|
|
23
|
-
expect(result.metadata?.statusCode).toBe(200);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should return healthy for any status without status assertion", async () => {
|
|
27
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
28
|
-
new Response(null, { status: 404 })
|
|
29
|
-
);
|
|
30
|
-
const result = await strategy.execute(defaultConfig);
|
|
31
|
-
// Without assertions, any response is "healthy" if reachable
|
|
32
|
-
expect(result.status).toBe("healthy");
|
|
33
|
-
expect(result.metadata?.statusCode).toBe(404);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe("statusCode assertions", () => {
|
|
38
|
-
it("should pass statusCode equals assertion", async () => {
|
|
39
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
40
|
-
new Response(null, { status: 200 })
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const config: HttpHealthCheckConfig = {
|
|
44
|
-
...defaultConfig,
|
|
45
|
-
assertions: [{ field: "statusCode", operator: "equals", value: 200 }],
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const result = await strategy.execute(config);
|
|
49
|
-
expect(result.status).toBe("healthy");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("should fail statusCode equals assertion when mismatch", async () => {
|
|
53
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
54
|
-
new Response(null, { status: 404 })
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const config: HttpHealthCheckConfig = {
|
|
58
|
-
...defaultConfig,
|
|
59
|
-
assertions: [{ field: "statusCode", operator: "equals", value: 200 }],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const result = await strategy.execute(config);
|
|
63
|
-
expect(result.status).toBe("unhealthy");
|
|
64
|
-
expect(result.message).toContain("statusCode");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("should pass statusCode lessThan assertion", async () => {
|
|
68
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
69
|
-
new Response(null, { status: 201 })
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const config: HttpHealthCheckConfig = {
|
|
73
|
-
...defaultConfig,
|
|
74
|
-
assertions: [{ field: "statusCode", operator: "lessThan", value: 300 }],
|
|
75
|
-
};
|
|
11
|
+
describe("createClient", () => {
|
|
12
|
+
it("should return a connected client", async () => {
|
|
13
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
76
14
|
|
|
77
|
-
|
|
78
|
-
expect(
|
|
15
|
+
expect(connectedClient.client).toBeDefined();
|
|
16
|
+
expect(connectedClient.client.exec).toBeDefined();
|
|
17
|
+
expect(connectedClient.close).toBeDefined();
|
|
79
18
|
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe("responseTime assertions", () => {
|
|
83
|
-
it("should pass responseTime assertion when fast", async () => {
|
|
84
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
85
|
-
new Response(null, { status: 200 })
|
|
86
|
-
);
|
|
87
19
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
assertions: [
|
|
91
|
-
{ field: "responseTime", operator: "lessThan", value: 10000 },
|
|
92
|
-
],
|
|
93
|
-
};
|
|
20
|
+
it("should allow closing the client", async () => {
|
|
21
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
94
22
|
|
|
95
|
-
|
|
96
|
-
expect(result.status).toBe("healthy");
|
|
23
|
+
expect(() => connectedClient.close()).not.toThrow();
|
|
97
24
|
});
|
|
98
25
|
});
|
|
99
26
|
|
|
100
|
-
describe("
|
|
101
|
-
it("should
|
|
27
|
+
describe("client.exec", () => {
|
|
28
|
+
it("should return successful response for valid request", async () => {
|
|
102
29
|
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
103
|
-
new Response(JSON.stringify({}), {
|
|
30
|
+
new Response(JSON.stringify({ status: "ok" }), {
|
|
104
31
|
status: 200,
|
|
105
|
-
|
|
32
|
+
statusText: "OK",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
106
34
|
})
|
|
107
35
|
);
|
|
108
36
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
};
|
|
37
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
38
|
+
const result = await connectedClient.client.exec({
|
|
39
|
+
url: "https://example.com/api",
|
|
40
|
+
method: "GET",
|
|
41
|
+
timeout: 5000,
|
|
42
|
+
});
|
|
115
43
|
|
|
116
|
-
|
|
117
|
-
expect(result.
|
|
118
|
-
|
|
119
|
-
});
|
|
44
|
+
expect(result.statusCode).toBe(200);
|
|
45
|
+
expect(result.statusText).toBe("OK");
|
|
46
|
+
expect(result.contentType).toContain("application/json");
|
|
120
47
|
|
|
121
|
-
|
|
122
|
-
it("should pass header exists assertion", async () => {
|
|
123
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
124
|
-
new Response(null, {
|
|
125
|
-
status: 200,
|
|
126
|
-
headers: { "X-Request-Id": "abc123" },
|
|
127
|
-
})
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const config: HttpHealthCheckConfig = {
|
|
131
|
-
...defaultConfig,
|
|
132
|
-
assertions: [
|
|
133
|
-
{ field: "header", headerName: "X-Request-Id", operator: "exists" },
|
|
134
|
-
],
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const result = await strategy.execute(config);
|
|
138
|
-
expect(result.status).toBe("healthy");
|
|
48
|
+
connectedClient.close();
|
|
139
49
|
});
|
|
140
50
|
|
|
141
|
-
it("should
|
|
51
|
+
it("should return 404 status for not found", async () => {
|
|
142
52
|
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
143
|
-
new Response(null, { status:
|
|
53
|
+
new Response(null, { status: 404, statusText: "Not Found" })
|
|
144
54
|
);
|
|
145
55
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const result = await strategy.execute(config);
|
|
154
|
-
expect(result.status).toBe("unhealthy");
|
|
155
|
-
expect(result.message).toContain("X-Missing");
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("should pass header equals assertion", async () => {
|
|
159
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
160
|
-
new Response(null, {
|
|
161
|
-
status: 200,
|
|
162
|
-
headers: { "Cache-Control": "no-cache" },
|
|
163
|
-
})
|
|
164
|
-
);
|
|
56
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
57
|
+
const result = await connectedClient.client.exec({
|
|
58
|
+
url: "https://example.com/notfound",
|
|
59
|
+
method: "GET",
|
|
60
|
+
timeout: 5000,
|
|
61
|
+
});
|
|
165
62
|
|
|
166
|
-
|
|
167
|
-
...defaultConfig,
|
|
168
|
-
assertions: [
|
|
169
|
-
{
|
|
170
|
-
field: "header",
|
|
171
|
-
headerName: "Cache-Control",
|
|
172
|
-
operator: "equals",
|
|
173
|
-
value: "no-cache",
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
};
|
|
63
|
+
expect(result.statusCode).toBe(404);
|
|
177
64
|
|
|
178
|
-
|
|
179
|
-
expect(result.status).toBe("healthy");
|
|
65
|
+
connectedClient.close();
|
|
180
66
|
});
|
|
181
|
-
});
|
|
182
67
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
spyOn(globalThis, "fetch").
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const config: HttpHealthCheckConfig = {
|
|
193
|
-
...defaultConfig,
|
|
194
|
-
assertions: [
|
|
195
|
-
{
|
|
196
|
-
field: "jsonPath",
|
|
197
|
-
path: "$.status",
|
|
198
|
-
operator: "equals",
|
|
199
|
-
value: "UP",
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const result = await strategy.execute(config);
|
|
205
|
-
expect(result.status).toBe("healthy");
|
|
206
|
-
});
|
|
68
|
+
it("should send custom headers with request", async () => {
|
|
69
|
+
let capturedHeaders: Record<string, string> | undefined;
|
|
70
|
+
spyOn(globalThis, "fetch").mockImplementation((async (
|
|
71
|
+
_url: RequestInfo | URL,
|
|
72
|
+
options?: RequestInit
|
|
73
|
+
) => {
|
|
74
|
+
capturedHeaders = options?.headers as Record<string, string>;
|
|
75
|
+
return new Response(null, { status: 200 });
|
|
76
|
+
}) as unknown as typeof fetch);
|
|
207
77
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
78
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
79
|
+
await connectedClient.client.exec({
|
|
80
|
+
url: "https://example.com/api",
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: "Bearer my-token",
|
|
84
|
+
"X-Custom-Header": "custom-value",
|
|
85
|
+
},
|
|
86
|
+
timeout: 5000,
|
|
87
|
+
});
|
|
215
88
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{
|
|
220
|
-
field: "jsonPath",
|
|
221
|
-
path: "$.status",
|
|
222
|
-
operator: "equals",
|
|
223
|
-
value: "UP",
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
};
|
|
89
|
+
expect(capturedHeaders).toBeDefined();
|
|
90
|
+
expect(capturedHeaders?.["Authorization"]).toBe("Bearer my-token");
|
|
91
|
+
expect(capturedHeaders?.["X-Custom-Header"]).toBe("custom-value");
|
|
227
92
|
|
|
228
|
-
|
|
229
|
-
expect(result.status).toBe("unhealthy");
|
|
230
|
-
expect(result.message).toContain("Actual");
|
|
93
|
+
connectedClient.close();
|
|
231
94
|
});
|
|
232
95
|
|
|
233
|
-
it("should
|
|
96
|
+
it("should return JSON body as string", async () => {
|
|
97
|
+
const responseBody = { foo: "bar", count: 42 };
|
|
234
98
|
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
235
|
-
new Response(JSON.stringify(
|
|
99
|
+
new Response(JSON.stringify(responseBody), {
|
|
236
100
|
status: 200,
|
|
237
101
|
headers: { "Content-Type": "application/json" },
|
|
238
102
|
})
|
|
239
103
|
);
|
|
240
104
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const result = await strategy.execute(config);
|
|
249
|
-
expect(result.status).toBe("healthy");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("should fail jsonPath exists assertion when path not found", async () => {
|
|
253
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
254
|
-
new Response(JSON.stringify({ other: "data" }), {
|
|
255
|
-
status: 200,
|
|
256
|
-
headers: { "Content-Type": "application/json" },
|
|
257
|
-
})
|
|
258
|
-
);
|
|
105
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
106
|
+
const result = await connectedClient.client.exec({
|
|
107
|
+
url: "https://example.com/api",
|
|
108
|
+
method: "GET",
|
|
109
|
+
timeout: 5000,
|
|
110
|
+
});
|
|
259
111
|
|
|
260
|
-
|
|
261
|
-
...defaultConfig,
|
|
262
|
-
assertions: [
|
|
263
|
-
{ field: "jsonPath", path: "$.missing", operator: "exists" },
|
|
264
|
-
],
|
|
265
|
-
};
|
|
112
|
+
expect(result.body).toBe(JSON.stringify(responseBody));
|
|
266
113
|
|
|
267
|
-
|
|
268
|
-
expect(result.status).toBe("unhealthy");
|
|
114
|
+
connectedClient.close();
|
|
269
115
|
});
|
|
270
116
|
|
|
271
|
-
it("should
|
|
117
|
+
it("should handle text body", async () => {
|
|
272
118
|
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
273
|
-
new Response(
|
|
119
|
+
new Response("Hello World", {
|
|
274
120
|
status: 200,
|
|
275
|
-
headers: { "Content-Type": "
|
|
121
|
+
headers: { "Content-Type": "text/plain" },
|
|
276
122
|
})
|
|
277
123
|
);
|
|
278
124
|
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
operator: "contains",
|
|
286
|
-
value: "Hello",
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const result = await strategy.execute(config);
|
|
292
|
-
expect(result.status).toBe("healthy");
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it("should pass jsonPath matches (regex) assertion", async () => {
|
|
296
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
297
|
-
new Response(JSON.stringify({ id: "abc-123" }), {
|
|
298
|
-
status: 200,
|
|
299
|
-
headers: { "Content-Type": "application/json" },
|
|
300
|
-
})
|
|
301
|
-
);
|
|
125
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
126
|
+
const result = await connectedClient.client.exec({
|
|
127
|
+
url: "https://example.com/api",
|
|
128
|
+
method: "GET",
|
|
129
|
+
timeout: 5000,
|
|
130
|
+
});
|
|
302
131
|
|
|
303
|
-
|
|
304
|
-
...defaultConfig,
|
|
305
|
-
assertions: [
|
|
306
|
-
{
|
|
307
|
-
field: "jsonPath",
|
|
308
|
-
path: "$.id",
|
|
309
|
-
operator: "matches",
|
|
310
|
-
value: "^[a-z]{3}-\\d{3}$",
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
};
|
|
132
|
+
expect(result.body).toBe("Hello World");
|
|
314
133
|
|
|
315
|
-
|
|
316
|
-
expect(result.status).toBe("healthy");
|
|
134
|
+
connectedClient.close();
|
|
317
135
|
});
|
|
318
136
|
|
|
319
|
-
it("should
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
...defaultConfig,
|
|
329
|
-
assertions: [{ field: "jsonPath", path: "$.id", operator: "exists" }],
|
|
330
|
-
};
|
|
137
|
+
it("should send POST body", async () => {
|
|
138
|
+
let capturedBody: string | undefined;
|
|
139
|
+
spyOn(globalThis, "fetch").mockImplementation((async (
|
|
140
|
+
_url: RequestInfo | URL,
|
|
141
|
+
options?: RequestInit
|
|
142
|
+
) => {
|
|
143
|
+
capturedBody = options?.body as string;
|
|
144
|
+
return new Response(null, { status: 201 });
|
|
145
|
+
}) as unknown as typeof fetch);
|
|
331
146
|
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
147
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
148
|
+
await connectedClient.client.exec({
|
|
149
|
+
url: "https://example.com/api",
|
|
150
|
+
method: "POST",
|
|
151
|
+
body: JSON.stringify({ name: "test" }),
|
|
152
|
+
timeout: 5000,
|
|
153
|
+
});
|
|
337
154
|
|
|
338
|
-
|
|
339
|
-
it("should pass multiple assertion types", async () => {
|
|
340
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
341
|
-
new Response(JSON.stringify({ healthy: true }), {
|
|
342
|
-
status: 200,
|
|
343
|
-
headers: {
|
|
344
|
-
"Content-Type": "application/json",
|
|
345
|
-
"X-Request-Id": "test-123",
|
|
346
|
-
},
|
|
347
|
-
})
|
|
348
|
-
);
|
|
155
|
+
expect(capturedBody).toBe('{"name":"test"}');
|
|
349
156
|
|
|
350
|
-
|
|
351
|
-
...defaultConfig,
|
|
352
|
-
assertions: [
|
|
353
|
-
{ field: "statusCode", operator: "equals", value: 200 },
|
|
354
|
-
{ field: "responseTime", operator: "lessThan", value: 10000 },
|
|
355
|
-
{ field: "contentType", operator: "contains", value: "json" },
|
|
356
|
-
{ field: "header", headerName: "X-Request-Id", operator: "exists" },
|
|
357
|
-
{
|
|
358
|
-
field: "jsonPath",
|
|
359
|
-
path: "$.healthy",
|
|
360
|
-
operator: "equals",
|
|
361
|
-
value: "true",
|
|
362
|
-
},
|
|
363
|
-
],
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const result = await strategy.execute(config);
|
|
367
|
-
expect(result.status).toBe("healthy");
|
|
368
|
-
expect(result.message).toContain("5 assertion");
|
|
157
|
+
connectedClient.close();
|
|
369
158
|
});
|
|
370
|
-
});
|
|
371
159
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
let capturedHeaders: Record<string, string> | undefined;
|
|
160
|
+
it("should use correct HTTP method", async () => {
|
|
161
|
+
let capturedMethod: string | undefined;
|
|
375
162
|
spyOn(globalThis, "fetch").mockImplementation((async (
|
|
376
163
|
_url: RequestInfo | URL,
|
|
377
164
|
options?: RequestInit
|
|
378
165
|
) => {
|
|
379
|
-
|
|
166
|
+
capturedMethod = options?.method;
|
|
380
167
|
return new Response(null, { status: 200 });
|
|
381
168
|
}) as unknown as typeof fetch);
|
|
382
169
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
};
|
|
170
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
171
|
+
await connectedClient.client.exec({
|
|
172
|
+
url: "https://example.com/api",
|
|
173
|
+
method: "DELETE",
|
|
174
|
+
timeout: 5000,
|
|
175
|
+
});
|
|
390
176
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
177
|
+
expect(capturedMethod).toBe("DELETE");
|
|
178
|
+
|
|
179
|
+
connectedClient.close();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("aggregateResult", () => {
|
|
184
|
+
it("should count errors correctly", () => {
|
|
185
|
+
const runs = [
|
|
186
|
+
{
|
|
187
|
+
id: "1",
|
|
188
|
+
status: "unhealthy" as const,
|
|
189
|
+
latencyMs: 100,
|
|
190
|
+
checkId: "c1",
|
|
191
|
+
timestamp: new Date(),
|
|
192
|
+
metadata: {
|
|
193
|
+
error: "Connection refused",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "2",
|
|
198
|
+
status: "healthy" as const,
|
|
199
|
+
latencyMs: 150,
|
|
200
|
+
checkId: "c1",
|
|
201
|
+
timestamp: new Date(),
|
|
202
|
+
metadata: {},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "3",
|
|
206
|
+
status: "unhealthy" as const,
|
|
207
|
+
latencyMs: 120,
|
|
208
|
+
checkId: "c1",
|
|
209
|
+
timestamp: new Date(),
|
|
210
|
+
metadata: {
|
|
211
|
+
error: "Timeout",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const aggregated = strategy.aggregateResult(runs);
|
|
217
|
+
|
|
218
|
+
expect(aggregated.errorCount).toBe(2);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should return zero errors when all runs succeed", () => {
|
|
222
|
+
const runs = [
|
|
223
|
+
{
|
|
224
|
+
id: "1",
|
|
225
|
+
status: "healthy" as const,
|
|
226
|
+
latencyMs: 100,
|
|
227
|
+
checkId: "c1",
|
|
228
|
+
timestamp: new Date(),
|
|
229
|
+
metadata: {},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "2",
|
|
233
|
+
status: "healthy" as const,
|
|
234
|
+
latencyMs: 150,
|
|
235
|
+
checkId: "c1",
|
|
236
|
+
timestamp: new Date(),
|
|
237
|
+
metadata: {},
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const aggregated = strategy.aggregateResult(runs);
|
|
242
|
+
|
|
243
|
+
expect(aggregated.errorCount).toBe(0);
|
|
396
244
|
});
|
|
397
245
|
});
|
|
398
246
|
});
|