@thebookingkit/server 0.1.1 → 0.1.3
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/README.md +50 -0
- package/dist/adapters/email-adapter.js +2 -2
- package/dist/adapters/email-adapter.js.map +1 -1
- package/dist/adapters/job-adapter.d.ts +10 -10
- package/dist/adapters/job-adapter.d.ts.map +1 -1
- package/dist/adapters/job-adapter.js +10 -10
- package/dist/adapters/job-adapter.js.map +1 -1
- package/dist/api.d.ts +3 -3
- package/dist/api.js +5 -5
- package/dist/api.js.map +1 -1
- package/package.json +21 -2
- package/.turbo/turbo-build.log +0 -6
- package/.turbo/turbo-test.log +0 -20
- package/CHANGELOG.md +0 -9
- package/dist/__tests__/api.test.d.ts +0 -2
- package/dist/__tests__/api.test.d.ts.map +0 -1
- package/dist/__tests__/api.test.js +0 -280
- package/dist/__tests__/api.test.js.map +0 -1
- package/dist/__tests__/auth.test.d.ts +0 -2
- package/dist/__tests__/auth.test.d.ts.map +0 -1
- package/dist/__tests__/auth.test.js +0 -78
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/concurrent-booking.test.d.ts +0 -2
- package/dist/__tests__/concurrent-booking.test.d.ts.map +0 -1
- package/dist/__tests__/concurrent-booking.test.js +0 -111
- package/dist/__tests__/concurrent-booking.test.js.map +0 -1
- package/dist/__tests__/multi-tenancy.test.d.ts +0 -2
- package/dist/__tests__/multi-tenancy.test.d.ts.map +0 -1
- package/dist/__tests__/multi-tenancy.test.js +0 -196
- package/dist/__tests__/multi-tenancy.test.js.map +0 -1
- package/dist/__tests__/serialization-retry.test.d.ts +0 -2
- package/dist/__tests__/serialization-retry.test.d.ts.map +0 -1
- package/dist/__tests__/serialization-retry.test.js +0 -53
- package/dist/__tests__/serialization-retry.test.js.map +0 -1
- package/dist/__tests__/webhooks.test.d.ts +0 -2
- package/dist/__tests__/webhooks.test.d.ts.map +0 -1
- package/dist/__tests__/webhooks.test.js +0 -286
- package/dist/__tests__/webhooks.test.js.map +0 -1
- package/dist/__tests__/workflows.test.d.ts +0 -2
- package/dist/__tests__/workflows.test.d.ts.map +0 -1
- package/dist/__tests__/workflows.test.js +0 -299
- package/dist/__tests__/workflows.test.js.map +0 -1
- package/src/__tests__/api.test.ts +0 -354
- package/src/__tests__/auth.test.ts +0 -111
- package/src/__tests__/concurrent-booking.test.ts +0 -170
- package/src/__tests__/multi-tenancy.test.ts +0 -267
- package/src/__tests__/serialization-retry.test.ts +0 -76
- package/src/__tests__/webhooks.test.ts +0 -412
- package/src/__tests__/workflows.test.ts +0 -422
- package/src/adapters/calendar-adapter.ts +0 -49
- package/src/adapters/email-adapter.ts +0 -108
- package/src/adapters/index.ts +0 -36
- package/src/adapters/job-adapter.ts +0 -26
- package/src/adapters/payment-adapter.ts +0 -118
- package/src/adapters/sms-adapter.ts +0 -35
- package/src/adapters/storage-adapter.ts +0 -11
- package/src/api.ts +0 -446
- package/src/auth.ts +0 -146
- package/src/booking-tokens.ts +0 -61
- package/src/email-templates.ts +0 -140
- package/src/index.ts +0 -192
- package/src/multi-tenancy.ts +0 -301
- package/src/notification-jobs.ts +0 -428
- package/src/serialization-retry.ts +0 -94
- package/src/webhooks.ts +0 -378
- package/src/workflows.ts +0 -441
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -7
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
signWebhookPayload,
|
|
4
|
-
verifyWebhookSignature,
|
|
5
|
-
createWebhookEnvelope,
|
|
6
|
-
resolvePayloadTemplate,
|
|
7
|
-
matchWebhookSubscriptions,
|
|
8
|
-
getRetryDelay,
|
|
9
|
-
isSuccessResponse,
|
|
10
|
-
validateWebhookSubscription,
|
|
11
|
-
WebhookValidationError,
|
|
12
|
-
DEFAULT_RETRY_CONFIG,
|
|
13
|
-
WEBHOOK_TRIGGERS,
|
|
14
|
-
type WebhookSubscription,
|
|
15
|
-
type WebhookPayload,
|
|
16
|
-
type WebhookTrigger,
|
|
17
|
-
} from "../webhooks.js";
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Fixtures
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
const testSecret = "whsec_test_secret_key_123";
|
|
24
|
-
const testPayload = '{"triggerEvent":"BOOKING_CREATED","payload":{}}';
|
|
25
|
-
|
|
26
|
-
const samplePayload: WebhookPayload = {
|
|
27
|
-
bookingId: "bk-1",
|
|
28
|
-
eventType: "consultation",
|
|
29
|
-
startTime: "2026-03-15T14:00:00.000Z",
|
|
30
|
-
endTime: "2026-03-15T14:30:00.000Z",
|
|
31
|
-
organizer: { name: "Dr. Smith", email: "smith@example.com" },
|
|
32
|
-
attendees: [{ email: "jane@example.com", name: "Jane Doe" }],
|
|
33
|
-
status: "confirmed",
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
function makeSub(
|
|
37
|
-
overrides?: Partial<WebhookSubscription>,
|
|
38
|
-
): WebhookSubscription {
|
|
39
|
-
return {
|
|
40
|
-
id: "wh-1",
|
|
41
|
-
subscriberUrl: "https://api.example.com/webhooks",
|
|
42
|
-
triggers: ["BOOKING_CREATED"],
|
|
43
|
-
isActive: true,
|
|
44
|
-
...overrides,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// signWebhookPayload
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
describe("signWebhookPayload", () => {
|
|
53
|
-
it("produces a hex-encoded HMAC-SHA256 signature", () => {
|
|
54
|
-
const sig = signWebhookPayload(testPayload, testSecret, 1710000000);
|
|
55
|
-
expect(sig).toMatch(/^[0-9a-f]{64}$/);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("produces different signatures for different payloads", () => {
|
|
59
|
-
const sig1 = signWebhookPayload("body1", testSecret, 1710000000);
|
|
60
|
-
const sig2 = signWebhookPayload("body2", testSecret, 1710000000);
|
|
61
|
-
expect(sig1).not.toBe(sig2);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("produces different signatures for different timestamps", () => {
|
|
65
|
-
const sig1 = signWebhookPayload(testPayload, testSecret, 1710000000);
|
|
66
|
-
const sig2 = signWebhookPayload(testPayload, testSecret, 1710000001);
|
|
67
|
-
expect(sig1).not.toBe(sig2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("produces different signatures for different secrets", () => {
|
|
71
|
-
const sig1 = signWebhookPayload(testPayload, "secret1", 1710000000);
|
|
72
|
-
const sig2 = signWebhookPayload(testPayload, "secret2", 1710000000);
|
|
73
|
-
expect(sig1).not.toBe(sig2);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// verifyWebhookSignature
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
describe("verifyWebhookSignature", () => {
|
|
82
|
-
beforeEach(() => {
|
|
83
|
-
vi.useFakeTimers();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
afterEach(() => {
|
|
87
|
-
vi.useRealTimers();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("returns valid for a correct signature within tolerance", () => {
|
|
91
|
-
const now = 1710000000;
|
|
92
|
-
vi.setSystemTime(now * 1000);
|
|
93
|
-
const sig = signWebhookPayload(testPayload, testSecret, now);
|
|
94
|
-
|
|
95
|
-
const result = verifyWebhookSignature(
|
|
96
|
-
testPayload,
|
|
97
|
-
sig,
|
|
98
|
-
now,
|
|
99
|
-
testSecret,
|
|
100
|
-
);
|
|
101
|
-
expect(result).toEqual({ valid: true });
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("returns signature_mismatch for tampered payload", () => {
|
|
105
|
-
const now = 1710000000;
|
|
106
|
-
vi.setSystemTime(now * 1000);
|
|
107
|
-
const sig = signWebhookPayload(testPayload, testSecret, now);
|
|
108
|
-
|
|
109
|
-
const result = verifyWebhookSignature(
|
|
110
|
-
"tampered_body",
|
|
111
|
-
sig,
|
|
112
|
-
now,
|
|
113
|
-
testSecret,
|
|
114
|
-
);
|
|
115
|
-
expect(result).toEqual({ valid: false, reason: "signature_mismatch" });
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("returns signature_mismatch for wrong secret", () => {
|
|
119
|
-
const now = 1710000000;
|
|
120
|
-
vi.setSystemTime(now * 1000);
|
|
121
|
-
const sig = signWebhookPayload(testPayload, testSecret, now);
|
|
122
|
-
|
|
123
|
-
const result = verifyWebhookSignature(
|
|
124
|
-
testPayload,
|
|
125
|
-
sig,
|
|
126
|
-
now,
|
|
127
|
-
"wrong_secret",
|
|
128
|
-
);
|
|
129
|
-
expect(result).toEqual({ valid: false, reason: "signature_mismatch" });
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("returns timestamp_expired for old timestamp (>5 min)", () => {
|
|
133
|
-
const now = 1710000600; // 10 min later
|
|
134
|
-
vi.setSystemTime(now * 1000);
|
|
135
|
-
const oldTimestamp = now - 301; // 5 min 1 sec ago
|
|
136
|
-
const sig = signWebhookPayload(testPayload, testSecret, oldTimestamp);
|
|
137
|
-
|
|
138
|
-
const result = verifyWebhookSignature(
|
|
139
|
-
testPayload,
|
|
140
|
-
sig,
|
|
141
|
-
oldTimestamp,
|
|
142
|
-
testSecret,
|
|
143
|
-
);
|
|
144
|
-
expect(result).toEqual({ valid: false, reason: "timestamp_expired" });
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("accepts timestamp within tolerance", () => {
|
|
148
|
-
const now = 1710000000;
|
|
149
|
-
vi.setSystemTime(now * 1000);
|
|
150
|
-
const recentTimestamp = now - 299; // 4 min 59 sec ago
|
|
151
|
-
const sig = signWebhookPayload(testPayload, testSecret, recentTimestamp);
|
|
152
|
-
|
|
153
|
-
const result = verifyWebhookSignature(
|
|
154
|
-
testPayload,
|
|
155
|
-
sig,
|
|
156
|
-
recentTimestamp,
|
|
157
|
-
testSecret,
|
|
158
|
-
);
|
|
159
|
-
expect(result).toEqual({ valid: true });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("supports custom tolerance", () => {
|
|
163
|
-
const now = 1710000000;
|
|
164
|
-
vi.setSystemTime(now * 1000);
|
|
165
|
-
const oldTimestamp = now - 61;
|
|
166
|
-
const sig = signWebhookPayload(testPayload, testSecret, oldTimestamp);
|
|
167
|
-
|
|
168
|
-
const result = verifyWebhookSignature(
|
|
169
|
-
testPayload,
|
|
170
|
-
sig,
|
|
171
|
-
oldTimestamp,
|
|
172
|
-
testSecret,
|
|
173
|
-
{ toleranceSeconds: 60 },
|
|
174
|
-
);
|
|
175
|
-
expect(result).toEqual({ valid: false, reason: "timestamp_expired" });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("rejects future timestamps beyond tolerance", () => {
|
|
179
|
-
const now = 1710000000;
|
|
180
|
-
vi.setSystemTime(now * 1000);
|
|
181
|
-
const futureTimestamp = now + 400;
|
|
182
|
-
const sig = signWebhookPayload(testPayload, testSecret, futureTimestamp);
|
|
183
|
-
|
|
184
|
-
const result = verifyWebhookSignature(
|
|
185
|
-
testPayload,
|
|
186
|
-
sig,
|
|
187
|
-
futureTimestamp,
|
|
188
|
-
testSecret,
|
|
189
|
-
);
|
|
190
|
-
expect(result).toEqual({ valid: false, reason: "timestamp_expired" });
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
// createWebhookEnvelope
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
|
|
198
|
-
describe("createWebhookEnvelope", () => {
|
|
199
|
-
it("creates a valid envelope with ISO 8601 timestamp", () => {
|
|
200
|
-
const envelope = createWebhookEnvelope("BOOKING_CREATED", samplePayload);
|
|
201
|
-
|
|
202
|
-
expect(envelope.triggerEvent).toBe("BOOKING_CREATED");
|
|
203
|
-
expect(envelope.payload).toBe(samplePayload);
|
|
204
|
-
expect(new Date(envelope.createdAt).toISOString()).toBe(
|
|
205
|
-
envelope.createdAt,
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
// resolvePayloadTemplate
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
describe("resolvePayloadTemplate", () => {
|
|
215
|
-
const envelope = createWebhookEnvelope("BOOKING_CREATED", samplePayload);
|
|
216
|
-
|
|
217
|
-
it("resolves all standard template variables", () => {
|
|
218
|
-
const template = '{"event":"{{triggerEvent}}","booking":"{{bookingId}}","type":"{{eventType}}","start":"{{startTime}}","end":"{{endTime}}","status":"{{status}}","org":"{{organizerName}}","email":"{{organizerEmail}}"}';
|
|
219
|
-
const result = resolvePayloadTemplate(template, envelope);
|
|
220
|
-
const parsed = JSON.parse(result);
|
|
221
|
-
|
|
222
|
-
expect(parsed.event).toBe("BOOKING_CREATED");
|
|
223
|
-
expect(parsed.booking).toBe("bk-1");
|
|
224
|
-
expect(parsed.type).toBe("consultation");
|
|
225
|
-
expect(parsed.start).toBe("2026-03-15T14:00:00.000Z");
|
|
226
|
-
expect(parsed.status).toBe("confirmed");
|
|
227
|
-
expect(parsed.org).toBe("Dr. Smith");
|
|
228
|
-
expect(parsed.email).toBe("smith@example.com");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("handles template with no variables", () => {
|
|
232
|
-
const result = resolvePayloadTemplate('{"static":"value"}', envelope);
|
|
233
|
-
expect(result).toBe('{"static":"value"}');
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
// matchWebhookSubscriptions
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
|
|
241
|
-
describe("matchWebhookSubscriptions", () => {
|
|
242
|
-
const subs: WebhookSubscription[] = [
|
|
243
|
-
makeSub({ id: "wh-1", triggers: ["BOOKING_CREATED", "BOOKING_CANCELLED"] }),
|
|
244
|
-
makeSub({ id: "wh-2", triggers: ["BOOKING_CREATED"], isActive: false }),
|
|
245
|
-
makeSub({
|
|
246
|
-
id: "wh-3",
|
|
247
|
-
triggers: ["BOOKING_CREATED"],
|
|
248
|
-
eventTypeId: "evt-1",
|
|
249
|
-
}),
|
|
250
|
-
makeSub({
|
|
251
|
-
id: "wh-4",
|
|
252
|
-
triggers: ["BOOKING_CREATED"],
|
|
253
|
-
teamId: "team-1",
|
|
254
|
-
}),
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
it("matches active webhooks with matching trigger", () => {
|
|
258
|
-
const matched = matchWebhookSubscriptions(subs, "BOOKING_CREATED");
|
|
259
|
-
expect(matched.map((s) => s.id)).toContain("wh-1");
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it("excludes inactive webhooks", () => {
|
|
263
|
-
const matched = matchWebhookSubscriptions(subs, "BOOKING_CREATED");
|
|
264
|
-
expect(matched.find((s) => s.id === "wh-2")).toBeUndefined();
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("matches scoped webhooks when scope matches", () => {
|
|
268
|
-
const matched = matchWebhookSubscriptions(subs, "BOOKING_CREATED", {
|
|
269
|
-
eventTypeId: "evt-1",
|
|
270
|
-
});
|
|
271
|
-
expect(matched.find((s) => s.id === "wh-3")).toBeDefined();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it("excludes scoped webhooks when scope doesn't match", () => {
|
|
275
|
-
const matched = matchWebhookSubscriptions(subs, "BOOKING_CREATED", {
|
|
276
|
-
eventTypeId: "evt-99",
|
|
277
|
-
});
|
|
278
|
-
expect(matched.find((s) => s.id === "wh-3")).toBeUndefined();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("matches team-scoped webhooks", () => {
|
|
282
|
-
const matched = matchWebhookSubscriptions(subs, "BOOKING_CREATED", {
|
|
283
|
-
teamId: "team-1",
|
|
284
|
-
});
|
|
285
|
-
expect(matched.find((s) => s.id === "wh-4")).toBeDefined();
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("returns empty for unsubscribed trigger", () => {
|
|
289
|
-
const matched = matchWebhookSubscriptions(subs, "OOO_CREATED");
|
|
290
|
-
expect(matched).toHaveLength(0);
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
295
|
-
// getRetryDelay
|
|
296
|
-
// ---------------------------------------------------------------------------
|
|
297
|
-
|
|
298
|
-
describe("getRetryDelay", () => {
|
|
299
|
-
it("returns correct delays for default config", () => {
|
|
300
|
-
expect(getRetryDelay(0)).toBe(10);
|
|
301
|
-
expect(getRetryDelay(1)).toBe(60);
|
|
302
|
-
expect(getRetryDelay(2)).toBe(300);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it("returns null when max retries exceeded", () => {
|
|
306
|
-
expect(getRetryDelay(3)).toBeNull();
|
|
307
|
-
expect(getRetryDelay(10)).toBeNull();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it("uses custom config", () => {
|
|
311
|
-
const config = { maxRetries: 2, backoffSeconds: [5, 30] };
|
|
312
|
-
expect(getRetryDelay(0, config)).toBe(5);
|
|
313
|
-
expect(getRetryDelay(1, config)).toBe(30);
|
|
314
|
-
expect(getRetryDelay(2, config)).toBeNull();
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// ---------------------------------------------------------------------------
|
|
319
|
-
// isSuccessResponse
|
|
320
|
-
// ---------------------------------------------------------------------------
|
|
321
|
-
|
|
322
|
-
describe("isSuccessResponse", () => {
|
|
323
|
-
it("returns true for 200", () => {
|
|
324
|
-
expect(isSuccessResponse(200)).toBe(true);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("returns true for 201", () => {
|
|
328
|
-
expect(isSuccessResponse(201)).toBe(true);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it("returns true for 204", () => {
|
|
332
|
-
expect(isSuccessResponse(204)).toBe(true);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it("returns false for 400", () => {
|
|
336
|
-
expect(isSuccessResponse(400)).toBe(false);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it("returns false for 500", () => {
|
|
340
|
-
expect(isSuccessResponse(500)).toBe(false);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("returns false for 301 redirect", () => {
|
|
344
|
-
expect(isSuccessResponse(301)).toBe(false);
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
// ---------------------------------------------------------------------------
|
|
349
|
-
// validateWebhookSubscription
|
|
350
|
-
// ---------------------------------------------------------------------------
|
|
351
|
-
|
|
352
|
-
describe("validateWebhookSubscription", () => {
|
|
353
|
-
it("accepts valid subscription", () => {
|
|
354
|
-
expect(() =>
|
|
355
|
-
validateWebhookSubscription({
|
|
356
|
-
subscriberUrl: "https://example.com/webhook",
|
|
357
|
-
triggers: ["BOOKING_CREATED"],
|
|
358
|
-
isActive: true,
|
|
359
|
-
}),
|
|
360
|
-
).not.toThrow();
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
it("rejects missing URL", () => {
|
|
364
|
-
expect(() =>
|
|
365
|
-
validateWebhookSubscription({
|
|
366
|
-
subscriberUrl: "",
|
|
367
|
-
triggers: ["BOOKING_CREATED"],
|
|
368
|
-
isActive: true,
|
|
369
|
-
}),
|
|
370
|
-
).toThrow("Subscriber URL is required");
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it("rejects invalid URL", () => {
|
|
374
|
-
expect(() =>
|
|
375
|
-
validateWebhookSubscription({
|
|
376
|
-
subscriberUrl: "not-a-url",
|
|
377
|
-
triggers: ["BOOKING_CREATED"],
|
|
378
|
-
isActive: true,
|
|
379
|
-
}),
|
|
380
|
-
).toThrow("Invalid subscriber URL");
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("rejects empty triggers array", () => {
|
|
384
|
-
expect(() =>
|
|
385
|
-
validateWebhookSubscription({
|
|
386
|
-
subscriberUrl: "https://example.com/webhook",
|
|
387
|
-
triggers: [],
|
|
388
|
-
isActive: true,
|
|
389
|
-
}),
|
|
390
|
-
).toThrow("At least one trigger");
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it("rejects invalid trigger", () => {
|
|
394
|
-
expect(() =>
|
|
395
|
-
validateWebhookSubscription({
|
|
396
|
-
subscriberUrl: "https://example.com/webhook",
|
|
397
|
-
triggers: ["INVALID_TRIGGER" as WebhookTrigger],
|
|
398
|
-
isActive: true,
|
|
399
|
-
}),
|
|
400
|
-
).toThrow('Invalid trigger: "INVALID_TRIGGER"');
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it("accepts all valid triggers", () => {
|
|
404
|
-
expect(() =>
|
|
405
|
-
validateWebhookSubscription({
|
|
406
|
-
subscriberUrl: "https://example.com/webhook",
|
|
407
|
-
triggers: [...WEBHOOK_TRIGGERS],
|
|
408
|
-
isActive: true,
|
|
409
|
-
}),
|
|
410
|
-
).not.toThrow();
|
|
411
|
-
});
|
|
412
|
-
});
|