@bash-app/bash-common 30.246.0 → 30.253.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/README.md +4 -0
- package/dist/__tests__/bashSectionTitleForBashType.test.js +32 -1
- package/dist/__tests__/bashSectionTitleForBashType.test.js.map +1 -1
- package/dist/__tests__/definitionsHelpers.test.d.ts +2 -0
- package/dist/__tests__/definitionsHelpers.test.d.ts.map +1 -0
- package/dist/__tests__/definitionsHelpers.test.js +85 -0
- package/dist/__tests__/definitionsHelpers.test.js.map +1 -0
- package/dist/__tests__/extendedSchemas.test.d.ts +2 -0
- package/dist/__tests__/extendedSchemas.test.d.ts.map +1 -0
- package/dist/__tests__/extendedSchemas.test.js +40 -0
- package/dist/__tests__/extendedSchemas.test.js.map +1 -0
- package/dist/__tests__/membershipDefinitions.test.d.ts +2 -0
- package/dist/__tests__/membershipDefinitions.test.d.ts.map +1 -0
- package/dist/__tests__/membershipDefinitions.test.js +95 -0
- package/dist/__tests__/membershipDefinitions.test.js.map +1 -0
- package/dist/__tests__/ticketAdmission.test.d.ts +5 -0
- package/dist/__tests__/ticketAdmission.test.d.ts.map +1 -0
- package/dist/__tests__/ticketAdmission.test.js +42 -0
- package/dist/__tests__/ticketAdmission.test.js.map +1 -0
- package/dist/__tests__/utmAttribution.test.d.ts +2 -0
- package/dist/__tests__/utmAttribution.test.d.ts.map +1 -0
- package/dist/__tests__/utmAttribution.test.js +92 -0
- package/dist/__tests__/utmAttribution.test.js.map +1 -0
- package/dist/definitions.d.ts +18 -5
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +84 -22
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +41 -1
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +27 -0
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/sms/smsTemplates.d.ts +27 -0
- package/dist/sms/smsTemplates.d.ts.map +1 -0
- package/dist/sms/smsTemplates.js +11 -0
- package/dist/sms/smsTemplates.js.map +1 -0
- package/dist/utils/__tests__/addressUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/addressUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/addressUtils.test.js +138 -0
- package/dist/utils/__tests__/addressUtils.test.js.map +1 -0
- package/dist/utils/__tests__/apiUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/apiUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/apiUtils.test.js +161 -0
- package/dist/utils/__tests__/apiUtils.test.js.map +1 -0
- package/dist/utils/__tests__/arrayUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/arrayUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/arrayUtils.test.js +23 -0
- package/dist/utils/__tests__/arrayUtils.test.js.map +1 -0
- package/dist/utils/__tests__/blogUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/blogUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/blogUtils.test.js +102 -0
- package/dist/utils/__tests__/blogUtils.test.js.map +1 -0
- package/dist/utils/__tests__/dateTimeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/dateTimeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/dateTimeUtils.test.js +180 -0
- package/dist/utils/__tests__/dateTimeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts +2 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.d.ts.map +1 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.js +356 -0
- package/dist/utils/__tests__/discountEngine/discountCalculator.test.js.map +1 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.js +26 -0
- package/dist/utils/__tests__/generalDateTimeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/luxonUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/luxonUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/luxonUtils.test.js +139 -0
- package/dist/utils/__tests__/luxonUtils.test.js.map +1 -0
- package/dist/utils/__tests__/mathUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/mathUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/mathUtils.test.js +26 -0
- package/dist/utils/__tests__/mathUtils.test.js.map +1 -0
- package/dist/utils/__tests__/objUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/objUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/objUtils.test.js +41 -0
- package/dist/utils/__tests__/objUtils.test.js.map +1 -0
- package/dist/utils/__tests__/paymentUtils.test.js +48 -1
- package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
- package/dist/utils/__tests__/qrCodeUtils.test.js +16 -1
- package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -1
- package/dist/utils/__tests__/reviewUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/reviewUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/reviewUtils.test.js +49 -0
- package/dist/utils/__tests__/reviewUtils.test.js.map +1 -0
- package/dist/utils/__tests__/slugUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/slugUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/slugUtils.test.js +103 -0
- package/dist/utils/__tests__/slugUtils.test.js.map +1 -0
- package/dist/utils/__tests__/sortUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/sortUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/sortUtils.test.js +28 -0
- package/dist/utils/__tests__/sortUtils.test.js.map +1 -0
- package/dist/utils/__tests__/stringUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/stringUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/stringUtils.test.js +27 -0
- package/dist/utils/__tests__/stringUtils.test.js.map +1 -0
- package/dist/utils/__tests__/ticketListUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/ticketListUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/ticketListUtils.test.js +41 -0
- package/dist/utils/__tests__/ticketListUtils.test.js.map +1 -0
- package/dist/utils/__tests__/typeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/typeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/typeUtils.test.js +11 -0
- package/dist/utils/__tests__/typeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts +5 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.js +66 -0
- package/dist/utils/__tests__/userSubscriptionUtils.test.js.map +1 -0
- package/dist/utils/__tests__/userUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/userUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/userUtils.test.js +34 -0
- package/dist/utils/__tests__/userUtils.test.js.map +1 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.d.ts +2 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.d.ts.map +1 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.js +25 -0
- package/dist/utils/__tests__/venueTypeFormatter.test.js.map +1 -0
- package/dist/utils/service/__tests__/regexUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/regexUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/regexUtils.test.js +51 -0
- package/dist/utils/service/__tests__/regexUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.js +17 -0
- package/dist/utils/service/__tests__/serviceDBUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js +60 -0
- package/dist/utils/service/__tests__/serviceFeaturedSortUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceUtils.test.js +135 -0
- package/dist/utils/service/__tests__/serviceUtils.test.js.map +1 -0
- package/dist/utils/service/serviceFeaturedSortUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceFeaturedSortUtils.js +5 -1
- package/dist/utils/service/serviceFeaturedSortUtils.js.map +1 -1
- package/dist/utils/ticketListUtils.d.ts.map +1 -1
- package/dist/utils/ticketListUtils.js +11 -7
- package/dist/utils/ticketListUtils.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +47 -0
- package/src/__tests__/bashSectionTitleForBashType.test.ts +48 -1
- package/src/__tests__/definitionsHelpers.test.ts +136 -0
- package/src/__tests__/extendedSchemas.test.ts +50 -0
- package/src/__tests__/membershipDefinitions.test.ts +144 -0
- package/src/__tests__/ticketAdmission.test.ts +73 -0
- package/src/__tests__/utmAttribution.test.ts +118 -0
- package/src/definitions.ts +86 -23
- package/src/extendedSchemas.ts +35 -1
- package/src/index.ts +1 -0
- package/src/sms/smsTemplates.ts +20 -0
- package/src/utils/__tests__/addressUtils.test.ts +176 -0
- package/src/utils/__tests__/apiUtils.test.ts +219 -0
- package/src/utils/__tests__/arrayUtils.test.ts +26 -0
- package/src/utils/__tests__/blogUtils.test.ts +145 -0
- package/src/utils/__tests__/dateTimeUtils.test.ts +233 -0
- package/src/utils/__tests__/discountEngine/discountCalculator.test.ts +410 -0
- package/src/utils/__tests__/generalDateTimeUtils.test.ts +34 -0
- package/src/utils/__tests__/luxonUtils.test.ts +171 -0
- package/src/utils/__tests__/mathUtils.test.ts +30 -0
- package/src/utils/__tests__/objUtils.test.ts +48 -0
- package/src/utils/__tests__/paymentUtils.test.ts +59 -0
- package/src/utils/__tests__/qrCodeUtils.test.ts +28 -1
- package/src/utils/__tests__/reviewUtils.test.ts +65 -0
- package/src/utils/__tests__/slugUtils.test.ts +143 -0
- package/src/utils/__tests__/sortUtils.test.ts +33 -0
- package/src/utils/__tests__/stringUtils.test.ts +37 -0
- package/src/utils/__tests__/ticketListUtils.test.ts +47 -0
- package/src/utils/__tests__/typeUtils.test.ts +12 -0
- package/src/utils/__tests__/userSubscriptionUtils.test.ts +87 -0
- package/src/utils/__tests__/userUtils.test.ts +41 -0
- package/src/utils/__tests__/venueTypeFormatter.test.ts +31 -0
- package/src/utils/service/__tests__/regexUtils.test.ts +66 -0
- package/src/utils/service/__tests__/serviceDBUtils.test.ts +19 -0
- package/src/utils/service/__tests__/serviceFeaturedSortUtils.test.ts +67 -0
- package/src/utils/service/__tests__/serviceUtils.test.ts +190 -0
- package/src/utils/service/serviceFeaturedSortUtils.ts +5 -1
- package/src/utils/ticketListUtils.ts +13 -6
package/prisma/schema.prisma
CHANGED
|
@@ -1791,6 +1791,8 @@ model Ticket {
|
|
|
1791
1791
|
* Set when we send the "your waitlist window expires soon" reminder (once per ticket).
|
|
1792
1792
|
*/
|
|
1793
1793
|
waitlistReminderSentAt DateTime?
|
|
1794
|
+
/// When we last sent the 1-hour-before-start reminder (email/push/SMS); avoids duplicate cron sends.
|
|
1795
|
+
lastReminded1hAt DateTime?
|
|
1794
1796
|
refundRequestedAt DateTime? // Set when RefundPending; cleared once refund processes
|
|
1795
1797
|
checkInLocation String?
|
|
1796
1798
|
checkOutLocation String?
|
|
@@ -2332,6 +2334,8 @@ model User {
|
|
|
2332
2334
|
zipCode String?
|
|
2333
2335
|
country String? @default("US")
|
|
2334
2336
|
phone String?
|
|
2337
|
+
/// Set when the user completes SMS phone verification (OTP).
|
|
2338
|
+
phoneVerifiedAt DateTime?
|
|
2335
2339
|
documentIDId String? @unique
|
|
2336
2340
|
/// Encrypted-S3 key for the user's stored government-ID image (deleted per retention policy / on ID expiration)
|
|
2337
2341
|
govIdImageS3Key String?
|
|
@@ -2639,6 +2643,24 @@ model User {
|
|
|
2639
2643
|
// Vanity paths
|
|
2640
2644
|
requestedVanityPaths EventVanityPath[] @relation("VanityRequests")
|
|
2641
2645
|
approvedVanityPaths EventVanityPath[] @relation("VanityApprovals")
|
|
2646
|
+
|
|
2647
|
+
phoneVerificationCodes PhoneVerificationCode[]
|
|
2648
|
+
smsSendLogs SmsSendLog[]
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
model PhoneVerificationCode {
|
|
2652
|
+
id String @id @default(cuid())
|
|
2653
|
+
userId String
|
|
2654
|
+
phoneE164 String
|
|
2655
|
+
codeHash String
|
|
2656
|
+
attempts Int @default(0)
|
|
2657
|
+
expiresAt DateTime
|
|
2658
|
+
consumedAt DateTime?
|
|
2659
|
+
createdAt DateTime @default(now())
|
|
2660
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
2661
|
+
|
|
2662
|
+
@@index([userId])
|
|
2663
|
+
@@index([phoneE164])
|
|
2642
2664
|
}
|
|
2643
2665
|
|
|
2644
2666
|
model UserPreferences {
|
|
@@ -3843,12 +3865,37 @@ model Organization {
|
|
|
3843
3865
|
/// Recorded for analytics — informational only, NOT a gate for verification or fee waivers.
|
|
3844
3866
|
stripeNonprofitPricingStatus StripeNonprofitPricingStatus @default(NotApplied)
|
|
3845
3867
|
|
|
3868
|
+
/// Monthly cap on estimated SMS spend (cents) for transactional templated SMS tied to this org.
|
|
3869
|
+
smsMonthlyCapCents Int @default(500)
|
|
3870
|
+
smsMonthlySentCents Int @default(0)
|
|
3871
|
+
smsMonthAnchor DateTime @default(now())
|
|
3872
|
+
smsAutoShutoffAt DateTime?
|
|
3873
|
+
|
|
3874
|
+
smsSendLogs SmsSendLog[]
|
|
3875
|
+
|
|
3846
3876
|
@@index([ownerId])
|
|
3847
3877
|
@@index([slug])
|
|
3848
3878
|
@@index([parentOrganizationId])
|
|
3849
3879
|
@@index([nonprofitVerifiedAt])
|
|
3850
3880
|
}
|
|
3851
3881
|
|
|
3882
|
+
model SmsSendLog {
|
|
3883
|
+
id String @id @default(cuid())
|
|
3884
|
+
organizationId String?
|
|
3885
|
+
userId String
|
|
3886
|
+
templateKey String
|
|
3887
|
+
phoneE164 String
|
|
3888
|
+
segments Int
|
|
3889
|
+
estCostCents Int
|
|
3890
|
+
snsMessageId String?
|
|
3891
|
+
sentAt DateTime @default(now())
|
|
3892
|
+
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: SetNull)
|
|
3893
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
3894
|
+
|
|
3895
|
+
@@index([organizationId, sentAt])
|
|
3896
|
+
@@index([userId, sentAt])
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3852
3899
|
/// Sports-specific metadata for league or team organizations (1:1 with Organization).
|
|
3853
3900
|
model SportsProfile {
|
|
3854
3901
|
id String @id @default(cuid())
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { BashEventType } from "@prisma/client";
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
bashSectionTitleForBashType,
|
|
19
|
+
canonicalBashEventRowKey,
|
|
20
|
+
} from "../definitions.js";
|
|
18
21
|
|
|
19
22
|
describe("bashSectionTitleForBashType", () => {
|
|
20
23
|
describe("free-text scraper section keys (non-enum)", () => {
|
|
@@ -92,4 +95,48 @@ describe("bashSectionTitleForBashType", () => {
|
|
|
92
95
|
expect(bashSectionTitleForBashType("Party")).toBe("Parties");
|
|
93
96
|
});
|
|
94
97
|
});
|
|
98
|
+
|
|
99
|
+
describe("keys that diverge from Prisma enum literals (case or display labels)", () => {
|
|
100
|
+
it("lower-case prisma value 'party' → 'Parties'", () => {
|
|
101
|
+
expect(bashSectionTitleForBashType("party")).toBe("Parties");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("lower-case 'fundraiser' → 'Fundraisers'", () => {
|
|
105
|
+
expect(bashSectionTitleForBashType("fundraiser")).toBe("Fundraisers");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("singular display label row key 'House Party' → 'House Parties'", () => {
|
|
109
|
+
expect(bashSectionTitleForBashType("House Party")).toBe("House Parties");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("trimmed stray whitespace still pluralizes", () => {
|
|
113
|
+
expect(bashSectionTitleForBashType(" Fundraiser ")).toBe("Fundraisers");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("already-plural enum headings", () => {
|
|
118
|
+
it("normalizes casing when the row key matches a pluralized enum title", () => {
|
|
119
|
+
expect(bashSectionTitleForBashType("House Parties")).toBe(
|
|
120
|
+
"House Parties"
|
|
121
|
+
);
|
|
122
|
+
expect(bashSectionTitleForBashType("house parties")).toBe("House Parties");
|
|
123
|
+
expect(bashSectionTitleForBashType("Comedy Shows")).toBe("Comedy Shows");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("canonicalBashEventRowKey", () => {
|
|
129
|
+
it("maps singular labels and enum literals to the Prisma enum string", () => {
|
|
130
|
+
expect(canonicalBashEventRowKey("House Party")).toBe(
|
|
131
|
+
BashEventType.HouseParty
|
|
132
|
+
);
|
|
133
|
+
expect(canonicalBashEventRowKey(BashEventType.HouseParty)).toBe(
|
|
134
|
+
BashEventType.HouseParty
|
|
135
|
+
);
|
|
136
|
+
expect(canonicalBashEventRowKey("houseparty")).toBe(BashEventType.HouseParty);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("returns trimmed free-text when no enum matches", () => {
|
|
140
|
+
expect(canonicalBashEventRowKey("Graduation")).toBe("Graduation");
|
|
141
|
+
});
|
|
95
142
|
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
createEmptyFilterFields,
|
|
4
|
+
removeDataFromResult,
|
|
5
|
+
canonicalBashEventRowKey,
|
|
6
|
+
PASSWORD_REQUIREMENTS_REGEX,
|
|
7
|
+
PASSWORD_MIN_LENGTH,
|
|
8
|
+
EMPTY_FILTER_FIELDS,
|
|
9
|
+
ticketIsPaidForEventAdmission,
|
|
10
|
+
isUnpaidPayAtDoorTicket,
|
|
11
|
+
TICKET_PURCHASE_TYPE_COMP,
|
|
12
|
+
TICKET_PURCHASE_TYPE_PAY_AT_DOOR,
|
|
13
|
+
} from "../definitions.js";
|
|
14
|
+
|
|
15
|
+
describe("definitions helpers", () => {
|
|
16
|
+
describe("createEmptyFilterFields", () => {
|
|
17
|
+
test("returns fresh arrays not shared with EMPTY_FILTER_FIELDS", () => {
|
|
18
|
+
const a = createEmptyFilterFields();
|
|
19
|
+
const b = createEmptyFilterFields();
|
|
20
|
+
a.price.push("free");
|
|
21
|
+
expect(b.price).toEqual([]);
|
|
22
|
+
expect(EMPTY_FILTER_FIELDS.price).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("includes default boolean flags", () => {
|
|
26
|
+
expect(createEmptyFilterFields()).toMatchObject({
|
|
27
|
+
hasOpenTasks: false,
|
|
28
|
+
acceptsBashPoints: false,
|
|
29
|
+
ambashadorOnly: false,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("removeDataFromResult", () => {
|
|
35
|
+
test("strips data while preserving error metadata", () => {
|
|
36
|
+
const result = {
|
|
37
|
+
data: { id: "1" },
|
|
38
|
+
error: true,
|
|
39
|
+
code: "ERR",
|
|
40
|
+
} as any;
|
|
41
|
+
expect(removeDataFromResult(result)).toEqual({ error: true, code: "ERR" });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("returns undefined for nullish input", () => {
|
|
45
|
+
expect(removeDataFromResult(null)).toBeUndefined();
|
|
46
|
+
expect(removeDataFromResult(undefined)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("canonicalBashEventRowKey", () => {
|
|
51
|
+
test("normalizes singular labels to enum keys", () => {
|
|
52
|
+
expect(canonicalBashEventRowKey("House Party")).toBe("HouseParty");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("passes through unknown trimmed values", () => {
|
|
56
|
+
expect(canonicalBashEventRowKey(" Custom Row ")).toBe("Custom Row");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns empty string for blank input", () => {
|
|
60
|
+
expect(canonicalBashEventRowKey(" ")).toBe("");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("PASSWORD_REQUIREMENTS_REGEX", () => {
|
|
65
|
+
test("requires min length and mixed character classes", () => {
|
|
66
|
+
expect(PASSWORD_MIN_LENGTH).toBe(8);
|
|
67
|
+
expect(PASSWORD_REQUIREMENTS_REGEX.test("Abcd1234!")).toBe(true);
|
|
68
|
+
expect(PASSWORD_REQUIREMENTS_REGEX.test("Sh0rt!")).toBe(false);
|
|
69
|
+
expect(PASSWORD_REQUIREMENTS_REGEX.test("alllowercase1!")).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("ticketIsPaidForEventAdmission", () => {
|
|
74
|
+
test("free tiers always admit without payment", () => {
|
|
75
|
+
expect(
|
|
76
|
+
ticketIsPaidForEventAdmission({ purchaseType: "USD", paidOn: null }, 0)
|
|
77
|
+
).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("comp tickets admit without paidOn", () => {
|
|
81
|
+
expect(
|
|
82
|
+
ticketIsPaidForEventAdmission(
|
|
83
|
+
{ purchaseType: TICKET_PURCHASE_TYPE_COMP, paidOn: null },
|
|
84
|
+
2500
|
|
85
|
+
)
|
|
86
|
+
).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("pay-at-door requires paidOn when tier has price", () => {
|
|
90
|
+
expect(
|
|
91
|
+
ticketIsPaidForEventAdmission(
|
|
92
|
+
{ purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR, paidOn: null },
|
|
93
|
+
1000
|
|
94
|
+
)
|
|
95
|
+
).toBe(false);
|
|
96
|
+
expect(
|
|
97
|
+
ticketIsPaidForEventAdmission(
|
|
98
|
+
{ purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR, paidOn: new Date() },
|
|
99
|
+
1000
|
|
100
|
+
)
|
|
101
|
+
).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("standard paid tickets require paidOn", () => {
|
|
105
|
+
expect(
|
|
106
|
+
ticketIsPaidForEventAdmission({ purchaseType: "USD", paidOn: null }, 500)
|
|
107
|
+
).toBe(false);
|
|
108
|
+
expect(
|
|
109
|
+
ticketIsPaidForEventAdmission({ purchaseType: "USD", paidOn: new Date() }, 500)
|
|
110
|
+
).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("isUnpaidPayAtDoorTicket", () => {
|
|
115
|
+
test("true only for pay-at-door without paidOn", () => {
|
|
116
|
+
expect(
|
|
117
|
+
isUnpaidPayAtDoorTicket({
|
|
118
|
+
purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR,
|
|
119
|
+
paidOn: null,
|
|
120
|
+
})
|
|
121
|
+
).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("false once paid or for other purchase types", () => {
|
|
125
|
+
expect(
|
|
126
|
+
isUnpaidPayAtDoorTicket({
|
|
127
|
+
purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR,
|
|
128
|
+
paidOn: new Date(),
|
|
129
|
+
})
|
|
130
|
+
).toBe(false);
|
|
131
|
+
expect(
|
|
132
|
+
isUnpaidPayAtDoorTicket({ purchaseType: "USD", paidOn: null })
|
|
133
|
+
).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
BASH_EVENT_DATA_TO_CLONE,
|
|
4
|
+
BASH_EVENT_DATA_TO_REMOVE,
|
|
5
|
+
FRONT_END_USER_DATA_TO_SELECT,
|
|
6
|
+
PUBLIC_USER_SUBSCRIPTION_DATA_TO_SELECT,
|
|
7
|
+
TICKET_TIER_DATA_TO_INCLUDE,
|
|
8
|
+
} from "../extendedSchemas.js";
|
|
9
|
+
|
|
10
|
+
describe("extendedSchemas exports", () => {
|
|
11
|
+
test("PUBLIC_USER_SUBSCRIPTION_DATA_TO_SELECT includes subscription counters", () => {
|
|
12
|
+
expect(PUBLIC_USER_SUBSCRIPTION_DATA_TO_SELECT).toMatchObject({
|
|
13
|
+
type: true,
|
|
14
|
+
status: true,
|
|
15
|
+
ownerId: true,
|
|
16
|
+
subscriptionCount: true,
|
|
17
|
+
listedCount: true,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("FRONT_END_USER_DATA_TO_SELECT exposes public profile fields", () => {
|
|
22
|
+
expect(FRONT_END_USER_DATA_TO_SELECT).toMatchObject({
|
|
23
|
+
id: true,
|
|
24
|
+
username: true,
|
|
25
|
+
email: true,
|
|
26
|
+
givenName: true,
|
|
27
|
+
familyName: true,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("TICKET_TIER_DATA_TO_INCLUDE nests tickets and promoCodes", () => {
|
|
32
|
+
expect(TICKET_TIER_DATA_TO_INCLUDE.tickets).toBeDefined();
|
|
33
|
+
expect(TICKET_TIER_DATA_TO_INCLUDE.promoCodes).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("BASH_EVENT_DATA_TO_CLONE lists cloneable relations", () => {
|
|
37
|
+
expect(BASH_EVENT_DATA_TO_CLONE).toEqual([
|
|
38
|
+
"ticketTiers",
|
|
39
|
+
"media",
|
|
40
|
+
"recurrence",
|
|
41
|
+
"invitations",
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("BASH_EVENT_DATA_TO_REMOVE strips nested creator and tasks", () => {
|
|
46
|
+
expect(BASH_EVENT_DATA_TO_REMOVE).toContain("creator");
|
|
47
|
+
expect(BASH_EVENT_DATA_TO_REMOVE).toContain("eventTasks");
|
|
48
|
+
expect(BASH_EVENT_DATA_TO_REMOVE).toContain("tickets");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { MembershipTier } from "@prisma/client";
|
|
3
|
+
import { BashPassTier } from "@prisma/client";
|
|
4
|
+
import {
|
|
5
|
+
calculateBashEventFee,
|
|
6
|
+
calculateServiceBookingFee,
|
|
7
|
+
calculatePartnerPaymentFee,
|
|
8
|
+
isTrustedHostMembershipTier,
|
|
9
|
+
hasFeatureAccess,
|
|
10
|
+
getRequiredTierForFeature,
|
|
11
|
+
hasFeatureLimit,
|
|
12
|
+
getFeatureLimit,
|
|
13
|
+
hasReachedFeatureLimit,
|
|
14
|
+
MEMBERSHIP_FEATURES,
|
|
15
|
+
BASIC_TIER_MAX_PROMO_CODES_PER_EVENT,
|
|
16
|
+
MEMBERSHIP_BASH_PASS_TIER,
|
|
17
|
+
MEMBERSHIP_TIER_ORDER,
|
|
18
|
+
MEMBERSHIP_TIER_HIERARCHY,
|
|
19
|
+
} from "../membershipDefinitions.js";
|
|
20
|
+
|
|
21
|
+
describe("membershipDefinitions helpers", () => {
|
|
22
|
+
describe("fee calculators", () => {
|
|
23
|
+
test("calculateBashEventFee applies percent, minimum, and cap", () => {
|
|
24
|
+
expect(calculateBashEventFee(10000, MembershipTier.Basic)).toBeGreaterThanOrEqual(0);
|
|
25
|
+
expect(calculateBashEventFee(0, MembershipTier.Premium)).toBe(
|
|
26
|
+
calculateBashEventFee(1, MembershipTier.Premium)
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("calculateServiceBookingFee includes Stripe fixed fee", () => {
|
|
31
|
+
const fee = calculateServiceBookingFee(10000, MembershipTier.Basic);
|
|
32
|
+
expect(fee).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("calculatePartnerPaymentFee scales with amount", () => {
|
|
36
|
+
const small = calculatePartnerPaymentFee(1000, MembershipTier.Pro);
|
|
37
|
+
const large = calculatePartnerPaymentFee(100000, MembershipTier.Pro);
|
|
38
|
+
expect(large).toBeGreaterThan(small);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("isTrustedHostMembershipTier", () => {
|
|
43
|
+
test("Premium and above are trusted", () => {
|
|
44
|
+
expect(isTrustedHostMembershipTier(MembershipTier.Premium)).toBe(true);
|
|
45
|
+
expect(isTrustedHostMembershipTier(MembershipTier.Legend)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Basic and missing tier are not trusted", () => {
|
|
49
|
+
expect(isTrustedHostMembershipTier(MembershipTier.Basic)).toBe(false);
|
|
50
|
+
expect(isTrustedHostMembershipTier(null)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("hasFeatureAccess", () => {
|
|
55
|
+
test("Basic gets promo codes", () => {
|
|
56
|
+
expect(hasFeatureAccess(MembershipTier.Basic, MEMBERSHIP_FEATURES.PROMO_CODES)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("Pro unlocks vendor directory", () => {
|
|
60
|
+
expect(hasFeatureAccess(MembershipTier.Pro, MEMBERSHIP_FEATURES.VENDOR_DIRECTORY)).toBe(
|
|
61
|
+
true
|
|
62
|
+
);
|
|
63
|
+
expect(hasFeatureAccess(MembershipTier.Premium, MEMBERSHIP_FEATURES.VENDOR_DIRECTORY)).toBe(
|
|
64
|
+
false
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("unknown feature returns false", () => {
|
|
69
|
+
expect(hasFeatureAccess(MembershipTier.Legend, "not_a_feature")).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("getRequiredTierForFeature", () => {
|
|
74
|
+
test("returns minimum tier that unlocks feature", () => {
|
|
75
|
+
expect(getRequiredTierForFeature(MEMBERSHIP_FEATURES.PROMO_CODES)).toBe(MembershipTier.Basic);
|
|
76
|
+
expect(getRequiredTierForFeature(MEMBERSHIP_FEATURES.VENDOR_DIRECTORY)).toBe(
|
|
77
|
+
MembershipTier.Pro
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("hasFeatureLimit / getFeatureLimit", () => {
|
|
83
|
+
test("reads boolean limits from MEMBERSHIP_LIMITS", () => {
|
|
84
|
+
expect(hasFeatureLimit(MembershipTier.Basic, "canCreateEvents")).toBe(true);
|
|
85
|
+
expect(hasFeatureLimit(MembershipTier.Basic, "analyticsAccess")).toBe(false);
|
|
86
|
+
expect(getFeatureLimit(MembershipTier.Pro, "maxServiceListings")).toBe(3);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("hasReachedFeatureLimit", () => {
|
|
91
|
+
test("blocks when featured event quota is exhausted", () => {
|
|
92
|
+
expect(hasReachedFeatureLimit(MembershipTier.Pro, "featuredEvents", 1)).toBe(true);
|
|
93
|
+
expect(hasReachedFeatureLimit(MembershipTier.Pro, "featuredEvents", 0)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("Basic tier promo code cap constant", () => {
|
|
98
|
+
expect(BASIC_TIER_MAX_PROMO_CODES_PER_EVENT).toBe(3);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("tier ordering constants", () => {
|
|
102
|
+
test("MEMBERSHIP_TIER_HIERARCHY starts at Basic and ends at Legend", () => {
|
|
103
|
+
expect(MEMBERSHIP_TIER_HIERARCHY[0]).toBe(MembershipTier.Basic);
|
|
104
|
+
expect(MEMBERSHIP_TIER_HIERARCHY.at(-1)).toBe(MembershipTier.Legend);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("MEMBERSHIP_TIER_ORDER increases with tier level", () => {
|
|
108
|
+
expect(MEMBERSHIP_TIER_ORDER[MembershipTier.Basic]).toBeLessThan(
|
|
109
|
+
MEMBERSHIP_TIER_ORDER[MembershipTier.Premium]
|
|
110
|
+
);
|
|
111
|
+
expect(MEMBERSHIP_TIER_ORDER[MembershipTier.Pro]).toBeLessThan(
|
|
112
|
+
MEMBERSHIP_TIER_ORDER[MembershipTier.Legend]
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("MEMBERSHIP_BASH_PASS_TIER", () => {
|
|
118
|
+
test("maps paid membership tiers to bundled BashPass levels", () => {
|
|
119
|
+
expect(MEMBERSHIP_BASH_PASS_TIER[MembershipTier.Premium]).toBe(BashPassTier.LITE);
|
|
120
|
+
expect(MEMBERSHIP_BASH_PASS_TIER[MembershipTier.Pro]).toBe(BashPassTier.STANDARD);
|
|
121
|
+
expect(MEMBERSHIP_BASH_PASS_TIER[MembershipTier.Elite]).toBe(BashPassTier.UNLIMITED);
|
|
122
|
+
expect(MEMBERSHIP_BASH_PASS_TIER[MembershipTier.Basic]).toBeUndefined();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("getRequiredTierForFeature edge cases", () => {
|
|
127
|
+
test("returns null for unknown feature", () => {
|
|
128
|
+
expect(getRequiredTierForFeature("__not_a_real_feature__")).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("hasReachedFeatureLimit default branch", () => {
|
|
133
|
+
test("returns false for unsupported feature keys", () => {
|
|
134
|
+
expect(hasReachedFeatureLimit(MembershipTier.Pro, "featuredEvents", 999)).toBe(true);
|
|
135
|
+
expect(
|
|
136
|
+
hasReachedFeatureLimit(
|
|
137
|
+
MembershipTier.Pro,
|
|
138
|
+
"unknownFeature" as unknown as "featuredEvents",
|
|
139
|
+
0
|
|
140
|
+
)
|
|
141
|
+
).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket admission helpers — paid vs pay-at-door state.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "@jest/globals";
|
|
6
|
+
import {
|
|
7
|
+
ticketIsPaidForEventAdmission,
|
|
8
|
+
isUnpaidPayAtDoorTicket,
|
|
9
|
+
TICKET_PURCHASE_TYPE_COMP,
|
|
10
|
+
TICKET_PURCHASE_TYPE_PAY_AT_DOOR,
|
|
11
|
+
TICKET_PURCHASE_TYPE_USD,
|
|
12
|
+
} from "../definitions.js";
|
|
13
|
+
|
|
14
|
+
describe("ticketIsPaidForEventAdmission", () => {
|
|
15
|
+
test("free tiers always admit without payment", () => {
|
|
16
|
+
expect(ticketIsPaidForEventAdmission({ purchaseType: TICKET_PURCHASE_TYPE_USD, paidOn: null }, 0)).toBe(
|
|
17
|
+
true
|
|
18
|
+
);
|
|
19
|
+
expect(
|
|
20
|
+
ticketIsPaidForEventAdmission({ purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR, paidOn: null }, 0)
|
|
21
|
+
).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("comp tickets admit regardless of paidOn", () => {
|
|
25
|
+
expect(
|
|
26
|
+
ticketIsPaidForEventAdmission({ purchaseType: TICKET_PURCHASE_TYPE_COMP, paidOn: null }, 2500)
|
|
27
|
+
).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("paid USD tickets admit when paidOn is set", () => {
|
|
31
|
+
expect(
|
|
32
|
+
ticketIsPaidForEventAdmission(
|
|
33
|
+
{ purchaseType: TICKET_PURCHASE_TYPE_USD, paidOn: new Date("2024-01-01") },
|
|
34
|
+
2500
|
|
35
|
+
)
|
|
36
|
+
).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("unpaid USD tickets do not admit", () => {
|
|
40
|
+
expect(
|
|
41
|
+
ticketIsPaidForEventAdmission({ purchaseType: TICKET_PURCHASE_TYPE_USD, paidOn: null }, 2500)
|
|
42
|
+
).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("pay-at-door tickets admit only after paidOn is set", () => {
|
|
46
|
+
const unpaid = { purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR, paidOn: null };
|
|
47
|
+
const paid = { purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR, paidOn: "2024-06-01T12:00:00Z" };
|
|
48
|
+
expect(ticketIsPaidForEventAdmission(unpaid, 1500)).toBe(false);
|
|
49
|
+
expect(ticketIsPaidForEventAdmission(paid, 1500)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("isUnpaidPayAtDoorTicket", () => {
|
|
54
|
+
test("returns true only for pay-at-door without paidOn", () => {
|
|
55
|
+
expect(isUnpaidPayAtDoorTicket({ purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR, paidOn: null })).toBe(
|
|
56
|
+
true
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("returns false once pay-at-door ticket is marked paid", () => {
|
|
61
|
+
expect(
|
|
62
|
+
isUnpaidPayAtDoorTicket({
|
|
63
|
+
purchaseType: TICKET_PURCHASE_TYPE_PAY_AT_DOOR,
|
|
64
|
+
paidOn: new Date(),
|
|
65
|
+
})
|
|
66
|
+
).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("returns false for other purchase types even when unpaid", () => {
|
|
70
|
+
expect(isUnpaidPayAtDoorTicket({ purchaseType: TICKET_PURCHASE_TYPE_USD, paidOn: null })).toBe(false);
|
|
71
|
+
expect(isUnpaidPayAtDoorTicket({ purchaseType: TICKET_PURCHASE_TYPE_COMP, paidOn: null })).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
getStoredUtmFields,
|
|
4
|
+
utmFieldsFromCheckoutArgs,
|
|
5
|
+
utmFieldsFromStripeMetadata,
|
|
6
|
+
utmFieldsToStripeMetadata,
|
|
7
|
+
utmFieldsToTicketColumns,
|
|
8
|
+
} from "../utmAttribution.js";
|
|
9
|
+
|
|
10
|
+
describe("utmAttribution", () => {
|
|
11
|
+
describe("utmFieldsFromCheckoutArgs", () => {
|
|
12
|
+
test("returns undefined when all fields blank", () => {
|
|
13
|
+
expect(utmFieldsFromCheckoutArgs({ utmSource: " ", utmMedium: "", utmCampaign: undefined })).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("trims and includes only non-empty fields", () => {
|
|
17
|
+
expect(
|
|
18
|
+
utmFieldsFromCheckoutArgs({
|
|
19
|
+
utmSource: " newsletter ",
|
|
20
|
+
utmMedium: "email",
|
|
21
|
+
})
|
|
22
|
+
).toEqual({ utmSource: "newsletter", utmMedium: "email" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("caps field length at 500 characters", () => {
|
|
26
|
+
const long = "a".repeat(600);
|
|
27
|
+
const result = utmFieldsFromCheckoutArgs({ utmSource: long });
|
|
28
|
+
expect(result?.utmSource?.length).toBe(500);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("utmFieldsToStripeMetadata", () => {
|
|
33
|
+
test("maps to snake_case Stripe keys", () => {
|
|
34
|
+
expect(
|
|
35
|
+
utmFieldsToStripeMetadata({
|
|
36
|
+
utmSource: "google",
|
|
37
|
+
utmCampaign: "spring",
|
|
38
|
+
})
|
|
39
|
+
).toEqual({ utm_source: "google", utm_campaign: "spring" });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("returns empty object for undefined input", () => {
|
|
43
|
+
expect(utmFieldsToStripeMetadata(undefined)).toEqual({});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("utmFieldsToTicketColumns", () => {
|
|
48
|
+
test("maps to Prisma Ticket column names", () => {
|
|
49
|
+
expect(
|
|
50
|
+
utmFieldsToTicketColumns({
|
|
51
|
+
utmSource: "fb",
|
|
52
|
+
utmMedium: "cpc",
|
|
53
|
+
utmCampaign: "launch",
|
|
54
|
+
})
|
|
55
|
+
).toEqual({
|
|
56
|
+
utmSource: "fb",
|
|
57
|
+
utmMedium: "cpc",
|
|
58
|
+
utmCampaign: "launch",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("omits empty fields", () => {
|
|
63
|
+
expect(utmFieldsToTicketColumns({ utmSource: "x" })).toEqual({ utmSource: "x" });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("utmFieldsFromStripeMetadata", () => {
|
|
68
|
+
test("reads snake_case metadata into camelCase fields", () => {
|
|
69
|
+
expect(
|
|
70
|
+
utmFieldsFromStripeMetadata({
|
|
71
|
+
utm_source: " twitter ",
|
|
72
|
+
utm_medium: "social",
|
|
73
|
+
})
|
|
74
|
+
).toEqual({ utmSource: "twitter", utmMedium: "social" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("returns empty object for null metadata", () => {
|
|
78
|
+
expect(utmFieldsFromStripeMetadata(null)).toEqual({});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("getStoredUtmFields", () => {
|
|
83
|
+
const originalSessionStorage = global.sessionStorage;
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
const store = new Map<string, string>();
|
|
87
|
+
Object.defineProperty(global, "sessionStorage", {
|
|
88
|
+
configurable: true,
|
|
89
|
+
value: {
|
|
90
|
+
getItem: (key: string) => store.get(key) ?? null,
|
|
91
|
+
setItem: (key: string, value: string) => {
|
|
92
|
+
store.set(key, value);
|
|
93
|
+
},
|
|
94
|
+
removeItem: (key: string) => {
|
|
95
|
+
store.delete(key);
|
|
96
|
+
},
|
|
97
|
+
clear: () => store.clear(),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
Object.defineProperty(global, "sessionStorage", {
|
|
104
|
+
configurable: true,
|
|
105
|
+
value: originalSessionStorage,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("reads previously stored session values", () => {
|
|
110
|
+
sessionStorage.setItem("bash_utm_source", "newsletter");
|
|
111
|
+
sessionStorage.setItem("bash_utm_campaign", "june");
|
|
112
|
+
expect(getStoredUtmFields()).toEqual({
|
|
113
|
+
utmSource: "newsletter",
|
|
114
|
+
utmCampaign: "june",
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|