@checkstack/integration-webhook-backend 0.0.34 → 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 +116 -0
- package/package.json +7 -6
- package/src/automations.test.ts +299 -0
- package/src/automations.ts +193 -0
- package/src/index.ts +15 -22
- package/tsconfig.json +4 -4
- package/src/provider.test.ts +0 -513
- package/src/provider.ts +0 -347
package/tsconfig.json
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
],
|
|
6
6
|
"references": [
|
|
7
7
|
{
|
|
8
|
-
"path": "../../core/backend
|
|
8
|
+
"path": "../../core/automation-backend"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
|
-
"path": "../../core/
|
|
11
|
+
"path": "../../core/backend-api"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
"path": "../../core/
|
|
14
|
+
"path": "../../core/common"
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
-
"path": "../../core/
|
|
17
|
+
"path": "../../core/test-utils-backend"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
}
|
package/src/provider.test.ts
DELETED
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, mock, spyOn } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
webhookProvider,
|
|
4
|
-
webhookConfigSchemaV1,
|
|
5
|
-
type WebhookConfig,
|
|
6
|
-
} from "./provider";
|
|
7
|
-
import type { IntegrationDeliveryContext } from "@checkstack/integration-backend";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Unit tests for the Webhook Integration Provider.
|
|
11
|
-
*
|
|
12
|
-
* Tests cover:
|
|
13
|
-
* - Config schema validation
|
|
14
|
-
* - Successful webhook delivery
|
|
15
|
-
* - Authentication methods (bearer, basic, header)
|
|
16
|
-
* - Error handling and retry logic
|
|
17
|
-
* - Test connection functionality
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
// Mock logger
|
|
21
|
-
const mockLogger = {
|
|
22
|
-
debug: mock(() => {}),
|
|
23
|
-
info: mock(() => {}),
|
|
24
|
-
warn: mock(() => {}),
|
|
25
|
-
error: mock(() => {}),
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Create a test delivery context
|
|
29
|
-
function createTestContext(
|
|
30
|
-
configOverrides: Partial<WebhookConfig> = {},
|
|
31
|
-
): IntegrationDeliveryContext<WebhookConfig> {
|
|
32
|
-
const defaultConfig: WebhookConfig = {
|
|
33
|
-
url: "https://example.com/webhook",
|
|
34
|
-
method: "POST",
|
|
35
|
-
contentType: "application/json",
|
|
36
|
-
authType: "none",
|
|
37
|
-
timeout: 10_000,
|
|
38
|
-
...configOverrides,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
event: {
|
|
43
|
-
eventId: "test-plugin.incident.created",
|
|
44
|
-
payload: { incidentId: "inc-123", severity: "critical" },
|
|
45
|
-
timestamp: new Date().toISOString(),
|
|
46
|
-
deliveryId: "del-456",
|
|
47
|
-
},
|
|
48
|
-
subscription: {
|
|
49
|
-
id: "sub-789",
|
|
50
|
-
name: "Test Subscription",
|
|
51
|
-
},
|
|
52
|
-
providerConfig: defaultConfig,
|
|
53
|
-
logger: mockLogger,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
describe("WebhookProvider", () => {
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
mockLogger.debug.mockClear();
|
|
60
|
-
mockLogger.info.mockClear();
|
|
61
|
-
mockLogger.warn.mockClear();
|
|
62
|
-
mockLogger.error.mockClear();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
66
|
-
// Provider Metadata
|
|
67
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
68
|
-
|
|
69
|
-
describe("metadata", () => {
|
|
70
|
-
it("has correct basic metadata", () => {
|
|
71
|
-
expect(webhookProvider.id).toBe("webhook");
|
|
72
|
-
expect(webhookProvider.displayName).toBe("Webhook");
|
|
73
|
-
expect(webhookProvider.description).toContain("HTTP POST");
|
|
74
|
-
expect(webhookProvider.icon).toBe("Webhook");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("has a versioned config schema", () => {
|
|
78
|
-
expect(webhookProvider.config).toBeDefined();
|
|
79
|
-
expect(webhookProvider.config.version).toBe(1);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
84
|
-
// Config Schema Validation
|
|
85
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
describe("config schema", () => {
|
|
88
|
-
it("requires valid URL", () => {
|
|
89
|
-
expect(() => {
|
|
90
|
-
webhookConfigSchemaV1.parse({
|
|
91
|
-
url: "not-a-url",
|
|
92
|
-
});
|
|
93
|
-
}).toThrow();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("accepts valid URL", () => {
|
|
97
|
-
const result = webhookConfigSchemaV1.parse({
|
|
98
|
-
url: "https://example.com/webhook",
|
|
99
|
-
});
|
|
100
|
-
expect(result.url).toBe("https://example.com/webhook");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("applies default values", () => {
|
|
104
|
-
const result = webhookConfigSchemaV1.parse({
|
|
105
|
-
url: "https://example.com/webhook",
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
expect(result.method).toBe("POST");
|
|
109
|
-
expect(result.contentType).toBe("application/json");
|
|
110
|
-
expect(result.authType).toBe("none");
|
|
111
|
-
expect(result.timeout).toBe(10_000);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("validates method enum", () => {
|
|
115
|
-
expect(() => {
|
|
116
|
-
webhookConfigSchemaV1.parse({
|
|
117
|
-
url: "https://example.com",
|
|
118
|
-
method: "DELETE", // Not allowed
|
|
119
|
-
});
|
|
120
|
-
}).toThrow();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("allows valid methods", () => {
|
|
124
|
-
for (const method of ["POST", "PUT", "PATCH"] as const) {
|
|
125
|
-
const result = webhookConfigSchemaV1.parse({
|
|
126
|
-
url: "https://example.com",
|
|
127
|
-
method,
|
|
128
|
-
});
|
|
129
|
-
expect(result.method).toBe(method);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("validates timeout range", () => {
|
|
134
|
-
expect(() => {
|
|
135
|
-
webhookConfigSchemaV1.parse({
|
|
136
|
-
url: "https://example.com",
|
|
137
|
-
timeout: 500, // Too short
|
|
138
|
-
});
|
|
139
|
-
}).toThrow();
|
|
140
|
-
|
|
141
|
-
expect(() => {
|
|
142
|
-
webhookConfigSchemaV1.parse({
|
|
143
|
-
url: "https://example.com",
|
|
144
|
-
timeout: 100_000, // Too long
|
|
145
|
-
});
|
|
146
|
-
}).toThrow();
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
151
|
-
// Delivery - Basic
|
|
152
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
describe("deliver - basic", () => {
|
|
155
|
-
it("makes HTTP request with correct payload structure", async () => {
|
|
156
|
-
let capturedBody: string | undefined;
|
|
157
|
-
let capturedHeaders: Record<string, string> | undefined;
|
|
158
|
-
|
|
159
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
160
|
-
_url: RequestInfo | URL,
|
|
161
|
-
options?: RequestInit,
|
|
162
|
-
) => {
|
|
163
|
-
capturedBody = options?.body as string;
|
|
164
|
-
capturedHeaders = options?.headers as Record<string, string>;
|
|
165
|
-
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
166
|
-
}) as unknown as typeof fetch);
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
const context = createTestContext();
|
|
170
|
-
const result = await webhookProvider.deliver(context);
|
|
171
|
-
|
|
172
|
-
expect(result.success).toBe(true);
|
|
173
|
-
expect(capturedBody).toBeDefined();
|
|
174
|
-
|
|
175
|
-
const parsedBody = JSON.parse(capturedBody!);
|
|
176
|
-
expect(parsedBody.id).toBe("del-456");
|
|
177
|
-
expect(parsedBody.eventType).toBe("test-plugin.incident.created");
|
|
178
|
-
expect(parsedBody.subscription.id).toBe("sub-789");
|
|
179
|
-
expect(parsedBody.data).toEqual({
|
|
180
|
-
incidentId: "inc-123",
|
|
181
|
-
severity: "critical",
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
expect(capturedHeaders?.["Content-Type"]).toBe("application/json");
|
|
185
|
-
expect(capturedHeaders?.["X-Delivery-Id"]).toBe("del-456");
|
|
186
|
-
expect(capturedHeaders?.["X-Event-Type"]).toBe(
|
|
187
|
-
"test-plugin.incident.created",
|
|
188
|
-
);
|
|
189
|
-
} finally {
|
|
190
|
-
mockFetch.mockRestore();
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("returns external ID from response if present", async () => {
|
|
195
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
196
|
-
(async () => {
|
|
197
|
-
return new Response(JSON.stringify({ id: "external-id-123" }), {
|
|
198
|
-
status: 200,
|
|
199
|
-
});
|
|
200
|
-
}) as unknown as typeof fetch,
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const context = createTestContext();
|
|
205
|
-
const result = await webhookProvider.deliver(context);
|
|
206
|
-
|
|
207
|
-
expect(result.success).toBe(true);
|
|
208
|
-
expect(result.externalId).toBe("external-id-123");
|
|
209
|
-
} finally {
|
|
210
|
-
mockFetch.mockRestore();
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it("handles non-JSON response gracefully", async () => {
|
|
215
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
216
|
-
(async () => {
|
|
217
|
-
return new Response("OK", { status: 200 });
|
|
218
|
-
}) as unknown as typeof fetch,
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const context = createTestContext();
|
|
223
|
-
const result = await webhookProvider.deliver(context);
|
|
224
|
-
|
|
225
|
-
expect(result.success).toBe(true);
|
|
226
|
-
expect(result.externalId).toBeUndefined();
|
|
227
|
-
} finally {
|
|
228
|
-
mockFetch.mockRestore();
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
234
|
-
// Delivery - Authentication
|
|
235
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
236
|
-
|
|
237
|
-
describe("deliver - authentication", () => {
|
|
238
|
-
it("adds Bearer token header", async () => {
|
|
239
|
-
let capturedHeaders: Record<string, string> | undefined;
|
|
240
|
-
|
|
241
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
242
|
-
_url: RequestInfo | URL,
|
|
243
|
-
options?: RequestInit,
|
|
244
|
-
) => {
|
|
245
|
-
capturedHeaders = options?.headers as Record<string, string>;
|
|
246
|
-
return new Response("OK", { status: 200 });
|
|
247
|
-
}) as unknown as typeof fetch);
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
const context = createTestContext({
|
|
251
|
-
authType: "bearer",
|
|
252
|
-
bearerToken: "my-secret-token",
|
|
253
|
-
});
|
|
254
|
-
await webhookProvider.deliver(context);
|
|
255
|
-
|
|
256
|
-
expect(capturedHeaders?.["Authorization"]).toBe(
|
|
257
|
-
"Bearer my-secret-token",
|
|
258
|
-
);
|
|
259
|
-
} finally {
|
|
260
|
-
mockFetch.mockRestore();
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it("adds Basic auth header", async () => {
|
|
265
|
-
let capturedHeaders: Record<string, string> | undefined;
|
|
266
|
-
|
|
267
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
268
|
-
_url: RequestInfo | URL,
|
|
269
|
-
options?: RequestInit,
|
|
270
|
-
) => {
|
|
271
|
-
capturedHeaders = options?.headers as Record<string, string>;
|
|
272
|
-
return new Response("OK", { status: 200 });
|
|
273
|
-
}) as unknown as typeof fetch);
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const context = createTestContext({
|
|
277
|
-
authType: "basic",
|
|
278
|
-
basicUsername: "user",
|
|
279
|
-
basicPassword: "pass",
|
|
280
|
-
});
|
|
281
|
-
await webhookProvider.deliver(context);
|
|
282
|
-
|
|
283
|
-
// user:pass in base64
|
|
284
|
-
const expectedAuth = `Basic ${Buffer.from("user:pass").toString(
|
|
285
|
-
"base64",
|
|
286
|
-
)}`;
|
|
287
|
-
expect(capturedHeaders?.["Authorization"]).toBe(expectedAuth);
|
|
288
|
-
} finally {
|
|
289
|
-
mockFetch.mockRestore();
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("adds custom auth header", async () => {
|
|
294
|
-
let capturedHeaders: Record<string, string> | undefined;
|
|
295
|
-
|
|
296
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
297
|
-
_url: RequestInfo | URL,
|
|
298
|
-
options?: RequestInit,
|
|
299
|
-
) => {
|
|
300
|
-
capturedHeaders = options?.headers as Record<string, string>;
|
|
301
|
-
return new Response("OK", { status: 200 });
|
|
302
|
-
}) as unknown as typeof fetch);
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
const context = createTestContext({
|
|
306
|
-
authType: "header",
|
|
307
|
-
authHeaderName: "X-API-Key",
|
|
308
|
-
authHeaderValue: "api-key-123",
|
|
309
|
-
});
|
|
310
|
-
await webhookProvider.deliver(context);
|
|
311
|
-
|
|
312
|
-
expect(capturedHeaders?.["X-API-Key"]).toBe("api-key-123");
|
|
313
|
-
} finally {
|
|
314
|
-
mockFetch.mockRestore();
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it("adds custom headers", async () => {
|
|
319
|
-
let capturedHeaders: Record<string, string> | undefined;
|
|
320
|
-
|
|
321
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
322
|
-
_url: RequestInfo | URL,
|
|
323
|
-
options?: RequestInit,
|
|
324
|
-
) => {
|
|
325
|
-
capturedHeaders = options?.headers as Record<string, string>;
|
|
326
|
-
return new Response("OK", { status: 200 });
|
|
327
|
-
}) as unknown as typeof fetch);
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
const context = createTestContext({
|
|
331
|
-
customHeaders: [
|
|
332
|
-
{ name: "X-Custom-Header", value: "custom-value" },
|
|
333
|
-
{ name: "X-Another-Header", value: "another-value" },
|
|
334
|
-
],
|
|
335
|
-
});
|
|
336
|
-
await webhookProvider.deliver(context);
|
|
337
|
-
|
|
338
|
-
expect(capturedHeaders?.["X-Custom-Header"]).toBe("custom-value");
|
|
339
|
-
expect(capturedHeaders?.["X-Another-Header"]).toBe("another-value");
|
|
340
|
-
} finally {
|
|
341
|
-
mockFetch.mockRestore();
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
347
|
-
// Delivery - Error Handling
|
|
348
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
349
|
-
|
|
350
|
-
describe("deliver - error handling", () => {
|
|
351
|
-
it("returns error for non-OK response", async () => {
|
|
352
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
353
|
-
(async () => {
|
|
354
|
-
return new Response("Not Found", { status: 404 });
|
|
355
|
-
}) as unknown as typeof fetch,
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
const context = createTestContext();
|
|
360
|
-
const result = await webhookProvider.deliver(context);
|
|
361
|
-
|
|
362
|
-
expect(result.success).toBe(false);
|
|
363
|
-
expect(result.error).toContain("404");
|
|
364
|
-
} finally {
|
|
365
|
-
mockFetch.mockRestore();
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("returns retryable error for configured status codes", async () => {
|
|
370
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
371
|
-
(async () => {
|
|
372
|
-
return new Response("Too Many Requests", { status: 429 });
|
|
373
|
-
}) as unknown as typeof fetch,
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
const context = createTestContext({
|
|
378
|
-
retryOnStatus: [429, 503],
|
|
379
|
-
});
|
|
380
|
-
const result = await webhookProvider.deliver(context);
|
|
381
|
-
|
|
382
|
-
expect(result.success).toBe(false);
|
|
383
|
-
expect(result.error).toContain("retryable");
|
|
384
|
-
expect(result.retryAfterMs).toBeDefined();
|
|
385
|
-
} finally {
|
|
386
|
-
mockFetch.mockRestore();
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it("parses Retry-After header", async () => {
|
|
391
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
392
|
-
(async () => {
|
|
393
|
-
const headers = new Headers();
|
|
394
|
-
headers.set("Retry-After", "60");
|
|
395
|
-
return new Response("Too Many Requests", {
|
|
396
|
-
status: 429,
|
|
397
|
-
headers,
|
|
398
|
-
});
|
|
399
|
-
}) as unknown as typeof fetch,
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
try {
|
|
403
|
-
const context = createTestContext({
|
|
404
|
-
retryOnStatus: [429],
|
|
405
|
-
});
|
|
406
|
-
const result = await webhookProvider.deliver(context);
|
|
407
|
-
|
|
408
|
-
expect(result.retryAfterMs).toBe(60_000);
|
|
409
|
-
} finally {
|
|
410
|
-
mockFetch.mockRestore();
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it("handles network errors with retry", async () => {
|
|
415
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
416
|
-
(async () => {
|
|
417
|
-
throw new Error("ECONNREFUSED");
|
|
418
|
-
}) as unknown as typeof fetch,
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
try {
|
|
422
|
-
const context = createTestContext();
|
|
423
|
-
const result = await webhookProvider.deliver(context);
|
|
424
|
-
|
|
425
|
-
expect(result.success).toBe(false);
|
|
426
|
-
expect(result.error).toContain("ECONNREFUSED");
|
|
427
|
-
expect(result.retryAfterMs).toBe(30_000);
|
|
428
|
-
} finally {
|
|
429
|
-
mockFetch.mockRestore();
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it("handles timeout errors with retry", async () => {
|
|
434
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
|
|
435
|
-
(async () => {
|
|
436
|
-
throw new Error("timeout");
|
|
437
|
-
}) as unknown as typeof fetch,
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
try {
|
|
441
|
-
const context = createTestContext();
|
|
442
|
-
const result = await webhookProvider.deliver(context);
|
|
443
|
-
|
|
444
|
-
expect(result.success).toBe(false);
|
|
445
|
-
expect(result.retryAfterMs).toBe(30_000);
|
|
446
|
-
} finally {
|
|
447
|
-
mockFetch.mockRestore();
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
453
|
-
// Content Types
|
|
454
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
455
|
-
|
|
456
|
-
describe("content types", () => {
|
|
457
|
-
it("sends JSON body for application/json", async () => {
|
|
458
|
-
let capturedBody: string | undefined;
|
|
459
|
-
let capturedContentType: string | undefined;
|
|
460
|
-
|
|
461
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
462
|
-
_url: RequestInfo | URL,
|
|
463
|
-
options?: RequestInit,
|
|
464
|
-
) => {
|
|
465
|
-
capturedBody = options?.body as string;
|
|
466
|
-
capturedContentType = (options?.headers as Record<string, string>)?.[
|
|
467
|
-
"Content-Type"
|
|
468
|
-
];
|
|
469
|
-
return new Response("OK", { status: 200 });
|
|
470
|
-
}) as unknown as typeof fetch);
|
|
471
|
-
|
|
472
|
-
try {
|
|
473
|
-
const context = createTestContext({
|
|
474
|
-
contentType: "application/json",
|
|
475
|
-
});
|
|
476
|
-
await webhookProvider.deliver(context);
|
|
477
|
-
|
|
478
|
-
expect(capturedContentType).toBe("application/json");
|
|
479
|
-
expect(() => JSON.parse(capturedBody!)).not.toThrow();
|
|
480
|
-
} finally {
|
|
481
|
-
mockFetch.mockRestore();
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
it("sends form-encoded body for application/x-www-form-urlencoded", async () => {
|
|
486
|
-
let capturedBody: string | undefined;
|
|
487
|
-
let capturedContentType: string | undefined;
|
|
488
|
-
|
|
489
|
-
const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
|
|
490
|
-
_url: RequestInfo | URL,
|
|
491
|
-
options?: RequestInit,
|
|
492
|
-
) => {
|
|
493
|
-
capturedBody = options?.body as string;
|
|
494
|
-
capturedContentType = (options?.headers as Record<string, string>)?.[
|
|
495
|
-
"Content-Type"
|
|
496
|
-
];
|
|
497
|
-
return new Response("OK", { status: 200 });
|
|
498
|
-
}) as unknown as typeof fetch);
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
const context = createTestContext({
|
|
502
|
-
contentType: "application/x-www-form-urlencoded",
|
|
503
|
-
});
|
|
504
|
-
await webhookProvider.deliver(context);
|
|
505
|
-
|
|
506
|
-
expect(capturedContentType).toBe("application/x-www-form-urlencoded");
|
|
507
|
-
expect(capturedBody).toContain("payload=");
|
|
508
|
-
} finally {
|
|
509
|
-
mockFetch.mockRestore();
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
});
|