@thebookingkit/server 0.1.1 → 0.1.2
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/package.json +1 -1
- package/src/__tests__/api.test.ts +6 -6
- package/src/adapters/email-adapter.ts +2 -2
- package/src/adapters/job-adapter.ts +10 -10
- package/src/api.ts +5 -5
- package/.turbo/turbo-build.log +0 -6
- package/.turbo/turbo-test.log +0 -20
- 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/dist/adapters/calendar-adapter.d.ts +0 -47
- package/dist/adapters/calendar-adapter.d.ts.map +0 -1
- package/dist/adapters/calendar-adapter.js +0 -2
- package/dist/adapters/calendar-adapter.js.map +0 -1
- package/dist/adapters/email-adapter.d.ts +0 -65
- package/dist/adapters/email-adapter.d.ts.map +0 -1
- package/dist/adapters/email-adapter.js +0 -40
- package/dist/adapters/email-adapter.js.map +0 -1
- package/dist/adapters/index.d.ts +0 -9
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/index.js +0 -3
- package/dist/adapters/index.js.map +0 -1
- package/dist/adapters/job-adapter.d.ts +0 -26
- package/dist/adapters/job-adapter.d.ts.map +0 -1
- package/dist/adapters/job-adapter.js +0 -13
- package/dist/adapters/job-adapter.js.map +0 -1
- package/dist/adapters/payment-adapter.d.ts +0 -106
- package/dist/adapters/payment-adapter.d.ts.map +0 -1
- package/dist/adapters/payment-adapter.js +0 -8
- package/dist/adapters/payment-adapter.js.map +0 -1
- package/dist/adapters/sms-adapter.d.ts +0 -33
- package/dist/adapters/sms-adapter.d.ts.map +0 -1
- package/dist/adapters/sms-adapter.js +0 -8
- package/dist/adapters/sms-adapter.js.map +0 -1
- package/dist/adapters/storage-adapter.d.ts +0 -12
- package/dist/adapters/storage-adapter.d.ts.map +0 -1
- package/dist/adapters/storage-adapter.js +0 -2
- package/dist/adapters/storage-adapter.js.map +0 -1
- package/dist/api.d.ts +0 -223
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -271
- package/dist/api.js.map +0 -1
- package/dist/auth.d.ts +0 -71
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -81
- package/dist/auth.js.map +0 -1
- package/dist/booking-tokens.d.ts +0 -23
- package/dist/booking-tokens.d.ts.map +0 -1
- package/dist/booking-tokens.js +0 -52
- package/dist/booking-tokens.js.map +0 -1
- package/dist/email-templates.d.ts +0 -36
- package/dist/email-templates.d.ts.map +0 -1
- package/dist/email-templates.js +0 -112
- package/dist/email-templates.js.map +0 -1
- package/dist/index.d.ts +0 -13
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -22
- package/dist/index.js.map +0 -1
- package/dist/multi-tenancy.d.ts +0 -132
- package/dist/multi-tenancy.d.ts.map +0 -1
- package/dist/multi-tenancy.js +0 -188
- package/dist/multi-tenancy.js.map +0 -1
- package/dist/notification-jobs.d.ts +0 -143
- package/dist/notification-jobs.d.ts.map +0 -1
- package/dist/notification-jobs.js +0 -278
- package/dist/notification-jobs.js.map +0 -1
- package/dist/serialization-retry.d.ts +0 -28
- package/dist/serialization-retry.d.ts.map +0 -1
- package/dist/serialization-retry.js +0 -71
- package/dist/serialization-retry.js.map +0 -1
- package/dist/webhooks.d.ts +0 -164
- package/dist/webhooks.d.ts.map +0 -1
- package/dist/webhooks.js +0 -228
- package/dist/webhooks.js.map +0 -1
- package/dist/workflows.d.ts +0 -169
- package/dist/workflows.d.ts.map +0 -1
- package/dist/workflows.js +0 -251
- package/dist/workflows.js.map +0 -1
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { withSerializableRetry, BookingConflictError, SerializationRetryExhaustedError, } from "../index.js";
|
|
3
|
-
/**
|
|
4
|
-
* Concurrent booking simulation tests.
|
|
5
|
-
*
|
|
6
|
-
* These tests simulate the load test from E01-S04:
|
|
7
|
-
* "50 concurrent booking attempts for the same slot yields exactly 1 confirmed
|
|
8
|
-
* booking, 0 unhandled serialization errors, and all other callers receive
|
|
9
|
-
* a clear conflict response within 2 seconds."
|
|
10
|
-
*
|
|
11
|
-
* Since we can't connect to a real Postgres in unit tests, we simulate the
|
|
12
|
-
* database behavior with an in-memory "slot lock" that mimics the EXCLUDE
|
|
13
|
-
* constraint (SQLSTATE 23P01) and serialization failures (SQLSTATE 40001).
|
|
14
|
-
*/
|
|
15
|
-
/** Simulates a Postgres error with a SQLSTATE code */
|
|
16
|
-
class PgError extends Error {
|
|
17
|
-
code;
|
|
18
|
-
constructor(message, code) {
|
|
19
|
-
super(message);
|
|
20
|
-
this.name = "PgError";
|
|
21
|
-
this.code = code;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
describe("Concurrent Booking Simulation (E01-S04 Load Test)", () => {
|
|
25
|
-
it("50 concurrent attempts for the same slot: exactly 1 success, rest get conflict", async () => {
|
|
26
|
-
// Simulate a database slot that can only be booked once.
|
|
27
|
-
// The first successful INSERT wins; subsequent attempts get exclusion_violation.
|
|
28
|
-
let slotBooked = false;
|
|
29
|
-
let serializationFailureCount = 0;
|
|
30
|
-
const maxSimulatedSerializationFailures = 5; // simulate some contention
|
|
31
|
-
const attemptBooking = async (attemptId) => {
|
|
32
|
-
return withSerializableRetry(async () => {
|
|
33
|
-
// Simulate random serialization failures under contention
|
|
34
|
-
if (!slotBooked &&
|
|
35
|
-
serializationFailureCount < maxSimulatedSerializationFailures &&
|
|
36
|
-
Math.random() < 0.3) {
|
|
37
|
-
serializationFailureCount++;
|
|
38
|
-
throw new PgError("serialization failure", "40001");
|
|
39
|
-
}
|
|
40
|
-
// Simulate the EXCLUDE constraint check
|
|
41
|
-
if (slotBooked) {
|
|
42
|
-
throw new PgError("conflicting key value violates exclusion constraint", "23P01");
|
|
43
|
-
}
|
|
44
|
-
// Race: first writer wins
|
|
45
|
-
slotBooked = true;
|
|
46
|
-
return `booking-${attemptId}`;
|
|
47
|
-
}, { maxRetries: 3, baseDelayMs: 10 });
|
|
48
|
-
};
|
|
49
|
-
const CONCURRENCY = 50;
|
|
50
|
-
const results = await Promise.allSettled(Array.from({ length: CONCURRENCY }, (_, i) => attemptBooking(i)));
|
|
51
|
-
const successes = results.filter((r) => r.status === "fulfilled");
|
|
52
|
-
const failures = results.filter((r) => r.status === "rejected");
|
|
53
|
-
// Exactly 1 booking should succeed
|
|
54
|
-
expect(successes.length).toBe(1);
|
|
55
|
-
// All failures should be BookingConflictError (not unhandled errors)
|
|
56
|
-
for (const failure of failures) {
|
|
57
|
-
if (failure.status === "rejected") {
|
|
58
|
-
expect(failure.reason).toBeInstanceOf(BookingConflictError);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Zero unhandled serialization errors
|
|
62
|
-
const serializationErrors = failures.filter((f) => f.status === "rejected" &&
|
|
63
|
-
f.reason instanceof SerializationRetryExhaustedError);
|
|
64
|
-
expect(serializationErrors.length).toBe(0);
|
|
65
|
-
});
|
|
66
|
-
it("completes all 50 attempts within 2 seconds", async () => {
|
|
67
|
-
let slotBooked = false;
|
|
68
|
-
const attemptBooking = async (attemptId) => {
|
|
69
|
-
return withSerializableRetry(async () => {
|
|
70
|
-
if (slotBooked) {
|
|
71
|
-
throw new PgError("conflicting key value violates exclusion constraint", "23P01");
|
|
72
|
-
}
|
|
73
|
-
slotBooked = true;
|
|
74
|
-
return `booking-${attemptId}`;
|
|
75
|
-
}, { maxRetries: 3, baseDelayMs: 10 });
|
|
76
|
-
};
|
|
77
|
-
const start = Date.now();
|
|
78
|
-
await Promise.allSettled(Array.from({ length: 50 }, (_, i) => attemptBooking(i)));
|
|
79
|
-
const elapsed = Date.now() - start;
|
|
80
|
-
expect(elapsed).toBeLessThan(2000);
|
|
81
|
-
});
|
|
82
|
-
it("serialization failures are retried and can succeed on a different slot", async () => {
|
|
83
|
-
let attempt = 0;
|
|
84
|
-
const result = await withSerializableRetry(async () => {
|
|
85
|
-
attempt++;
|
|
86
|
-
if (attempt <= 2) {
|
|
87
|
-
throw new PgError("serialization failure", "40001");
|
|
88
|
-
}
|
|
89
|
-
return "booking-success";
|
|
90
|
-
}, { maxRetries: 3, baseDelayMs: 10 });
|
|
91
|
-
expect(result).toBe("booking-success");
|
|
92
|
-
expect(attempt).toBe(3);
|
|
93
|
-
});
|
|
94
|
-
it("retries are exhausted after maxRetries serialization failures", async () => {
|
|
95
|
-
const fn = () => withSerializableRetry(async () => {
|
|
96
|
-
throw new PgError("serialization failure", "40001");
|
|
97
|
-
}, { maxRetries: 3, baseDelayMs: 10 });
|
|
98
|
-
await expect(fn()).rejects.toThrow(SerializationRetryExhaustedError);
|
|
99
|
-
});
|
|
100
|
-
it("exclusion violation (slot taken) is never retried", async () => {
|
|
101
|
-
let callCount = 0;
|
|
102
|
-
const fn = () => withSerializableRetry(async () => {
|
|
103
|
-
callCount++;
|
|
104
|
-
throw new PgError("exclusion constraint", "23P01");
|
|
105
|
-
}, { maxRetries: 3, baseDelayMs: 10 });
|
|
106
|
-
await expect(fn()).rejects.toThrow(BookingConflictError);
|
|
107
|
-
// Should NOT have retried — only 1 call
|
|
108
|
-
expect(callCount).toBe(1);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
//# sourceMappingURL=concurrent-booking.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"concurrent-booking.test.js","sourceRoot":"","sources":["../../src/__tests__/concurrent-booking.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multi-tenancy.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/multi-tenancy.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolveEffectiveSettings, getRolePermissions, roleHasPermission, assertOrgPermission, assertTenantScope, buildOrgBookingUrl, parseOrgBookingPath, TenantAuthorizationError, GLOBAL_DEFAULTS, } from "../multi-tenancy.js";
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// resolveEffectiveSettings
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
describe("resolveEffectiveSettings", () => {
|
|
7
|
-
it("uses global defaults when no settings provided", () => {
|
|
8
|
-
const resolved = resolveEffectiveSettings();
|
|
9
|
-
expect(resolved.timezone).toBe(GLOBAL_DEFAULTS.timezone);
|
|
10
|
-
expect(resolved.currency).toBe(GLOBAL_DEFAULTS.currency);
|
|
11
|
-
expect(resolved.bufferMinutes).toBe(GLOBAL_DEFAULTS.bufferMinutes);
|
|
12
|
-
expect(resolved.branding).toEqual({});
|
|
13
|
-
expect(resolved.bookingLimits).toEqual({});
|
|
14
|
-
});
|
|
15
|
-
it("applies org settings over global defaults", () => {
|
|
16
|
-
const org = {
|
|
17
|
-
defaultTimezone: "America/New_York",
|
|
18
|
-
defaultCurrency: "EUR",
|
|
19
|
-
branding: { primaryColor: "#6366f1" },
|
|
20
|
-
};
|
|
21
|
-
const resolved = resolveEffectiveSettings(org);
|
|
22
|
-
expect(resolved.timezone).toBe("America/New_York");
|
|
23
|
-
expect(resolved.currency).toBe("EUR");
|
|
24
|
-
expect(resolved.branding.primaryColor).toBe("#6366f1");
|
|
25
|
-
});
|
|
26
|
-
it("provider settings override org settings", () => {
|
|
27
|
-
const org = {
|
|
28
|
-
defaultTimezone: "America/New_York",
|
|
29
|
-
defaultCurrency: "USD",
|
|
30
|
-
};
|
|
31
|
-
const provider = {
|
|
32
|
-
timezone: "Europe/London",
|
|
33
|
-
currency: "GBP",
|
|
34
|
-
};
|
|
35
|
-
const resolved = resolveEffectiveSettings(org, provider);
|
|
36
|
-
expect(resolved.timezone).toBe("Europe/London");
|
|
37
|
-
expect(resolved.currency).toBe("GBP");
|
|
38
|
-
});
|
|
39
|
-
it("event type settings override provider settings", () => {
|
|
40
|
-
const org = { defaultTimezone: "UTC" };
|
|
41
|
-
const provider = { timezone: "America/New_York" };
|
|
42
|
-
const eventType = { timezone: "Asia/Tokyo" };
|
|
43
|
-
const resolved = resolveEffectiveSettings(org, provider, eventType);
|
|
44
|
-
expect(resolved.timezone).toBe("Asia/Tokyo");
|
|
45
|
-
});
|
|
46
|
-
it("merges branding settings additively", () => {
|
|
47
|
-
const org = {
|
|
48
|
-
branding: { primaryColor: "#000", logoUrl: "https://example.com/logo.png" },
|
|
49
|
-
};
|
|
50
|
-
const provider = {
|
|
51
|
-
branding: { primaryColor: "#fff" },
|
|
52
|
-
};
|
|
53
|
-
const resolved = resolveEffectiveSettings(org, provider);
|
|
54
|
-
expect(resolved.branding.primaryColor).toBe("#fff"); // provider overrides
|
|
55
|
-
expect(resolved.branding.logoUrl).toBe("https://example.com/logo.png"); // org preserved
|
|
56
|
-
});
|
|
57
|
-
it("merges booking limits additively", () => {
|
|
58
|
-
const org = {
|
|
59
|
-
defaultBookingLimits: { maxPerDay: 5 },
|
|
60
|
-
};
|
|
61
|
-
const provider = {
|
|
62
|
-
bookingLimits: { maxPerWeek: 20 },
|
|
63
|
-
};
|
|
64
|
-
const resolved = resolveEffectiveSettings(org, provider);
|
|
65
|
-
expect(resolved.bookingLimits).toMatchObject({ maxPerDay: 5, maxPerWeek: 20 });
|
|
66
|
-
});
|
|
67
|
-
it("handles null settings gracefully", () => {
|
|
68
|
-
expect(() => resolveEffectiveSettings(null, null, null)).not.toThrow();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// getRolePermissions
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
describe("getRolePermissions", () => {
|
|
75
|
-
it("owner has all permissions", () => {
|
|
76
|
-
const perms = getRolePermissions("owner");
|
|
77
|
-
expect(perms).toContain("manage:members");
|
|
78
|
-
expect(perms).toContain("manage:teams");
|
|
79
|
-
expect(perms).toContain("view:all-bookings");
|
|
80
|
-
expect(perms).toContain("manage:organization");
|
|
81
|
-
expect(perms).toContain("view:analytics");
|
|
82
|
-
});
|
|
83
|
-
it("admin has management permissions but not member management", () => {
|
|
84
|
-
const perms = getRolePermissions("admin");
|
|
85
|
-
expect(perms).toContain("manage:teams");
|
|
86
|
-
expect(perms).toContain("view:all-bookings");
|
|
87
|
-
expect(perms).not.toContain("manage:members");
|
|
88
|
-
expect(perms).not.toContain("manage:organization");
|
|
89
|
-
});
|
|
90
|
-
it("member has only own-resource permissions", () => {
|
|
91
|
-
const perms = getRolePermissions("member");
|
|
92
|
-
expect(perms).toContain("view:own-bookings");
|
|
93
|
-
expect(perms).toContain("manage:own-availability");
|
|
94
|
-
expect(perms).not.toContain("view:all-bookings");
|
|
95
|
-
expect(perms).not.toContain("manage:teams");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// roleHasPermission
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
describe("roleHasPermission", () => {
|
|
102
|
-
it("returns true for permitted action", () => {
|
|
103
|
-
expect(roleHasPermission("owner", "manage:members")).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
it("returns false for unpermitted action", () => {
|
|
106
|
-
expect(roleHasPermission("member", "manage:members")).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
it("admin can view all bookings", () => {
|
|
109
|
-
expect(roleHasPermission("admin", "view:all-bookings")).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
// assertOrgPermission
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
describe("assertOrgPermission", () => {
|
|
116
|
-
const ownerMember = {
|
|
117
|
-
userId: "user-1",
|
|
118
|
-
organizationId: "org-1",
|
|
119
|
-
role: "owner",
|
|
120
|
-
};
|
|
121
|
-
const regularMember = {
|
|
122
|
-
userId: "user-2",
|
|
123
|
-
organizationId: "org-1",
|
|
124
|
-
role: "member",
|
|
125
|
-
};
|
|
126
|
-
it("does not throw for permitted action", () => {
|
|
127
|
-
expect(() => assertOrgPermission(ownerMember, "manage:members")).not.toThrow();
|
|
128
|
-
});
|
|
129
|
-
it("throws for unpermitted action", () => {
|
|
130
|
-
expect(() => assertOrgPermission(regularMember, "manage:members")).toThrow(TenantAuthorizationError);
|
|
131
|
-
});
|
|
132
|
-
it("throws with descriptive message", () => {
|
|
133
|
-
expect(() => assertOrgPermission(regularMember, "view:all-bookings")).toThrow('"member"');
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// assertTenantScope
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
describe("assertTenantScope", () => {
|
|
140
|
-
it("does not throw when org IDs match", () => {
|
|
141
|
-
expect(() => assertTenantScope("org-1", "org-1")).not.toThrow();
|
|
142
|
-
});
|
|
143
|
-
it("does not throw when resource has no org ID", () => {
|
|
144
|
-
expect(() => assertTenantScope(null, "org-1")).not.toThrow();
|
|
145
|
-
expect(() => assertTenantScope(undefined, "org-1")).not.toThrow();
|
|
146
|
-
});
|
|
147
|
-
it("throws when org IDs don't match", () => {
|
|
148
|
-
expect(() => assertTenantScope("org-2", "org-1")).toThrow(TenantAuthorizationError);
|
|
149
|
-
expect(() => assertTenantScope("org-2", "org-1")).toThrow("does not belong to the current organization");
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
// buildOrgBookingUrl
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
describe("buildOrgBookingUrl", () => {
|
|
156
|
-
it("builds the correct URL", () => {
|
|
157
|
-
const url = buildOrgBookingUrl("acme-corp", "dr-smith", "consultation", "https://booking.example.com");
|
|
158
|
-
expect(url).toBe("https://booking.example.com/acme-corp/dr-smith/consultation");
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
// parseOrgBookingPath
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
describe("parseOrgBookingPath", () => {
|
|
165
|
-
it("parses valid path", () => {
|
|
166
|
-
const result = parseOrgBookingPath("/acme-corp/dr-smith/consultation");
|
|
167
|
-
expect(result).toEqual({
|
|
168
|
-
orgSlug: "acme-corp",
|
|
169
|
-
providerSlug: "dr-smith",
|
|
170
|
-
eventTypeSlug: "consultation",
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
it("returns null for invalid path", () => {
|
|
174
|
-
expect(parseOrgBookingPath("/only-two/segments")).toBeNull();
|
|
175
|
-
expect(parseOrgBookingPath("/too/many/path/segments")).toBeNull();
|
|
176
|
-
expect(parseOrgBookingPath("no-leading-slash")).toBeNull();
|
|
177
|
-
});
|
|
178
|
-
it("returns null for empty path", () => {
|
|
179
|
-
expect(parseOrgBookingPath("")).toBeNull();
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
// ---------------------------------------------------------------------------
|
|
183
|
-
// GLOBAL_DEFAULTS
|
|
184
|
-
// ---------------------------------------------------------------------------
|
|
185
|
-
describe("GLOBAL_DEFAULTS", () => {
|
|
186
|
-
it("uses UTC timezone", () => {
|
|
187
|
-
expect(GLOBAL_DEFAULTS.timezone).toBe("UTC");
|
|
188
|
-
});
|
|
189
|
-
it("uses USD currency", () => {
|
|
190
|
-
expect(GLOBAL_DEFAULTS.currency).toBe("USD");
|
|
191
|
-
});
|
|
192
|
-
it("uses 0 buffer minutes", () => {
|
|
193
|
-
expect(GLOBAL_DEFAULTS.bufferMinutes).toBe(0);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
//# sourceMappingURL=multi-tenancy.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multi-tenancy.test.js","sourceRoot":"","sources":["../../src/__tests__/multi-tenancy.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialization-retry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/serialization-retry.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { withSerializableRetry, BookingConflictError, SerializationRetryExhaustedError, } from "../index.js";
|
|
3
|
-
function makePostgresError(code, message = "pg error") {
|
|
4
|
-
const err = new Error(message);
|
|
5
|
-
err.code = code;
|
|
6
|
-
return err;
|
|
7
|
-
}
|
|
8
|
-
describe("withSerializableRetry", () => {
|
|
9
|
-
it("returns the result on success", async () => {
|
|
10
|
-
const result = await withSerializableRetry(async () => ({ id: "123" }));
|
|
11
|
-
expect(result).toEqual({ id: "123" });
|
|
12
|
-
});
|
|
13
|
-
it("throws BookingConflictError on exclusion violation (23P01)", async () => {
|
|
14
|
-
const fn = vi.fn().mockRejectedValue(makePostgresError("23P01"));
|
|
15
|
-
await expect(withSerializableRetry(fn)).rejects.toThrow(BookingConflictError);
|
|
16
|
-
// Should NOT retry on exclusion violation
|
|
17
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
18
|
-
});
|
|
19
|
-
it("retries on serialization failure (40001) and succeeds", async () => {
|
|
20
|
-
const fn = vi
|
|
21
|
-
.fn()
|
|
22
|
-
.mockRejectedValueOnce(makePostgresError("40001"))
|
|
23
|
-
.mockRejectedValueOnce(makePostgresError("40001"))
|
|
24
|
-
.mockResolvedValueOnce({ id: "456" });
|
|
25
|
-
const result = await withSerializableRetry(fn, {
|
|
26
|
-
maxRetries: 3,
|
|
27
|
-
baseDelayMs: 1, // fast for tests
|
|
28
|
-
});
|
|
29
|
-
expect(result).toEqual({ id: "456" });
|
|
30
|
-
expect(fn).toHaveBeenCalledTimes(3);
|
|
31
|
-
});
|
|
32
|
-
it("throws SerializationRetryExhaustedError when all retries fail", async () => {
|
|
33
|
-
const fn = vi.fn().mockRejectedValue(makePostgresError("40001"));
|
|
34
|
-
await expect(withSerializableRetry(fn, { maxRetries: 2, baseDelayMs: 1 })).rejects.toThrow(SerializationRetryExhaustedError);
|
|
35
|
-
// Initial attempt + 2 retries = 3 calls
|
|
36
|
-
expect(fn).toHaveBeenCalledTimes(3);
|
|
37
|
-
});
|
|
38
|
-
it("rethrows unknown errors without retrying", async () => {
|
|
39
|
-
const fn = vi.fn().mockRejectedValue(new Error("connection refused"));
|
|
40
|
-
await expect(withSerializableRetry(fn)).rejects.toThrow("connection refused");
|
|
41
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
42
|
-
});
|
|
43
|
-
it("uses default options (3 retries, 50ms base delay)", async () => {
|
|
44
|
-
const fn = vi
|
|
45
|
-
.fn()
|
|
46
|
-
.mockRejectedValueOnce(makePostgresError("40001"))
|
|
47
|
-
.mockResolvedValueOnce("ok");
|
|
48
|
-
const result = await withSerializableRetry(fn);
|
|
49
|
-
expect(result).toBe("ok");
|
|
50
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
//# sourceMappingURL=serialization-retry.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialization-retry.test.js","sourceRoot":"","sources":["../../src/__tests__/serialization-retry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhooks.test.ts"],"names":[],"mappings":""}
|