@checkstack/healthcheck-http-backend 0.0.3 → 0.1.0
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 +57 -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 +182 -324
- package/src/strategy.ts +106 -401
- package/src/transport-client.ts +29 -0
package/src/strategy.test.ts
CHANGED
|
@@ -1,398 +1,256 @@
|
|
|
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
|
-
});
|
|
11
|
+
describe("createClient", () => {
|
|
12
|
+
it("should return a connected client", async () => {
|
|
13
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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);
|
|
15
|
+
expect(connectedClient.client).toBeDefined();
|
|
16
|
+
expect(connectedClient.client.exec).toBeDefined();
|
|
17
|
+
expect(connectedClient.close).toBeDefined();
|
|
34
18
|
});
|
|
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
19
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
assertions: [{ field: "statusCode", operator: "equals", value: 200 }],
|
|
46
|
-
};
|
|
20
|
+
it("should allow closing the client", async () => {
|
|
21
|
+
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
47
22
|
|
|
48
|
-
|
|
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
|
-
};
|
|
76
|
-
|
|
77
|
-
const result = await strategy.execute(config);
|
|
78
|
-
expect(result.status).toBe("healthy");
|
|
79
|
-
});
|
|
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
|
-
|
|
88
|
-
const config: HttpHealthCheckConfig = {
|
|
89
|
-
...defaultConfig,
|
|
90
|
-
assertions: [
|
|
91
|
-
{ field: "responseTime", operator: "lessThan", value: 10000 },
|
|
92
|
-
],
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const result = await strategy.execute(config);
|
|
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
|
-
};
|
|
115
|
-
|
|
116
|
-
const result = await strategy.execute(config);
|
|
117
|
-
expect(result.status).toBe("healthy");
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe("header assertions", () => {
|
|
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
|
-
);
|
|
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
|
+
});
|
|
129
43
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{ field: "header", headerName: "X-Request-Id", operator: "exists" },
|
|
134
|
-
],
|
|
135
|
-
};
|
|
44
|
+
expect(result.statusCode).toBe(200);
|
|
45
|
+
expect(result.statusText).toBe("OK");
|
|
46
|
+
expect(result.contentType).toContain("application/json");
|
|
136
47
|
|
|
137
|
-
|
|
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
|
-
};
|
|
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
|
+
});
|
|
152
62
|
|
|
153
|
-
|
|
154
|
-
expect(result.status).toBe("unhealthy");
|
|
155
|
-
expect(result.message).toContain("X-Missing");
|
|
156
|
-
});
|
|
63
|
+
expect(result.statusCode).toBe(404);
|
|
157
64
|
|
|
158
|
-
|
|
159
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
160
|
-
new Response(null, {
|
|
161
|
-
status: 200,
|
|
162
|
-
headers: { "Cache-Control": "no-cache" },
|
|
163
|
-
})
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
const config: HttpHealthCheckConfig = {
|
|
167
|
-
...defaultConfig,
|
|
168
|
-
assertions: [
|
|
169
|
-
{
|
|
170
|
-
field: "header",
|
|
171
|
-
headerName: "Cache-Control",
|
|
172
|
-
operator: "equals",
|
|
173
|
-
value: "no-cache",
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const result = await strategy.execute(config);
|
|
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
|
-
};
|
|
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);
|
|
203
77
|
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
});
|
|
207
88
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
status: 200,
|
|
212
|
-
headers: { "Content-Type": "application/json" },
|
|
213
|
-
})
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const config: HttpHealthCheckConfig = {
|
|
217
|
-
...defaultConfig,
|
|
218
|
-
assertions: [
|
|
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
|
-
};
|
|
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
|
+
});
|
|
247
111
|
|
|
248
|
-
|
|
249
|
-
expect(result.status).toBe("healthy");
|
|
250
|
-
});
|
|
112
|
+
expect(result.body).toBe(JSON.stringify(responseBody));
|
|
251
113
|
|
|
252
|
-
|
|
253
|
-
spyOn(globalThis, "fetch").mockResolvedValue(
|
|
254
|
-
new Response(JSON.stringify({ other: "data" }), {
|
|
255
|
-
status: 200,
|
|
256
|
-
headers: { "Content-Type": "application/json" },
|
|
257
|
-
})
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
const config: HttpHealthCheckConfig = {
|
|
261
|
-
...defaultConfig,
|
|
262
|
-
assertions: [
|
|
263
|
-
{ field: "jsonPath", path: "$.missing", operator: "exists" },
|
|
264
|
-
],
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const result = await strategy.execute(config);
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
};
|
|
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
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.body).toBe("Hello World");
|
|
290
133
|
|
|
291
|
-
|
|
292
|
-
expect(result.status).toBe("healthy");
|
|
134
|
+
connectedClient.close();
|
|
293
135
|
});
|
|
294
136
|
|
|
295
|
-
it("should
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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);
|
|
302
146
|
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
],
|
|
313
|
-
};
|
|
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
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(capturedBody).toBe('{"name":"test"}');
|
|
314
156
|
|
|
315
|
-
|
|
316
|
-
expect(result.status).toBe("healthy");
|
|
157
|
+
connectedClient.close();
|
|
317
158
|
});
|
|
318
159
|
|
|
319
|
-
it("should
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
160
|
+
it("should use correct HTTP method", async () => {
|
|
161
|
+
let capturedMethod: string | undefined;
|
|
162
|
+
spyOn(globalThis, "fetch").mockImplementation((async (
|
|
163
|
+
_url: RequestInfo | URL,
|
|
164
|
+
options?: RequestInit
|
|
165
|
+
) => {
|
|
166
|
+
capturedMethod = options?.method;
|
|
167
|
+
return new Response(null, { status: 200 });
|
|
168
|
+
}) as unknown as typeof fetch);
|
|
169
|
+
|
|
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
|
+
});
|
|
326
176
|
|
|
327
|
-
|
|
328
|
-
...defaultConfig,
|
|
329
|
-
assertions: [{ field: "jsonPath", path: "$.id", operator: "exists" }],
|
|
330
|
-
};
|
|
177
|
+
expect(capturedMethod).toBe("DELETE");
|
|
331
178
|
|
|
332
|
-
|
|
333
|
-
expect(result.status).toBe("unhealthy");
|
|
334
|
-
expect(result.message).toContain("not valid JSON");
|
|
179
|
+
connectedClient.close();
|
|
335
180
|
});
|
|
336
181
|
});
|
|
337
182
|
|
|
338
|
-
describe("
|
|
339
|
-
it("should
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
183
|
+
describe("aggregateResult", () => {
|
|
184
|
+
it("should calculate status code counts correctly", () => {
|
|
185
|
+
const runs = [
|
|
186
|
+
{
|
|
187
|
+
id: "1",
|
|
188
|
+
status: "healthy" as const,
|
|
189
|
+
latencyMs: 100,
|
|
190
|
+
checkId: "c1",
|
|
191
|
+
timestamp: new Date(),
|
|
192
|
+
metadata: {
|
|
193
|
+
statusCode: 200,
|
|
346
194
|
},
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "2",
|
|
198
|
+
status: "healthy" as const,
|
|
199
|
+
latencyMs: 150,
|
|
200
|
+
checkId: "c1",
|
|
201
|
+
timestamp: new Date(),
|
|
202
|
+
metadata: {
|
|
203
|
+
statusCode: 200,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "3",
|
|
208
|
+
status: "healthy" as const,
|
|
209
|
+
latencyMs: 120,
|
|
210
|
+
checkId: "c1",
|
|
211
|
+
timestamp: new Date(),
|
|
212
|
+
metadata: {
|
|
213
|
+
statusCode: 404,
|
|
362
214
|
},
|
|
363
|
-
|
|
364
|
-
|
|
215
|
+
},
|
|
216
|
+
];
|
|
365
217
|
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
expect(
|
|
218
|
+
const aggregated = strategy.aggregateResult(runs);
|
|
219
|
+
|
|
220
|
+
expect(aggregated.statusCodeCounts["200"]).toBe(2);
|
|
221
|
+
expect(aggregated.statusCodeCounts["404"]).toBe(1);
|
|
222
|
+
expect(aggregated.successRate).toBe(100);
|
|
369
223
|
});
|
|
370
|
-
});
|
|
371
224
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
225
|
+
it("should count errors", () => {
|
|
226
|
+
const runs = [
|
|
227
|
+
{
|
|
228
|
+
id: "1",
|
|
229
|
+
status: "unhealthy" as const,
|
|
230
|
+
latencyMs: 100,
|
|
231
|
+
checkId: "c1",
|
|
232
|
+
timestamp: new Date(),
|
|
233
|
+
metadata: {
|
|
234
|
+
error: "Connection refused",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "2",
|
|
239
|
+
status: "healthy" as const,
|
|
240
|
+
latencyMs: 150,
|
|
241
|
+
checkId: "c1",
|
|
242
|
+
timestamp: new Date(),
|
|
243
|
+
metadata: {
|
|
244
|
+
statusCode: 200,
|
|
245
|
+
responseTime: 100,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
];
|
|
382
249
|
|
|
383
|
-
const
|
|
384
|
-
...defaultConfig,
|
|
385
|
-
headers: [
|
|
386
|
-
{ name: "Authorization", value: "Bearer my-token" },
|
|
387
|
-
{ name: "X-Custom-Header", value: "custom-value" },
|
|
388
|
-
],
|
|
389
|
-
};
|
|
250
|
+
const aggregated = strategy.aggregateResult(runs);
|
|
390
251
|
|
|
391
|
-
|
|
392
|
-
expect(
|
|
393
|
-
expect(capturedHeaders).toBeDefined();
|
|
394
|
-
expect(capturedHeaders?.["Authorization"]).toBe("Bearer my-token");
|
|
395
|
-
expect(capturedHeaders?.["X-Custom-Header"]).toBe("custom-value");
|
|
252
|
+
expect(aggregated.errorCount).toBe(1);
|
|
253
|
+
expect(aggregated.successRate).toBe(50);
|
|
396
254
|
});
|
|
397
255
|
});
|
|
398
256
|
});
|