@bash-app/bash-common 30.224.0 → 30.226.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/dist/__tests__/agePolicy.test.d.ts +2 -0
- package/dist/__tests__/agePolicy.test.d.ts.map +1 -0
- package/dist/__tests__/agePolicy.test.js +203 -0
- package/dist/__tests__/agePolicy.test.js.map +1 -0
- package/dist/__tests__/getDisplayGender.test.d.ts +2 -0
- package/dist/__tests__/getDisplayGender.test.d.ts.map +1 -0
- package/dist/__tests__/getDisplayGender.test.js +34 -0
- package/dist/__tests__/getDisplayGender.test.js.map +1 -0
- package/dist/__tests__/statusEnums.test.d.ts +2 -0
- package/dist/__tests__/statusEnums.test.d.ts.map +1 -0
- package/dist/__tests__/statusEnums.test.js +24 -0
- package/dist/__tests__/statusEnums.test.js.map +1 -0
- package/dist/aiApproval.d.ts +64 -0
- package/dist/aiApproval.d.ts.map +1 -0
- package/dist/aiApproval.js +54 -0
- package/dist/aiApproval.js.map +1 -0
- package/dist/definitions.d.ts +44 -2
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +106 -2
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +237 -104
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +12 -2
- 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/legalTemplates.d.ts +1 -1
- package/dist/legalTemplates.js +1 -1
- package/dist/utils/__tests__/badgeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/badgeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/badgeUtils.test.js +157 -0
- package/dist/utils/__tests__/badgeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.js +114 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.js.map +1 -0
- package/dist/utils/__tests__/contentFilterUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/contentFilterUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/contentFilterUtils.test.js +443 -0
- package/dist/utils/__tests__/contentFilterUtils.test.js.map +1 -0
- package/dist/utils/__tests__/displayDateUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/displayDateUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/displayDateUtils.test.js +105 -0
- package/dist/utils/__tests__/displayDateUtils.test.js.map +1 -0
- package/dist/utils/__tests__/getBackendHost.test.d.ts +2 -0
- package/dist/utils/__tests__/getBackendHost.test.d.ts.map +1 -0
- package/dist/utils/__tests__/getBackendHost.test.js +104 -0
- package/dist/utils/__tests__/getBackendHost.test.js.map +1 -0
- package/dist/utils/__tests__/getFrontendHost.test.d.ts +2 -0
- package/dist/utils/__tests__/getFrontendHost.test.d.ts.map +1 -0
- package/dist/utils/__tests__/getFrontendHost.test.js +207 -0
- package/dist/utils/__tests__/getFrontendHost.test.js.map +1 -0
- package/dist/utils/__tests__/promoCodesUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/promoCodesUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/promoCodesUtils.test.js +183 -0
- package/dist/utils/__tests__/promoCodesUtils.test.js.map +1 -0
- package/dist/utils/__tests__/qrCodeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/qrCodeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/qrCodeUtils.test.js +99 -0
- package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.js +75 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.js.map +1 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts +2 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts.map +1 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js +233 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js.map +1 -0
- package/dist/utils/blog/blogCommentDbUtils.d.ts +161 -0
- package/dist/utils/blog/blogCommentDbUtils.d.ts.map +1 -0
- package/dist/utils/blog/blogCommentDbUtils.js +111 -0
- package/dist/utils/blog/blogCommentDbUtils.js.map +1 -0
- package/dist/utils/blog/blogDbUtils.d.ts +10 -0
- package/dist/utils/blog/blogDbUtils.d.ts.map +1 -1
- package/dist/utils/blog/blogDbUtils.js +71 -0
- package/dist/utils/blog/blogDbUtils.js.map +1 -1
- package/dist/utils/urlUtils.d.ts +10 -0
- package/dist/utils/urlUtils.d.ts.map +1 -1
- package/dist/utils/urlUtils.js +24 -5
- package/dist/utils/urlUtils.js.map +1 -1
- package/dist/utils/userUtils.d.ts +32 -0
- package/dist/utils/userUtils.d.ts.map +1 -1
- package/dist/utils/userUtils.js +27 -0
- package/dist/utils/userUtils.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +951 -845
- package/src/__tests__/agePolicy.test.ts +257 -0
- package/src/__tests__/getDisplayGender.test.ts +50 -0
- package/src/__tests__/statusEnums.test.ts +36 -0
- package/src/definitions.ts +138 -1
- package/src/extendedSchemas.ts +41 -2
- package/src/index.ts +1 -0
- package/src/legalTemplates.ts +1 -1
- package/src/utils/__tests__/badgeUtils.test.ts +191 -0
- package/src/utils/__tests__/bashPointsPaymentUtils.test.ts +153 -0
- package/src/utils/__tests__/contentFilterUtils.test.ts +547 -0
- package/src/utils/__tests__/displayDateUtils.test.ts +113 -0
- package/src/utils/__tests__/getBackendHost.test.ts +128 -0
- package/src/utils/__tests__/getFrontendHost.test.ts +232 -0
- package/src/utils/__tests__/promoCodesUtils.test.ts +237 -0
- package/src/utils/__tests__/qrCodeUtils.test.ts +143 -0
- package/src/utils/__tests__/stripeAccountUtils.test.ts +93 -0
- package/src/utils/blog/__tests__/getRelatedBlogPosts.test.ts +271 -0
- package/src/utils/blog/blogCommentDbUtils.ts +161 -0
- package/src/utils/blog/blogDbUtils.ts +95 -1
- package/src/utils/urlUtils.ts +27 -5
- package/src/utils/userUtils.ts +40 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getBackendHost — chooses the API origin (api.bash.community vs localhost:3500
|
|
3
|
+
* vs the bundled API_HOST for native Capacitor builds).
|
|
4
|
+
*
|
|
5
|
+
* This file pins the contract so a future "let's tidy up env handling" change
|
|
6
|
+
* doesn't accidentally re-introduce the same class of bug we just fixed in
|
|
7
|
+
* getFrontendHost (server-only env vars leaking into the browser code path).
|
|
8
|
+
*/
|
|
9
|
+
import { getBackendHost } from "../urlUtils.js";
|
|
10
|
+
const ORIGINAL_WINDOW = globalThis.window;
|
|
11
|
+
function setWindow(loc, capacitor) {
|
|
12
|
+
globalThis.window = {
|
|
13
|
+
location: loc,
|
|
14
|
+
...(capacitor ? { Capacitor: capacitor } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function clearWindow() {
|
|
18
|
+
if (ORIGINAL_WINDOW === undefined) {
|
|
19
|
+
delete globalThis.window;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
globalThis.window = ORIGINAL_WINDOW;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
clearWindow();
|
|
27
|
+
});
|
|
28
|
+
describe("getBackendHost — local dev", () => {
|
|
29
|
+
it("returns http://localhost:3500 for localhost (does NOT reuse :3000 from the page)", () => {
|
|
30
|
+
setWindow({
|
|
31
|
+
protocol: "http:",
|
|
32
|
+
hostname: "localhost",
|
|
33
|
+
host: "localhost:3000",
|
|
34
|
+
port: "3000",
|
|
35
|
+
});
|
|
36
|
+
expect(getBackendHost()).toBe("http://localhost:3500");
|
|
37
|
+
});
|
|
38
|
+
it("returns http://127.0.0.1:3500 for 127.0.0.1", () => {
|
|
39
|
+
setWindow({
|
|
40
|
+
protocol: "http:",
|
|
41
|
+
hostname: "127.0.0.1",
|
|
42
|
+
host: "127.0.0.1:3000",
|
|
43
|
+
port: "3000",
|
|
44
|
+
});
|
|
45
|
+
expect(getBackendHost()).toBe("http://127.0.0.1:3500");
|
|
46
|
+
});
|
|
47
|
+
it("uses the LAN IP for 192.168.x.x (so a phone on the same wifi can hit the dev API)", () => {
|
|
48
|
+
setWindow({
|
|
49
|
+
protocol: "http:",
|
|
50
|
+
hostname: "192.168.1.42",
|
|
51
|
+
host: "192.168.1.42:3000",
|
|
52
|
+
port: "3000",
|
|
53
|
+
});
|
|
54
|
+
expect(getBackendHost()).toBe("http://192.168.1.42:3500");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("getBackendHost — deployed web", () => {
|
|
58
|
+
// For deployed web, getBackendHost falls back to API_HOST (compiled in from
|
|
59
|
+
// process.env.REACT_APP_API at bundle time; defaults to http://localhost:3500
|
|
60
|
+
// when unset, which is what jest sees here). The contract we pin is "for any
|
|
61
|
+
// non-local hostname, do NOT derive the API URL from window.location" — that
|
|
62
|
+
// would silently route prod browsers at api.bash.community… instead of the
|
|
63
|
+
// configured API_HOST.
|
|
64
|
+
it("does not derive the backend host from window.location for deployed sites", () => {
|
|
65
|
+
setWindow({
|
|
66
|
+
protocol: "https:",
|
|
67
|
+
hostname: "bash.community",
|
|
68
|
+
host: "bash.community",
|
|
69
|
+
port: "",
|
|
70
|
+
});
|
|
71
|
+
const result = getBackendHost();
|
|
72
|
+
expect(result).not.toContain("bash.community:3500");
|
|
73
|
+
expect(result).not.toContain("bash.community:3000");
|
|
74
|
+
});
|
|
75
|
+
it("does not derive the backend host from a QA subdomain either", () => {
|
|
76
|
+
setWindow({
|
|
77
|
+
protocol: "https:",
|
|
78
|
+
hostname: "qa.bash.community",
|
|
79
|
+
host: "qa.bash.community",
|
|
80
|
+
port: "",
|
|
81
|
+
});
|
|
82
|
+
const result = getBackendHost();
|
|
83
|
+
expect(result).not.toContain("qa.bash.community:3500");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe("getBackendHost — Capacitor native (iOS / Android)", () => {
|
|
87
|
+
it("uses the bundled API_HOST when running inside a Capacitor WebView (hostname is 'localhost' but it's not really local dev)", () => {
|
|
88
|
+
// Capacitor loads from a localhost-like origin inside the WebView. If we
|
|
89
|
+
// routed to the localhost dev API based on hostname alone, the app would
|
|
90
|
+
// hit a port that doesn't exist on the device. The native check must win.
|
|
91
|
+
setWindow({
|
|
92
|
+
protocol: "capacitor:",
|
|
93
|
+
hostname: "localhost",
|
|
94
|
+
host: "localhost",
|
|
95
|
+
port: "",
|
|
96
|
+
}, { isNativePlatform: () => true });
|
|
97
|
+
const result = getBackendHost();
|
|
98
|
+
// We can't pin the exact value (depends on REACT_APP_API at build time),
|
|
99
|
+
// but we CAN pin the negative: it must not be the dev-server URL just
|
|
100
|
+
// because the WebView happens to say hostname=localhost.
|
|
101
|
+
expect(result).toBe(process.env.REACT_APP_API ?? "http://localhost:3500");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=getBackendHost.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getBackendHost.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/getBackendHost.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAWhD,MAAM,eAAe,GAAI,UAAmC,CAAC,MAAM,CAAC;AAEpE,SAAS,SAAS,CAAC,GAAuB,EAAE,SAAyB;IAEjE,UAGD,CAAC,MAAM,GAAG;QACT,QAAQ,EAAE,GAAG;QACb,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,OAAQ,UAAmC,CAAC,MAAM,CAAC;IACrD,CAAC;SAAM,CAAC;QACL,UAAmC,CAAC,MAAM,GAAG,eAAe,CAAC;IAChE,CAAC;AACH,CAAC;AAED,SAAS,CAAC,GAAG,EAAE;IACb,WAAW,EAAE,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,SAAS,CAAC;YACR,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,WAAW;YACrB,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QACH,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,SAAS,CAAC;YACR,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,WAAW;YACrB,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QACH,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QAC3F,SAAS,CAAC;YACR,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,cAAc;YACxB,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QACH,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,uBAAuB;IACvB,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,SAAS,CAAC;YACR,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,gBAAgB;YAC1B,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,EAAE;SACT,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,SAAS,CAAC;YACR,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,mBAAmB;YAC7B,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,EAAE;SACT,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mDAAmD,EAAE,GAAG,EAAE;IACjE,EAAE,CAAC,2HAA2H,EAAE,GAAG,EAAE;QACnI,yEAAyE;QACzE,yEAAyE;QACzE,0EAA0E;QAC1E,SAAS,CACP;YACE,QAAQ,EAAE,YAAY;YACtB,QAAQ,EAAE,WAAW;YACrB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,EAAE;SACT,EACD,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CACjC,CAAC;QACF,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;QAChC,yEAAyE;QACzE,sEAAsE;QACtE,yDAAyD;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,uBAAuB,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getFrontendHost.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/getFrontendHost.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getFrontendHost — the single source of truth for the URL prefix that goes
|
|
3
|
+
* into every shareable Bash link (SharePopup, embed snippets, LinkedIn
|
|
4
|
+
* Announce, Facebook event helper, QR codes…).
|
|
5
|
+
*
|
|
6
|
+
* THE BUG THIS FILE PINS
|
|
7
|
+
* ----------------------
|
|
8
|
+
* Pre-fix, getFrontendHost() called isProduction() which inspects
|
|
9
|
+
* process.env.ENVIRONMENT / BASH_ENV / NODE_ENV. Those env vars are server-
|
|
10
|
+
* only — Vite's envPlugin only inlines REACT_APP_* / VITE_* / NODE_ENV /
|
|
11
|
+
* PUBLIC_URL into the browser bundle. As a result, in the production browser
|
|
12
|
+
* `process.env.ENVIRONMENT` is undefined → isProduction() returns false →
|
|
13
|
+
* getFrontendHost() falls into the dev branch → appends ":3000" to whatever
|
|
14
|
+
* hostname the user is on. Real user-visible breakage:
|
|
15
|
+
*
|
|
16
|
+
* https://bash.community:3000/bash/cmo3...-empanada-fest-slc?ref=...
|
|
17
|
+
*
|
|
18
|
+
* Facebook / iMessage / LinkedIn cannot fetch a preview because :3000 isn't
|
|
19
|
+
* publicly reachable, and the link itself looks broken to recipients.
|
|
20
|
+
*
|
|
21
|
+
* THE CONTRACT WE PIN
|
|
22
|
+
* -------------------
|
|
23
|
+
* localhost / 127.0.0.1 / 192.168.x.x → preserve the dev port (3000 default)
|
|
24
|
+
* <anything else> → use window.location.host AS-IS
|
|
25
|
+
* (no port appending, no env-var dependency)
|
|
26
|
+
*
|
|
27
|
+
* Browser-hostname-driven, not env-var-driven, so the answer is correct
|
|
28
|
+
* regardless of how CI sets ENVIRONMENT / BASH_ENV.
|
|
29
|
+
*/
|
|
30
|
+
import { getFrontendHost } from "../urlUtils.js";
|
|
31
|
+
const ORIGINAL_WINDOW = globalThis.window;
|
|
32
|
+
function setLocation(loc) {
|
|
33
|
+
globalThis.window = {
|
|
34
|
+
location: loc,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function clearLocation() {
|
|
38
|
+
if (ORIGINAL_WINDOW === undefined) {
|
|
39
|
+
delete globalThis.window;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
globalThis.window = ORIGINAL_WINDOW;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
clearLocation();
|
|
47
|
+
});
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// PRODUCTION (the regression we're fixing)
|
|
50
|
+
// ============================================================================
|
|
51
|
+
describe("getFrontendHost — production (the share-link bug)", () => {
|
|
52
|
+
it("returns 'https://bash.community' for the production domain (NO :3000)", () => {
|
|
53
|
+
setLocation({
|
|
54
|
+
protocol: "https:",
|
|
55
|
+
hostname: "bash.community",
|
|
56
|
+
host: "bash.community",
|
|
57
|
+
port: "",
|
|
58
|
+
});
|
|
59
|
+
const host = getFrontendHost();
|
|
60
|
+
expect(host).toBe("https://bash.community");
|
|
61
|
+
expect(host).not.toMatch(/:3000/);
|
|
62
|
+
});
|
|
63
|
+
it("does NOT append :3000 to bash.community even if the bundle has no ENVIRONMENT env var", () => {
|
|
64
|
+
// Simulate the production browser bundle: ENVIRONMENT/BASH_ENV are NOT inlined.
|
|
65
|
+
const origEnv = process.env.ENVIRONMENT;
|
|
66
|
+
const origBashEnv = process.env.BASH_ENV;
|
|
67
|
+
const origNodeEnv = process.env.NODE_ENV;
|
|
68
|
+
try {
|
|
69
|
+
delete process.env.ENVIRONMENT;
|
|
70
|
+
delete process.env.BASH_ENV;
|
|
71
|
+
// Vite would replace process.env.NODE_ENV with "production" at build time.
|
|
72
|
+
process.env.NODE_ENV = "production";
|
|
73
|
+
setLocation({
|
|
74
|
+
protocol: "https:",
|
|
75
|
+
hostname: "bash.community",
|
|
76
|
+
host: "bash.community",
|
|
77
|
+
port: "",
|
|
78
|
+
});
|
|
79
|
+
expect(getFrontendHost()).toBe("https://bash.community");
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (origEnv === undefined) {
|
|
83
|
+
delete process.env.ENVIRONMENT;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
process.env.ENVIRONMENT = origEnv;
|
|
87
|
+
}
|
|
88
|
+
if (origBashEnv === undefined) {
|
|
89
|
+
delete process.env.BASH_ENV;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
process.env.BASH_ENV = origBashEnv;
|
|
93
|
+
}
|
|
94
|
+
if (origNodeEnv === undefined) {
|
|
95
|
+
delete process.env.NODE_ENV;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
process.env.NODE_ENV = origNodeEnv;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
it("works for the QA subdomain (qa.bash.community → no :3000)", () => {
|
|
103
|
+
setLocation({
|
|
104
|
+
protocol: "https:",
|
|
105
|
+
hostname: "qa.bash.community",
|
|
106
|
+
host: "qa.bash.community",
|
|
107
|
+
port: "",
|
|
108
|
+
});
|
|
109
|
+
expect(getFrontendHost()).toBe("https://qa.bash.community");
|
|
110
|
+
});
|
|
111
|
+
it("works for any vanity subdomain (org.bash.community → no :3000)", () => {
|
|
112
|
+
setLocation({
|
|
113
|
+
protocol: "https:",
|
|
114
|
+
hostname: "myorg.bash.community",
|
|
115
|
+
host: "myorg.bash.community",
|
|
116
|
+
port: "",
|
|
117
|
+
});
|
|
118
|
+
expect(getFrontendHost()).toBe("https://myorg.bash.community");
|
|
119
|
+
});
|
|
120
|
+
it("preserves an EXPLICIT non-default port if the deployed site is running on one", () => {
|
|
121
|
+
// (uncommon but legal — e.g. preview deploy on :8080)
|
|
122
|
+
setLocation({
|
|
123
|
+
protocol: "https:",
|
|
124
|
+
hostname: "preview.bash.community",
|
|
125
|
+
host: "preview.bash.community:8080",
|
|
126
|
+
port: "8080",
|
|
127
|
+
});
|
|
128
|
+
expect(getFrontendHost()).toBe("https://preview.bash.community:8080");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// LOCAL DEV (must keep working)
|
|
133
|
+
// ============================================================================
|
|
134
|
+
describe("getFrontendHost — local development", () => {
|
|
135
|
+
it("returns http://localhost:3000 for hostname=localhost", () => {
|
|
136
|
+
setLocation({
|
|
137
|
+
protocol: "http:",
|
|
138
|
+
hostname: "localhost",
|
|
139
|
+
host: "localhost:3000",
|
|
140
|
+
port: "3000",
|
|
141
|
+
});
|
|
142
|
+
expect(getFrontendHost()).toBe("http://localhost:3000");
|
|
143
|
+
});
|
|
144
|
+
it("returns http://127.0.0.1:3000 for hostname=127.0.0.1", () => {
|
|
145
|
+
setLocation({
|
|
146
|
+
protocol: "http:",
|
|
147
|
+
hostname: "127.0.0.1",
|
|
148
|
+
host: "127.0.0.1:3000",
|
|
149
|
+
port: "3000",
|
|
150
|
+
});
|
|
151
|
+
expect(getFrontendHost()).toBe("http://127.0.0.1:3000");
|
|
152
|
+
});
|
|
153
|
+
it("returns the LAN-IP dev URL with :3000 for 192.168.x.x (mobile-on-same-wifi dev)", () => {
|
|
154
|
+
setLocation({
|
|
155
|
+
protocol: "http:",
|
|
156
|
+
hostname: "192.168.1.42",
|
|
157
|
+
host: "192.168.1.42:3000",
|
|
158
|
+
port: "3000",
|
|
159
|
+
});
|
|
160
|
+
expect(getFrontendHost()).toBe("http://192.168.1.42:3000");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// NGROK / TUNNELS (deployed-equivalent — should NOT get :3000)
|
|
165
|
+
// ============================================================================
|
|
166
|
+
describe("getFrontendHost — ngrok / public tunnels", () => {
|
|
167
|
+
it("does NOT append :3000 to an ngrok-free.dev tunnel hostname", () => {
|
|
168
|
+
// ngrok forwards port 443 publicly to the local :3000; the link Facebook
|
|
169
|
+
// crawls must hit the public 443, never the local 3000.
|
|
170
|
+
setLocation({
|
|
171
|
+
protocol: "https:",
|
|
172
|
+
hostname: "abc-123.ngrok-free.dev",
|
|
173
|
+
host: "abc-123.ngrok-free.dev",
|
|
174
|
+
port: "",
|
|
175
|
+
});
|
|
176
|
+
expect(getFrontendHost()).toBe("https://abc-123.ngrok-free.dev");
|
|
177
|
+
});
|
|
178
|
+
it("does NOT append :3000 to a .lhr.life tunnel", () => {
|
|
179
|
+
setLocation({
|
|
180
|
+
protocol: "https:",
|
|
181
|
+
hostname: "myapp.lhr.life",
|
|
182
|
+
host: "myapp.lhr.life",
|
|
183
|
+
port: "",
|
|
184
|
+
});
|
|
185
|
+
expect(getFrontendHost()).toBe("https://myapp.lhr.life");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// SHARE-URL ASSEMBLY (the exact concatenation SharePopup performs)
|
|
190
|
+
// ============================================================================
|
|
191
|
+
describe("getFrontendHost — composed share URL (SharePopup integration)", () => {
|
|
192
|
+
it("composes the canonical share URL Facebook can crawl", () => {
|
|
193
|
+
setLocation({
|
|
194
|
+
protocol: "https:",
|
|
195
|
+
hostname: "bash.community",
|
|
196
|
+
host: "bash.community",
|
|
197
|
+
port: "",
|
|
198
|
+
});
|
|
199
|
+
const host = getFrontendHost();
|
|
200
|
+
const path = "/bash/evt_123-empanada-fest-slc";
|
|
201
|
+
const fullShareUrl = host + path;
|
|
202
|
+
expect(fullShareUrl).toBe("https://bash.community/bash/evt_123-empanada-fest-slc");
|
|
203
|
+
// Hard guard — this is the literal regression we're preventing.
|
|
204
|
+
expect(fullShareUrl).not.toMatch(/bash\.community:3000/);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
//# sourceMappingURL=getFrontendHost.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getFrontendHost.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/getFrontendHost.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promoCodesUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/promoCodesUtils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* promoCodesUtils — locate / aggregate / re-distribute promo codes across a
|
|
3
|
+
* BashEvent's ticket tiers.
|
|
4
|
+
*
|
|
5
|
+
* These three helpers are tiny but they sit on the path between checkout
|
|
6
|
+
* UI ↔ pricing engine. A bug in `findPromoCodeFromBashEvent` would cause a
|
|
7
|
+
* promo to silently fail to apply at checkout (lost revenue / angry user).
|
|
8
|
+
* A bug in `updatePromoCodesOnBashEventTicketTiers` would scramble which
|
|
9
|
+
* tier a promo code belongs to after edits in the host wizard.
|
|
10
|
+
*/
|
|
11
|
+
import { findPromoCodeFromBashEvent, getPromoCodesFromBashEvent, updatePromoCodesOnBashEventTicketTiers, } from "../promoCodesUtils.js";
|
|
12
|
+
function makePromo(id, ticketTierId, overrides = {}) {
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
ticketTierId,
|
|
16
|
+
code: `CODE-${id}`,
|
|
17
|
+
discountPercent: 10,
|
|
18
|
+
discountAmount: null,
|
|
19
|
+
maxRedemptions: null,
|
|
20
|
+
redemptionCount: 0,
|
|
21
|
+
expiresAt: null,
|
|
22
|
+
createdAt: new Date(0),
|
|
23
|
+
updatedAt: new Date(0),
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function makeTier(id, promoCodes) {
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
title: `Tier ${id}`,
|
|
31
|
+
promoCodes,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function makeBashEvent(tiers) {
|
|
35
|
+
return { ticketTiers: tiers };
|
|
36
|
+
}
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// findPromoCodeFromBashEvent
|
|
39
|
+
// ============================================================================
|
|
40
|
+
describe("findPromoCodeFromBashEvent", () => {
|
|
41
|
+
it("returns undefined when promoCodeId is undefined (no lookup attempted)", () => {
|
|
42
|
+
const bash = makeBashEvent([makeTier("t1", [makePromo("p1", "t1")])]);
|
|
43
|
+
expect(findPromoCodeFromBashEvent(bash, undefined)).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
it("returns undefined when promoCodeId is the empty string (falsy guard)", () => {
|
|
46
|
+
const bash = makeBashEvent([makeTier("t1", [makePromo("p1", "t1")])]);
|
|
47
|
+
expect(findPromoCodeFromBashEvent(bash, "")).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
it("returns undefined when bashEvent is null", () => {
|
|
50
|
+
expect(findPromoCodeFromBashEvent(null, "p1")).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
it("returns undefined when bashEvent is undefined", () => {
|
|
53
|
+
expect(findPromoCodeFromBashEvent(undefined, "p1")).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
it("returns undefined when ticketTiers is empty", () => {
|
|
56
|
+
expect(findPromoCodeFromBashEvent(makeBashEvent([]), "p1")).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
it("finds a promo on the FIRST tier", () => {
|
|
59
|
+
const promo = makePromo("p1", "t1");
|
|
60
|
+
const bash = makeBashEvent([
|
|
61
|
+
makeTier("t1", [promo]),
|
|
62
|
+
makeTier("t2", [makePromo("p2", "t2")]),
|
|
63
|
+
]);
|
|
64
|
+
expect(findPromoCodeFromBashEvent(bash, "p1")).toBe(promo);
|
|
65
|
+
});
|
|
66
|
+
it("finds a promo on a NON-FIRST tier (iterates across tiers)", () => {
|
|
67
|
+
const promo = makePromo("p2", "t2");
|
|
68
|
+
const bash = makeBashEvent([
|
|
69
|
+
makeTier("t1", [makePromo("p1", "t1")]),
|
|
70
|
+
makeTier("t2", [promo]),
|
|
71
|
+
]);
|
|
72
|
+
expect(findPromoCodeFromBashEvent(bash, "p2")).toBe(promo);
|
|
73
|
+
});
|
|
74
|
+
it("returns the FIRST match if the same id appears on multiple tiers (defensive — shouldn't happen but guard the order)", () => {
|
|
75
|
+
const dupOnT1 = makePromo("dup", "t1");
|
|
76
|
+
const dupOnT2 = makePromo("dup", "t2");
|
|
77
|
+
const bash = makeBashEvent([
|
|
78
|
+
makeTier("t1", [dupOnT1]),
|
|
79
|
+
makeTier("t2", [dupOnT2]),
|
|
80
|
+
]);
|
|
81
|
+
expect(findPromoCodeFromBashEvent(bash, "dup")).toBe(dupOnT1);
|
|
82
|
+
});
|
|
83
|
+
it("returns undefined when promoCodeId doesn't match any tier's promo", () => {
|
|
84
|
+
const bash = makeBashEvent([makeTier("t1", [makePromo("p1", "t1")])]);
|
|
85
|
+
expect(findPromoCodeFromBashEvent(bash, "missing")).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
it("does not throw when a tier has no promoCodes array (defensive)", () => {
|
|
88
|
+
const tier = { id: "t1", promoCodes: [] };
|
|
89
|
+
expect(() => findPromoCodeFromBashEvent(makeBashEvent([tier]), "p1")).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// getPromoCodesFromBashEvent
|
|
94
|
+
// ============================================================================
|
|
95
|
+
describe("getPromoCodesFromBashEvent", () => {
|
|
96
|
+
it("returns [] when bashEvent is null", () => {
|
|
97
|
+
expect(getPromoCodesFromBashEvent(null)).toEqual([]);
|
|
98
|
+
});
|
|
99
|
+
it("returns [] when bashEvent is undefined", () => {
|
|
100
|
+
expect(getPromoCodesFromBashEvent(undefined)).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
it("returns [] when there are no ticket tiers", () => {
|
|
103
|
+
expect(getPromoCodesFromBashEvent(makeBashEvent([]))).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
it("returns [] when no tier has any promo codes", () => {
|
|
106
|
+
const bash = makeBashEvent([makeTier("t1", []), makeTier("t2", [])]);
|
|
107
|
+
expect(getPromoCodesFromBashEvent(bash)).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
it("flattens promo codes across multiple tiers in tier order", () => {
|
|
110
|
+
const p1 = makePromo("p1", "t1");
|
|
111
|
+
const p2 = makePromo("p2", "t2");
|
|
112
|
+
const p3 = makePromo("p3", "t2");
|
|
113
|
+
const bash = makeBashEvent([
|
|
114
|
+
makeTier("t1", [p1]),
|
|
115
|
+
makeTier("t2", [p2, p3]),
|
|
116
|
+
]);
|
|
117
|
+
expect(getPromoCodesFromBashEvent(bash)).toEqual([p1, p2, p3]);
|
|
118
|
+
});
|
|
119
|
+
it("skips tiers with empty promoCodes (filter step)", () => {
|
|
120
|
+
const p1 = makePromo("p1", "t1");
|
|
121
|
+
const bash = makeBashEvent([
|
|
122
|
+
makeTier("t1", [p1]),
|
|
123
|
+
makeTier("t2", []),
|
|
124
|
+
makeTier("t3", [makePromo("p3", "t3")]),
|
|
125
|
+
]);
|
|
126
|
+
const all = getPromoCodesFromBashEvent(bash);
|
|
127
|
+
expect(all.map((p) => p.id)).toEqual(["p1", "p3"]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// updatePromoCodesOnBashEventTicketTiers
|
|
132
|
+
// ============================================================================
|
|
133
|
+
describe("updatePromoCodesOnBashEventTicketTiers", () => {
|
|
134
|
+
it("re-buckets promos onto tiers by ticketTierId (mutates in place)", () => {
|
|
135
|
+
const bash = makeBashEvent([
|
|
136
|
+
makeTier("t1", []),
|
|
137
|
+
makeTier("t2", []),
|
|
138
|
+
]);
|
|
139
|
+
const promos = [
|
|
140
|
+
makePromo("p1", "t1"),
|
|
141
|
+
makePromo("p2", "t2"),
|
|
142
|
+
makePromo("p3", "t1"),
|
|
143
|
+
];
|
|
144
|
+
updatePromoCodesOnBashEventTicketTiers(bash, promos);
|
|
145
|
+
expect(bash.ticketTiers?.[0].promoCodes.map((p) => p.id)).toEqual([
|
|
146
|
+
"p1",
|
|
147
|
+
"p3",
|
|
148
|
+
]);
|
|
149
|
+
expect(bash.ticketTiers?.[1].promoCodes.map((p) => p.id)).toEqual(["p2"]);
|
|
150
|
+
});
|
|
151
|
+
it("REPLACES existing promo codes on each tier (not appended)", () => {
|
|
152
|
+
const oldPromo = makePromo("old", "t1");
|
|
153
|
+
const bash = makeBashEvent([makeTier("t1", [oldPromo])]);
|
|
154
|
+
updatePromoCodesOnBashEventTicketTiers(bash, [makePromo("new", "t1")]);
|
|
155
|
+
expect(bash.ticketTiers?.[0].promoCodes.map((p) => p.id)).toEqual([
|
|
156
|
+
"new",
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
it("clears tiers that have no matching promos in the new list", () => {
|
|
160
|
+
const bash = makeBashEvent([
|
|
161
|
+
makeTier("t1", [makePromo("oldA", "t1")]),
|
|
162
|
+
makeTier("t2", [makePromo("oldB", "t2")]),
|
|
163
|
+
]);
|
|
164
|
+
updatePromoCodesOnBashEventTicketTiers(bash, [makePromo("newA", "t1")]);
|
|
165
|
+
expect(bash.ticketTiers?.[0].promoCodes.map((p) => p.id)).toEqual([
|
|
166
|
+
"newA",
|
|
167
|
+
]);
|
|
168
|
+
expect(bash.ticketTiers?.[1].promoCodes).toEqual([]);
|
|
169
|
+
});
|
|
170
|
+
it("ignores promos whose ticketTierId doesn't match any tier on the event (orphans dropped)", () => {
|
|
171
|
+
const bash = makeBashEvent([makeTier("t1", [])]);
|
|
172
|
+
updatePromoCodesOnBashEventTicketTiers(bash, [
|
|
173
|
+
makePromo("p1", "t1"),
|
|
174
|
+
makePromo("orphan", "tDoesNotExist"),
|
|
175
|
+
]);
|
|
176
|
+
expect(bash.ticketTiers?.[0].promoCodes.map((p) => p.id)).toEqual(["p1"]);
|
|
177
|
+
});
|
|
178
|
+
it("is a no-op when ticketTiers is undefined", () => {
|
|
179
|
+
const bash = { ticketTiers: undefined };
|
|
180
|
+
expect(() => updatePromoCodesOnBashEventTicketTiers(bash, [makePromo("p1", "t1")])).not.toThrow();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=promoCodesUtils.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promoCodesUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/promoCodesUtils.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qrCodeUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/qrCodeUtils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qrCodeUtils — URL builders for the *navigable* `/ticket-details` deep
|
|
3
|
+
* links that get encoded into ticket QR codes (and createQrCodeImage that
|
|
4
|
+
* renders them as PNG data URLs).
|
|
5
|
+
*
|
|
6
|
+
* The contract these tests pin (consumed by the door-scan flow, ticket
|
|
7
|
+
* emails, and tier-share QR codes):
|
|
8
|
+
*
|
|
9
|
+
* bashEventPrismaCheckoutQrCodeUrl(eventId, checkoutId)
|
|
10
|
+
* → "/ticket-details/{eventId}/{checkoutId}"
|
|
11
|
+
*
|
|
12
|
+
* bashEventQrCodeUrl(eventId) → "/ticket-details/{eventId}"
|
|
13
|
+
* bashEventQrCodeUrl(eventId, tierId) → "/ticket-details/{eventId}/ticket-tier/{tierId}"
|
|
14
|
+
* bashEventQrCodeUrl(eventId, tierId, ticketId) → "/ticket-details/{eventId}/ticket/{ticketId}" (ticketId WINS over tierId)
|
|
15
|
+
* bashEventQrCodeUrl(eventId, _, _, slug) → "/ticket-details/{eventId}-{slug}" (slug version of the deep link)
|
|
16
|
+
*
|
|
17
|
+
* createQrCodeImage(data) → Promise<"data:image/png;base64,...">
|
|
18
|
+
*
|
|
19
|
+
* Bugs in any of these route door-scanners to the wrong page (= entry chaos).
|
|
20
|
+
*/
|
|
21
|
+
import { bashEventPrismaCheckoutQrCodeUrl, bashEventQrCodeUrl, createQrCodeImage, } from "../qrCodeUtils.js";
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// bashEventPrismaCheckoutQrCodeUrl — checkout-id deep link
|
|
24
|
+
// ============================================================================
|
|
25
|
+
describe("bashEventPrismaCheckoutQrCodeUrl", () => {
|
|
26
|
+
it("composes /ticket-details/{eventId}/{checkoutId}", () => {
|
|
27
|
+
expect(bashEventPrismaCheckoutQrCodeUrl("evt_123", "co_456")).toBe("/ticket-details/evt_123/co_456");
|
|
28
|
+
});
|
|
29
|
+
it("does NOT throw on null/undefined inputs (downstream link is broken-but-safe)", () => {
|
|
30
|
+
// The current contract just stringifies via template literal, so we pin
|
|
31
|
+
// the literal output rather than asserting an exception.
|
|
32
|
+
expect(bashEventPrismaCheckoutQrCodeUrl(null, null)).toBe("/ticket-details/null/null");
|
|
33
|
+
expect(bashEventPrismaCheckoutQrCodeUrl(undefined, undefined)).toBe("/ticket-details/undefined/undefined");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// bashEventQrCodeUrl — base / tier / ticket / slug variants
|
|
38
|
+
// ============================================================================
|
|
39
|
+
describe("bashEventQrCodeUrl", () => {
|
|
40
|
+
describe("base url (no tier, no ticket, no slug)", () => {
|
|
41
|
+
it("returns /ticket-details/{eventId}", () => {
|
|
42
|
+
expect(bashEventQrCodeUrl("evt_1")).toBe("/ticket-details/evt_1");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("with ticketTierId", () => {
|
|
46
|
+
it("appends /ticket-tier/{tierId}", () => {
|
|
47
|
+
expect(bashEventQrCodeUrl("evt_1", "tier_a")).toBe("/ticket-details/evt_1/ticket-tier/tier_a");
|
|
48
|
+
});
|
|
49
|
+
it("ignores null ticketId in favor of tier", () => {
|
|
50
|
+
expect(bashEventQrCodeUrl("evt_1", "tier_a", null)).toBe("/ticket-details/evt_1/ticket-tier/tier_a");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("with ticketId (the door-scan link)", () => {
|
|
54
|
+
it("appends /ticket/{ticketId}", () => {
|
|
55
|
+
expect(bashEventQrCodeUrl("evt_1", undefined, "ticket_xyz")).toBe("/ticket-details/evt_1/ticket/ticket_xyz");
|
|
56
|
+
});
|
|
57
|
+
it("PREFERS ticketId over ticketTierId when both are provided", () => {
|
|
58
|
+
// Tickets are more specific than tiers; the deep link must resolve to the
|
|
59
|
+
// individual ticket (otherwise the door scanner can't validate redemption).
|
|
60
|
+
expect(bashEventQrCodeUrl("evt_1", "tier_a", "ticket_xyz")).toBe("/ticket-details/evt_1/ticket/ticket_xyz");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("with slug", () => {
|
|
64
|
+
it("uses the slugged base (`/ticket-details/{eventId}-{slug}`)", () => {
|
|
65
|
+
expect(bashEventQrCodeUrl("evt_1", undefined, undefined, "summer-rave")).toBe("/ticket-details/evt_1-summer-rave");
|
|
66
|
+
});
|
|
67
|
+
it("appends ticketId on the slugged base", () => {
|
|
68
|
+
expect(bashEventQrCodeUrl("evt_1", undefined, "ticket_xyz", "summer-rave")).toBe("/ticket-details/evt_1-summer-rave/ticket/ticket_xyz");
|
|
69
|
+
});
|
|
70
|
+
it("appends ticket-tier on the slugged base when no ticketId", () => {
|
|
71
|
+
expect(bashEventQrCodeUrl("evt_1", "tier_a", undefined, "summer-rave")).toBe("/ticket-details/evt_1-summer-rave/ticket-tier/tier_a");
|
|
72
|
+
});
|
|
73
|
+
it("falls back to non-slug base when slug is null", () => {
|
|
74
|
+
expect(bashEventQrCodeUrl("evt_1", null, null, null)).toBe("/ticket-details/evt_1");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// createQrCodeImage — PNG data URL
|
|
80
|
+
// ============================================================================
|
|
81
|
+
describe("createQrCodeImage", () => {
|
|
82
|
+
it("returns a base64-encoded PNG data URL", async () => {
|
|
83
|
+
const dataUrl = await createQrCodeImage("https://bash.app/ticket-details/evt_1/ticket/abc");
|
|
84
|
+
expect(dataUrl).toMatch(/^data:image\/png;base64,/);
|
|
85
|
+
// Sanity: a real QR image will have *meaningful* base64 length, not a few bytes.
|
|
86
|
+
expect(dataUrl.length).toBeGreaterThan(200);
|
|
87
|
+
});
|
|
88
|
+
it("produces a deterministic PNG for the same input (no nonce / timestamp leakage)", async () => {
|
|
89
|
+
const a = await createQrCodeImage("ticket://abc");
|
|
90
|
+
const b = await createQrCodeImage("ticket://abc");
|
|
91
|
+
expect(a).toBe(b);
|
|
92
|
+
});
|
|
93
|
+
it("produces DIFFERENT output for different payloads", async () => {
|
|
94
|
+
const a = await createQrCodeImage("ticket://abc");
|
|
95
|
+
const b = await createQrCodeImage("ticket://xyz");
|
|
96
|
+
expect(a).not.toBe(b);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=qrCodeUtils.test.js.map
|