@faststore/core 4.9.3-dev.0 → 4.9.3-dev.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/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +136 -129
- package/CHANGELOG.md +18 -0
- package/package.json +6 -6
- package/src/components/region/RegionModal/useRegion.ts +2 -0
- package/src/pages/api/graphql.ts +23 -19
- package/src/sdk/cart/index.ts +11 -9
- package/src/sdk/localization/useLocalizationConfig.tsx +4 -0
- package/src/sdk/session/initialSession.ts +8 -0
- package/src/sdk/session/syncSalesChannelFromOrderForm.ts +55 -2
- package/src/utils/trustedForwardedHost.ts +50 -0
- package/src/utils/utilities.ts +9 -5
- package/test/components/region/useRegion.browser.test.ts +1 -1
- package/test/components/region/useRegion.test.ts +67 -0
- package/test/sdk/session/getInitialSession.browser.test.ts +8 -0
- package/test/sdk/session/reconcileSessionLocale.browser.test.ts +16 -0
- package/test/sdk/session/syncSalesChannelFromOrderForm.test.ts +158 -1
- package/test/utils/filterChannel.test.ts +25 -0
- package/test/utils/trustedForwardedHost.test.ts +105 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.9.
|
|
2
|
+
> @faststore/core@4.9.3-dev.1 generate /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> pnpm run gen-types && pnpm run cache-graphql
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @faststore/core@4.9.
|
|
6
|
+
> @faststore/core@4.9.3-dev.1 gen-types /home/runner/work/faststore/faststore/packages/core
|
|
7
7
|
> node ../cli/bin/run generate-types .
|
|
8
8
|
|
|
9
9
|
[33m[STARTED][39m Parse Configuration
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[32m[COMPLETED][39m Generate to @generated/
|
|
20
20
|
[32m[COMPLETED][39m Generate outputs
|
|
21
21
|
|
|
22
|
-
> @faststore/core@4.9.
|
|
22
|
+
> @faststore/core@4.9.3-dev.1 cache-graphql /home/runner/work/faststore/faststore/packages/core
|
|
23
23
|
> node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
|
|
24
24
|
|
|
25
25
|
[Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.9.
|
|
2
|
+
> @faststore/core@4.9.3-dev.1 test /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
@@ -7,23 +7,27 @@
|
|
|
7
7
|
|
|
8
8
|
Not implemented: navigation to another Document
|
|
9
9
|
Not implemented: navigation to another Document
|
|
10
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/ui/useScrollRestoration.browser.test.ts [2m([22m[2m43 tests[22m[2m)[22m[33m 1025[2mms[22m[39m
|
|
11
10
|
Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
|
|
12
11
|
npx update-browserslist-db@latest
|
|
13
12
|
Why you should do it regularly: https://github.com/browserslist/update-db#readme
|
|
14
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
15
|
-
|
|
16
|
-
[33m[2m✓[22m[39m
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
[33m[2m✓[22m[39m
|
|
21
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/
|
|
22
|
-
[33m[2m✓[22m[39m
|
|
23
|
-
|
|
24
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
25
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
26
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
13
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/ui/useScrollRestoration.browser.test.ts [2m([22m[2m43 tests[22m[2m)[22m[33m 1011[2mms[22m[39m
|
|
14
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/ContractSwitcher.browser.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[33m 2790[2mms[22m[39m
|
|
15
|
+
[33m[2m✓[22m[39m shows an error state when the list fails to load [33m 760[2mms[22m[39m
|
|
16
|
+
[33m[2m✓[22m[39m requires selecting a contract before Confirm is enabled, then switches (REQ-03) [33m 308[2mms[22m[39m
|
|
17
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/cards/MyAccountListCards.browser.test.tsx [2m([22m[2m16 tests[22m[2m)[22m[33m 3127[2mms[22m[39m
|
|
18
|
+
[33m[2m✓[22m[39m shows the tab bar and renders Personal cards by default [33m 1504[2mms[22m[39m
|
|
19
|
+
[33m[2m✓[22m[39m paginates when there are more than 25 cards [33m 504[2mms[22m[39m
|
|
20
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/BuyingPolicyAlert.browser.test.tsx [2m([22m[2m13 tests[22m[2m)[22m[33m 2487[2mms[22m[39m
|
|
21
|
+
[33m[2m✓[22m[39m opens the reject confirmation modal with the default English copy [33m 805[2mms[22m[39m
|
|
22
|
+
[33m[2m✓[22m[39m opens the reject confirmation modal with CMS-provided labels [33m 387[2mms[22m[39m
|
|
23
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx [2m([22m[2m13 tests[22m[2m)[22m[33m 457[2mms[22m[39m
|
|
24
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
|
25
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawer.browser.test.tsx [2m([22m[2m10 tests[22m[2m)[22m[33m 1931[2mms[22m[39m
|
|
26
|
+
[33m[2m✓[22m[39m renders the menu view with contract details and manage footer [33m 1038[2mms[22m[39m
|
|
27
|
+
[33m[2m✓[22m[39m opens the contract switcher and returns to the menu on back [33m 359[2mms[22m[39m
|
|
28
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/useBindingSelector.browser.test.tsx [2m([22m[2m9 tests[22m[2m)[22m[32m 121[2mms[22m[39m
|
|
29
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 83[2mms[22m[39m
|
|
30
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/recommendations/useRecommendationShelf.browser.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[32m 92[2mms[22m[39m
|
|
27
31
|
[90mstderr[2m | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
|
|
28
32
|
[22m[39mError in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
|
|
29
33
|
[90m at node:internal/deps/undici/undici:16416:13[39m
|
|
@@ -53,12 +57,10 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
|
|
|
53
57
|
[90mstderr[2m | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx[2m > [22m[2mRecommendationShelf[2m > [22m[2mrenders nothing when there is an error
|
|
54
58
|
[22m[39mError fetching recommendations [90mundefined[39m boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
|
|
55
59
|
|
|
56
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
57
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/
|
|
58
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/CartSidebar/CartSidebar.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
59
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrderDetails.browser.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
60
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/navigation/Navbar.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 51[2mms[22m[39m
|
|
61
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/getInitialSession.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
60
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 119[2mms[22m[39m
|
|
61
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/getInitialSession.browser.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
62
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/CartSidebar/CartSidebar.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 83[2mms[22m[39m
|
|
63
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrderDetails.browser.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 69[2mms[22m[39m
|
|
62
64
|
[90mstderr[2m | test/pages/password-protection.browser.test.tsx[2m > [22m[2mPasswordProtectionLogin[2m > [22m[2mposts the password and redirects to the requested return path
|
|
63
65
|
[22m[39mWarning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
|
|
64
66
|
|
|
@@ -144,6 +146,8 @@ act(() => {
|
|
|
144
146
|
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
|
|
145
147
|
at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
|
|
146
148
|
|
|
149
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/navigation/Navbar.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 100[2mms[22m[39m
|
|
150
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerFooter.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[33m 508[2mms[22m[39m
|
|
147
151
|
[90mstderr[2m | test/pages/password-protection.browser.test.tsx[2m > [22m[2mPasswordProtectionLogin[2m > [22m[2muses the fallback invalid-password message when API omits an error
|
|
148
152
|
[22m[39mWarning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
|
|
149
153
|
|
|
@@ -288,7 +292,8 @@ act(() => {
|
|
|
288
292
|
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
|
|
289
293
|
at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
|
|
290
294
|
|
|
291
|
-
[32m✓[39m [30m[46m browser [49m[39m test/pages/password-protection.browser.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[33m
|
|
295
|
+
[32m✓[39m [30m[46m browser [49m[39m test/pages/password-protection.browser.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[33m 983[2mms[22m[39m
|
|
296
|
+
[33m[2m✓[22m[39m renders the password protection form [33m 410[2mms[22m[39m
|
|
292
297
|
[90mstderr[2m | test/components/cms/RenderSections.browser.test.tsx
|
|
293
298
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
294
299
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -297,10 +302,9 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
297
302
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
298
303
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
299
304
|
|
|
300
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
301
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
302
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/cms/RenderSections.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
303
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/navigation/NavbarSlider.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 37[2mms[22m[39m
|
|
305
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useSwitchContract.browser.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 56[2mms[22m[39m
|
|
306
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/navigation/NavbarSlider.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 42[2mms[22m[39m
|
|
307
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/cms/RenderSections.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 92[2mms[22m[39m
|
|
304
308
|
[90mstderr[2m | test/sdk/localization/store-url.browser.test.ts
|
|
305
309
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
306
310
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -309,15 +313,15 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
309
313
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
310
314
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
311
315
|
|
|
312
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
313
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
314
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useAvailableContracts.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
315
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/templates/SearchPage/SearchWrapper.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
316
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
317
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountListQuotes.browser.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
318
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/navigation/NavbarAccountArea.browser.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
319
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
320
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
316
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/store-url.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
317
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/Menu.browser.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[33m 385[2mms[22m[39m
|
|
318
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useAvailableContracts.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 42[2mms[22m[39m
|
|
319
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/templates/SearchPage/SearchWrapper.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 35[2mms[22m[39m
|
|
320
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 128[2mms[22m[39m
|
|
321
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountListQuotes.browser.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 135[2mms[22m[39m
|
|
322
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/navigation/NavbarAccountArea.browser.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 78[2mms[22m[39m
|
|
323
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/changeContractToken.browser.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
|
324
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 54[2mms[22m[39m
|
|
321
325
|
[90mstderr[2m | test/components/account/doLogout.browser.test.ts
|
|
322
326
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
323
327
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -326,9 +330,9 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
326
330
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
327
331
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
328
332
|
|
|
329
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/doLogout.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
330
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/
|
|
331
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/
|
|
333
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/doLogout.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 39[2mms[22m[39m
|
|
334
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/ui/LocalizationButton.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 48[2mms[22m[39m
|
|
335
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerHeader.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[33m 359[2mms[22m[39m
|
|
332
336
|
[90mstderr[2m | test/sdk/account/clearPersistedSessionState.browser.test.ts
|
|
333
337
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
334
338
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -337,14 +341,14 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
337
341
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
338
342
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
339
343
|
|
|
340
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/clearPersistedSessionState.browser.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
341
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrderDelivery.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
342
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
343
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrderStatus.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
344
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/
|
|
345
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/
|
|
346
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrdersList.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
347
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/SummaryCard.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
344
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/clearPersistedSessionState.browser.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 19[2mms[22m[39m
|
|
345
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrderDelivery.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 102[2mms[22m[39m
|
|
346
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 104[2mms[22m[39m
|
|
347
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrderStatus.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 144[2mms[22m[39m
|
|
348
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/region/useRegion.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
349
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountListCards.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 92[2mms[22m[39m
|
|
350
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountOrdersList.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 30[2mms[22m[39m
|
|
351
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/SummaryCard.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 105[2mms[22m[39m
|
|
348
352
|
[90mstderr[2m | test/sdk/session/sessionStore.browser.test.ts
|
|
349
353
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
350
354
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -353,9 +357,9 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
353
357
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
354
358
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
355
359
|
|
|
356
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/sessionStore.browser.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
357
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationSignInButton.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
358
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
360
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/sessionStore.browser.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
361
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationSignInButton.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 270[2mms[22m[39m
|
|
362
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 31[2mms[22m[39m
|
|
359
363
|
[90mstderr[2m | test/sdk/session/validateSession.browser.test.ts
|
|
360
364
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
361
365
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -364,14 +368,14 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
364
368
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
365
369
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
366
370
|
|
|
367
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/validateSession.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
368
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/StatusCard.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
369
|
-
[32m✓[39m [30m[46m browser [49m[39m test/utils/isLocalHost.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
371
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/validateSession.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
372
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/StatusCard.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 107[2mms[22m[39m
|
|
373
|
+
[32m✓[39m [30m[46m browser [49m[39m test/utils/isLocalHost.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
370
374
|
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
371
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryCard.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
372
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
373
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/skeletons/ProductGridSkeleton.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
374
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/bindingPaths.test.ts [2m([22m[2m71 tests[22m[2m)[22m[32m
|
|
375
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryCard.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 63[2mms[22m[39m
|
|
376
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 51[2mms[22m[39m
|
|
377
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/skeletons/ProductGridSkeleton.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 45[2mms[22m[39m
|
|
378
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/bindingPaths.test.ts [2m([22m[2m71 tests[22m[2m)[22m[32m 31[2mms[22m[39m
|
|
375
379
|
[90mstderr[2m | test/sdk/account/refreshToken.browser.test.ts
|
|
376
380
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
377
381
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -380,13 +384,14 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
380
384
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
|
|
381
385
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
382
386
|
|
|
383
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/refreshToken.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
384
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/password-protection-service.test.ts [2m([22m[2m22 tests[22m[2m)[22m[32m
|
|
385
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/switch-contract.test.ts [2m([22m[2m25 tests[22m[2m)[22m[32m
|
|
386
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
387
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/match-url.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
388
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[32m
|
|
389
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/unlock.test.ts [2m([22m[2m15 tests[22m[2m)[22m[32m
|
|
387
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/refreshToken.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
388
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/password-protection-service.test.ts [2m([22m[2m22 tests[22m[2m)[22m[32m 37[2mms[22m[39m
|
|
389
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/switch-contract.test.ts [2m([22m[2m25 tests[22m[2m)[22m[32m 92[2mms[22m[39m
|
|
390
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 230[2mms[22m[39m
|
|
391
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/match-url.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
392
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
393
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/unlock.test.ts [2m([22m[2m15 tests[22m[2m)[22m[32m 141[2mms[22m[39m
|
|
394
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/syncSalesChannelFromOrderForm.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
390
395
|
[90mstderr[2m | test/components/search/SearchInputComponent.test.tsx
|
|
391
396
|
[22m[39mWarning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
|
|
392
397
|
|
|
@@ -450,24 +455,24 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
|
|
|
450
455
|
at Suspense
|
|
451
456
|
at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
|
|
452
457
|
|
|
453
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m
|
|
454
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInputComponent.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
455
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotesTable.test.tsx [2m([22m[2m15 tests[22m[2m)[22m[33m
|
|
456
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/search/useSearchHistory.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m
|
|
458
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
459
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInputComponent.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 94[2mms[22m[39m
|
|
460
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotesTable.test.tsx [2m([22m[2m15 tests[22m[2m)[22m[33m 474[2mms[22m[39m
|
|
461
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/search/useSearchHistory.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m 48[2mms[22m[39m
|
|
457
462
|
[90mstderr[2m | test/pages/pvt/account/cards/index.test.ts[2m > [22m[2mCards page getServerSideProps[2m > [22m[2mredirects to /pvt/account/403 when the query fails with 401/403
|
|
458
463
|
[22m[39m{ extensions: { status: [33m401[39m } }
|
|
459
464
|
|
|
460
465
|
[90mstderr[2m | test/pages/pvt/account/cards/index.test.ts[2m > [22m[2mCards page getServerSideProps[2m > [22m[2mrenders the error state (no redirect) when the query fails with a non-auth status
|
|
461
466
|
[22m[39m{ extensions: { status: [33m502[39m } }
|
|
462
467
|
|
|
463
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/pvt/account/cards/index.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
464
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotes.test.tsx [2m([22m[2m11 tests[22m[2m)[22m[33m
|
|
468
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/pvt/account/cards/index.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 11[2mms[22m[39m
|
|
469
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotes.test.tsx [2m([22m[2m11 tests[22m[2m)[22m[33m 832[2mms[22m[39m
|
|
465
470
|
[33m[2m✓[22m[39m navigates with the search term on blur [33m 358[2mms[22m[39m
|
|
466
|
-
[33m[2m✓[22m[39m navigates without a label param when the search is cleared [33m
|
|
467
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/quoteFilters.test.ts [2m([22m[2m26 tests[22m[2m)[22m[32m
|
|
468
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
469
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
470
|
-
[32m✓[39m [30m[43m node [49m[39m test/experimental/myAccountCmsServerSideProps.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
471
|
+
[33m[2m✓[22m[39m navigates without a label param when the search is cleared [33m 360[2mms[22m[39m
|
|
472
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/quoteFilters.test.ts [2m([22m[2m26 tests[22m[2m)[22m[32m 29[2mms[22m[39m
|
|
473
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/productJsonLd.test.ts [2m([22m[2m36 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
|
474
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
475
|
+
[32m✓[39m [30m[43m node [49m[39m test/experimental/myAccountCmsServerSideProps.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
|
471
476
|
[90mstderr[2m | test/pages/api/graphql.test.ts[2m > [22m[2m/api/graphql error status propagation[2m > [22m[2mpropagates the upstream 400 from a wrapped BadRequestError instead of 500
|
|
472
477
|
[22m[39mGraphql execution returned with error: [
|
|
473
478
|
{
|
|
@@ -535,7 +540,7 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
|
|
|
535
540
|
|
|
536
541
|
[90mstderr[2m | test/pages/api/graphql.test.ts[2m > [22m[2m/api/graphql request handling[2m > [22m[2mmaps UnauthorizedError thrown by execute to its status
|
|
537
542
|
[22m[39mSomething unexpected occurred querying Graphql endpoint:
|
|
538
|
-
|
|
543
|
+
ce [FastStoreError]: expired
|
|
539
544
|
at [90m/home/runner/work/faststore/faststore/packages/core/[39mtest/pages/api/graphql.test.ts:202:33
|
|
540
545
|
at [90m/home/runner/work/faststore/faststore/packages/core/[39mtest/pages/api/graphql.test.ts:207:39
|
|
541
546
|
at file:///home/runner/work/faststore/faststore/node_modules/[4m.pnpm[24m/@vitest+runner@4.0.18/node_modules/[4m@vitest/runner[24m/dist/index.js:1024:60
|
|
@@ -577,71 +582,73 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
|
|
|
577
582
|
at trace (file:///home/runner/work/faststore/faststore/node_modules/[4m.pnpm[24m/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@20.19.13_jiti@2.6.1_jsdom@27.4.0_sas_13be941c57d55f08f06eeaf1c180ddfe/node_modules/[4mvitest[24m/dist/chunks/test.B8ej_ZHS.js:239:21)
|
|
578
583
|
at runTest (file:///home/runner/work/faststore/faststore/node_modules/[4m.pnpm[24m/@vitest+runner@4.0.18/node_modules/[4m@vitest/runner[24m/dist/index.js:1653:12)
|
|
579
584
|
|
|
580
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/graphql.test.ts [2m([22m[2m19 tests[22m[2m)[22m[32m
|
|
581
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/clearCookies.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m
|
|
582
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/multipleTemplates.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
585
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/graphql.test.ts [2m([22m[2m19 tests[22m[2m)[22m[32m 31[2mms[22m[39m
|
|
586
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/clearCookies.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 28[2mms[22m[39m
|
|
587
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/multipleTemplates.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
583
588
|
[90mstderr[2m | test/sdk/account/useSetPassword.test.ts[2m > [22m[2museSetPassword[2m > [22m[2mmaps wrongcredentials error responses to the existing message
|
|
584
589
|
[22m[39mSet password request failed: Bad Request
|
|
585
590
|
|
|
586
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/useSetPassword.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
587
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryOperation.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
588
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/localizeRedirectDestination.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m
|
|
589
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesFilterSlider.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
590
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesStatusSelector.test.tsx [2m([22m[2m14 tests[22m[2m)[22m[32m
|
|
591
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/hreflang.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
592
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getMyAccountRoutes.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m
|
|
593
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesSelectedTags.test.tsx [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
594
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/redirects/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m
|
|
595
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryUpload.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
596
|
-
[32m✓[39m [30m[43m node [49m[39m test/proxy.localization.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
591
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/useSetPassword.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 31[2mms[22m[39m
|
|
592
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryOperation.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
|
593
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/localizeRedirectDestination.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
594
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesFilterSlider.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 226[2mms[22m[39m
|
|
595
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesStatusSelector.test.tsx [2m([22m[2m14 tests[22m[2m)[22m[32m 190[2mms[22m[39m
|
|
596
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/hreflang.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
597
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getMyAccountRoutes.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
598
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesSelectedTags.test.tsx [2m([22m[2m10 tests[22m[2m)[22m[32m 124[2mms[22m[39m
|
|
599
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/redirects/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 51[2mms[22m[39m
|
|
600
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryUpload.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 58[2mms[22m[39m
|
|
601
|
+
[32m✓[39m [30m[43m node [49m[39m test/proxy.localization.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 35[2mms[22m[39m
|
|
602
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntry.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 49[2mms[22m[39m
|
|
597
603
|
[32m✓[39m [30m[43m node [49m[39m test/components/account/orders/getDeliveryOptionLabel.test.ts [2m([22m[2m21 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
598
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/
|
|
599
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
600
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
601
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
602
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
603
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
604
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
605
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
606
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/useStartRecommendationSession.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
607
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/account/orders/StatusCard.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
608
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/
|
|
609
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/
|
|
610
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/
|
|
611
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/refreshToken.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
604
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/graphql/retryPolicy.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 23[2mms[22m[39m
|
|
605
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/myAccountDefaultSections.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
606
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/productJsonLdRendering.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
607
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/trustedForwardedHost.test.ts [2m([22m[2m27 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
608
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[33m 746[2mms[22m[39m
|
|
609
|
+
[32m✓[39m [30m[43m node [49m[39m test/proxy.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 23[2mms[22m[39m
|
|
610
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/webops-api.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
|
611
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/store-url.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
612
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/useStartRecommendationSession.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 77[2mms[22m[39m
|
|
613
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/orders/StatusCard.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 32[2mms[22m[39m
|
|
614
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderFormItems.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 41[2mms[22m[39m
|
|
615
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/cart/waitForSessionValidated.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
616
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/refreshToken.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
612
617
|
[32m✓[39m [30m[43m node [49m[39m test/server/content/service.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
613
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
614
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/auth/useAuth.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
618
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
619
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/auth/useAuth.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 44[2mms[22m[39m
|
|
615
620
|
[32m✓[39m [30m[43m node [49m[39m test/utils/getRequestHostname.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
616
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/account/Layout.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
617
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/rootSpan.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
618
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
619
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
620
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
621
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/vrn.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
621
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/Layout.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 77[2mms[22m[39m
|
|
622
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/rootSpan.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 48[2mms[22m[39m
|
|
623
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
624
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInput.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
625
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/validateSessionRefreshToken.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
626
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/vrn.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
627
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/region/useRegion.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
622
628
|
[32m✓[39m [30m[43m node [49m[39m test/server/cms/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
623
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/retry.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
624
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getB2BSessionClaims.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
625
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
626
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
627
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/account/OrganizationDrawerBody.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 33[2mms[22m[39m
|
|
629
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/retry.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
630
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getB2BSessionClaims.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
631
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/useRecommendationUserId.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 194[2mms[22m[39m
|
|
632
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/userOrderStatus.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
628
633
|
[32m✓[39m [30m[43m node [49m[39m test/utils/unlockResponse.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
629
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/useRecommendations.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
630
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
631
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
632
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/toSessionInput.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
634
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/useRecommendations.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
635
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/OrganizationDrawerBody.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
|
636
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/isLocalHost.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
637
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/toSessionInput.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
638
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuoteStatusBadge.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 49[2mms[22m[39m
|
|
633
639
|
[32m✓[39m [30m[43m node [49m[39m test/components/account/orders/getOrderTotalLabel.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
634
640
|
[32m✓[39m [30m[43m node [49m[39m test/components/account/orders/listOrdersLabels.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
635
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/getCookie.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
636
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/isSignInAreaResolved.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
637
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
638
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/auth/ProfileChallenge.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
641
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/getCookie.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
642
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/isSignInAreaResolved.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
643
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/filterChannel.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
644
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/auth/ProfileChallenge.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
|
639
645
|
[32m✓[39m [30m[43m node [49m[39m test/instrumentation.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
640
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
646
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/utils.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
647
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/orders/orderDetailsLabels.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
641
648
|
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/clearPersistedSessionState.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
642
649
|
|
|
643
|
-
[2m Test Files [22m [1m[
|
|
644
|
-
[2m Tests [22m [1m[
|
|
645
|
-
[2m Start at [22m
|
|
646
|
-
[2m Duration [22m
|
|
650
|
+
[2m Test Files [22m [1m[32m130 passed[39m[22m[90m (130)[39m
|
|
651
|
+
[2m Tests [22m [1m[32m1032 passed[39m[22m[90m (1032)[39m
|
|
652
|
+
[2m Start at [22m 00:35:08
|
|
653
|
+
[2m Duration [22m 62.92s[2m (transform 13.32s, setup 0ms, import 56.50s, tests 21.42s, environment 78.13s)[22m
|
|
647
654
|
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [4.9.3-dev.2](https://github.com/vtex/faststore/compare/v4.9.3-dev.1...v4.9.3-dev.2) (2026-10-07)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- address security & functional concerns from dk review ([d0b6106](https://github.com/vtex/faststore/commit/d0b610688ec551d8105c74750927be4ab1af7224))
|
|
11
|
+
- **core:** harden x-forwarded-host handling and drop orderNumber fallback ([c1faa35](https://github.com/vtex/faststore/commit/c1faa35fe070ff8b51d4f07a5f1f33da4b17d836)), closes [#3178](https://github.com/vtex/faststore/issues/3178)
|
|
12
|
+
- **core:** ignore multi-value x-forwarded-host and cover port and case ([b81d5d9](https://github.com/vtex/faststore/commit/b81d5d91721f9f8205251ad39e607b094386083c))
|
|
13
|
+
- **core:** log ignored x-forwarded-host and cover CR/LF hostnames ([adaa816](https://github.com/vtex/faststore/commit/adaa81608b7e72759272e1d6d23ca6257b9a4d59))
|
|
14
|
+
- **core:** make preview cookies host-only instead of rewriting their domain ([8e4901e](https://github.com/vtex/faststore/commit/8e4901ec7d127336bc27c0ae8e52c9cadea1a5a3))
|
|
15
|
+
- **core:** reject hostnames that would inject Set-Cookie attributes ([059a437](https://github.com/vtex/faststore/commit/059a4377c3b1b8f66aeed2b73ce130e1ab827f77))
|
|
16
|
+
- resolve ValidateCartMutation infinite loop in preview environments (SFS-3034) ([1cc9a5e](https://github.com/vtex/faststore/commit/1cc9a5efb1617f3d032623c4ee5f687ad5e2ccd9)), closes [#3178](https://github.com/vtex/faststore/issues/3178) [#3178](https://github.com/vtex/faststore/issues/3178) [#3190](https://github.com/vtex/faststore/issues/3190) [#3188](https://github.com/vtex/faststore/issues/3188) [#1467584](https://github.com/vtex/faststore/issues/1467584)
|
|
17
|
+
|
|
18
|
+
## [4.9.3-dev.1](https://github.com/vtex/faststore/compare/v4.9.3-dev.0...v4.9.3-dev.1) (2026-10-06)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- keep empty carts from moving the session sales channel ([#3509](https://github.com/vtex/faststore/issues/3509)) ([600fb7d](https://github.com/vtex/faststore/commit/600fb7d9403553c12bd01d6b3955f18c48f91054)), closes [#3435](https://github.com/vtex/faststore/issues/3435) [#3435](https://github.com/vtex/faststore/issues/3435)
|
|
23
|
+
|
|
6
24
|
## [4.9.3-dev.0](https://github.com/vtex/faststore/compare/v4.9.1-dev.0...v4.9.3-dev.0) (2026-10-05)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @faststore/core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "4.9.3-dev.
|
|
3
|
+
"version": "4.9.3-dev.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"style-loader": "^3.3.1",
|
|
72
72
|
"swr": "^2.2.5",
|
|
73
73
|
"use-sync-external-store": "^1.6.0",
|
|
74
|
-
"@faststore/api": "4.9.3-dev.
|
|
75
|
-
"@faststore/
|
|
76
|
-
"@faststore/
|
|
77
|
-
"@faststore/
|
|
78
|
-
"@faststore/
|
|
74
|
+
"@faststore/api": "4.9.3-dev.2",
|
|
75
|
+
"@faststore/diagnostics": "4.9.3-dev.2",
|
|
76
|
+
"@faststore/lighthouse": "4.9.3-dev.2",
|
|
77
|
+
"@faststore/sdk": "4.9.3-dev.2",
|
|
78
|
+
"@faststore/ui": "4.9.3-dev.2"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@cypress/code-coverage": "^3.12.1",
|
|
@@ -41,8 +41,10 @@ export default function useRegion(): UseRegionValues {
|
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
43
|
// `session` usually comes from `useSession()`, which also carries UI state
|
|
44
|
+
// and filters session-only channel keys (sales channel markers).
|
|
44
45
|
const newSession = {
|
|
45
46
|
...toSessionInput(session),
|
|
47
|
+
channel: sessionStore.read()?.channel ?? session.channel,
|
|
46
48
|
postalCode,
|
|
47
49
|
geoCoordinates: null, // Revalidate geo coordinates in API when users set a new postal code
|
|
48
50
|
} as Session
|
package/src/pages/api/graphql.ts
CHANGED
|
@@ -11,6 +11,11 @@ import discoveryConfig from 'discovery.config'
|
|
|
11
11
|
import { getJWTAutCookie } from 'src/utils/getCookie'
|
|
12
12
|
import { getRequestHostname } from 'src/utils/getRequestHostname'
|
|
13
13
|
import { isLocalHost } from 'src/utils/isLocalHost'
|
|
14
|
+
import {
|
|
15
|
+
isHostAllowed,
|
|
16
|
+
removeCookieDomain,
|
|
17
|
+
singleForwardedHost,
|
|
18
|
+
} from 'src/utils/trustedForwardedHost'
|
|
14
19
|
import { shouldForceRefreshTokenForValidateSession } from 'src/utils/validateSessionRefreshToken'
|
|
15
20
|
import { execute } from '../../server'
|
|
16
21
|
import { logger } from '@faststore/diagnostics'
|
|
@@ -56,22 +61,10 @@ const shouldReplaceCookieDomain = ({
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
allowList,
|
|
64
|
-
}: {
|
|
65
|
-
host: string
|
|
66
|
-
allowList: string[]
|
|
67
|
-
}) => {
|
|
68
|
-
const normalizedHost = host.toLowerCase()
|
|
69
|
-
|
|
70
|
-
return allowList.some((suffix) => normalizedHost.endsWith(suffix))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Ensure the cookie domain matches the current host so the browser can store it.
|
|
64
|
+
* On allowlisted hosts (previews, localhost) the upstream cookie domain does not
|
|
65
|
+
* match the browser host, so the Domain attribute is dropped and the cookie
|
|
66
|
+
* becomes host-only. The host is never written into the cookie, so a forged
|
|
67
|
+
* x-forwarded-host cannot choose the cookie scope.
|
|
75
68
|
*/
|
|
76
69
|
const normalizeSetCookieDomain = ({
|
|
77
70
|
request,
|
|
@@ -85,20 +78,31 @@ const normalizeSetCookieDomain = ({
|
|
|
85
78
|
return setCookie
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
|
|
81
|
+
// The preview ingress forwards a client-supplied x-forwarded-host as is, so
|
|
82
|
+
// it only decides whether the Domain is dropped, never which domain is used.
|
|
83
|
+
const forwardedHeader = request.headers['x-forwarded-host']
|
|
84
|
+
const forwardedHost = getRequestHostname(singleForwardedHost(forwardedHeader))
|
|
85
|
+
if (forwardedHeader && !forwardedHost) {
|
|
86
|
+
// The value is client-controllable, so it is not logged.
|
|
87
|
+
OTELLogger('warn', 'Ignoring multi-value or malformed x-forwarded-host')
|
|
88
|
+
}
|
|
89
|
+
const host =
|
|
90
|
+
forwardedHost && isHostAllowed(forwardedHost, ALLOWED_HOST_SUFFIXES)
|
|
91
|
+
? forwardedHost
|
|
92
|
+
: getRequestHostname(request.headers.host)
|
|
89
93
|
if (!host) {
|
|
90
94
|
return setCookie
|
|
91
95
|
}
|
|
92
96
|
const cookieDomain = domainMatch[1]
|
|
93
97
|
|
|
94
98
|
if (
|
|
95
|
-
!
|
|
99
|
+
!isHostAllowed(host, ALLOWED_HOST_SUFFIXES) ||
|
|
96
100
|
!shouldReplaceCookieDomain({ cookieDomain, host })
|
|
97
101
|
) {
|
|
98
102
|
return setCookie
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
return setCookie
|
|
105
|
+
return removeCookieDomain(setCookie)
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
const parseRequest = (request: NextApiRequest) => {
|
package/src/sdk/cart/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
sessionStore,
|
|
19
19
|
toSessionInput,
|
|
20
20
|
} from '../session'
|
|
21
|
-
import {
|
|
21
|
+
import { syncSessionWithValidatedCart } from '../session/syncSalesChannelFromOrderForm'
|
|
22
22
|
import { createValidationStore, useStore } from '../useStore'
|
|
23
23
|
import { waitForSessionValidated } from './waitForSessionValidated'
|
|
24
24
|
|
|
@@ -162,14 +162,16 @@ const validateCart = async (cart: Cart): Promise<Cart | null> => {
|
|
|
162
162
|
},
|
|
163
163
|
})
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
syncSessionWithValidatedCart(
|
|
166
|
+
{
|
|
167
|
+
adoptedSalesChannel: validated?.order?.salesChannel,
|
|
168
|
+
// `null` means the browser cart is already valid.
|
|
169
|
+
itemCount: (validated ? validated.order.acceptedOffer : cart.items)
|
|
170
|
+
.length,
|
|
171
|
+
},
|
|
172
|
+
() => sessionStore.read(),
|
|
173
|
+
(session) => sessionStore.setSilent(session)
|
|
174
|
+
)
|
|
173
175
|
|
|
174
176
|
return (
|
|
175
177
|
validated && {
|
|
@@ -47,6 +47,10 @@ export const useLocalizationConfig = (params?: { url?: string | URL }) => {
|
|
|
47
47
|
const session = sessionRef.current
|
|
48
48
|
const channel = JSON.parse(session.channel ?? '{}') ?? {}
|
|
49
49
|
channel.salesChannel = settings.salesChannel
|
|
50
|
+
// The URL is the SC intent: validateCart never adopts over it.
|
|
51
|
+
channel.salesChannelSource = 'url'
|
|
52
|
+
// Adoption never applies on the URL SC, so a recorded rejection is moot.
|
|
53
|
+
delete channel.rejectedSalesChannel
|
|
50
54
|
|
|
51
55
|
const newSession = {
|
|
52
56
|
...session,
|
|
@@ -46,6 +46,10 @@ export function getInitialSession(
|
|
|
46
46
|
const settings = getSettings()
|
|
47
47
|
const channel = JSON.parse(defaults.channel ?? '{}') ?? {}
|
|
48
48
|
channel.salesChannel = settings.salesChannel
|
|
49
|
+
// The URL is the SC intent: validateCart never adopts over it.
|
|
50
|
+
channel.salesChannelSource = 'url'
|
|
51
|
+
// Adoption never applies on the URL SC, so a recorded rejection is moot.
|
|
52
|
+
delete channel.rejectedSalesChannel
|
|
49
53
|
|
|
50
54
|
return {
|
|
51
55
|
...defaults,
|
|
@@ -81,6 +85,10 @@ export function reconcileSessionLocale(fromIDB: Session): Session {
|
|
|
81
85
|
const settings = getSettings()
|
|
82
86
|
const channel = safeParseChannel(fromIDB.channel)
|
|
83
87
|
channel.salesChannel = settings.salesChannel
|
|
88
|
+
// The URL is the SC intent: validateCart never adopts over it.
|
|
89
|
+
channel.salesChannelSource = 'url'
|
|
90
|
+
// Adoption never applies on the URL SC, so a recorded rejection is moot.
|
|
91
|
+
delete channel.rejectedSalesChannel
|
|
84
92
|
|
|
85
93
|
return {
|
|
86
94
|
...fromIDB,
|
|
@@ -35,8 +35,9 @@ function readSalesChannel(channel: Record<string, unknown>): string {
|
|
|
35
35
|
* (e.g. after Quick Order) without revalidating the cart.
|
|
36
36
|
*
|
|
37
37
|
* Uses `sessionStore.setSilent` so we do not trigger `cartStore.set` mid-flight
|
|
38
|
-
* (that race previously wiped SC-exclusive items). Marks the channel
|
|
39
|
-
*
|
|
38
|
+
* (that race previously wiped SC-exclusive items). Marks the channel with
|
|
39
|
+
* `salesChannelSource: 'orderForm'` so `validateSession` keeps it until the
|
|
40
|
+
* cart is empty (see `releaseAdoptedSalesChannel`).
|
|
40
41
|
*/
|
|
41
42
|
export function syncSalesChannelFromOrderForm(
|
|
42
43
|
salesChannel: string | null | undefined,
|
|
@@ -61,8 +62,60 @@ export function syncSalesChannelFromOrderForm(
|
|
|
61
62
|
...channel,
|
|
62
63
|
salesChannel: nextSalesChannel,
|
|
63
64
|
hasOnlyDefaultSalesChannel: false,
|
|
65
|
+
salesChannelSource: 'orderForm',
|
|
64
66
|
}),
|
|
65
67
|
})
|
|
66
68
|
|
|
67
69
|
return true
|
|
68
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Drops the orderForm adoption marker and any rejected SC once the cart has no
|
|
74
|
+
* items, so Session Manager can again reject or replace the sales channel on
|
|
75
|
+
* the next `validateSession`. The SC itself is kept: an empty `salesChannel`
|
|
76
|
+
* would reach Checkout and catalog queries.
|
|
77
|
+
*/
|
|
78
|
+
export function releaseAdoptedSalesChannel(
|
|
79
|
+
readSession: () => Session,
|
|
80
|
+
setSilent: (session: Session) => void
|
|
81
|
+
): boolean {
|
|
82
|
+
const session = readSession()
|
|
83
|
+
const { salesChannelSource, rejectedSalesChannel, ...channel } =
|
|
84
|
+
parseChannelRecord(session.channel)
|
|
85
|
+
const isAdopted = salesChannelSource === 'orderForm'
|
|
86
|
+
|
|
87
|
+
if (!isAdopted && rejectedSalesChannel === undefined) {
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setSilent({
|
|
92
|
+
...session,
|
|
93
|
+
channel: JSON.stringify(
|
|
94
|
+
isAdopted || salesChannelSource === undefined
|
|
95
|
+
? channel
|
|
96
|
+
: { ...channel, salesChannelSource }
|
|
97
|
+
),
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Applies the sales channel outcome of a `validateCart` to the session: adopt
|
|
105
|
+
* the orderForm SC when the API returned one, release the adoption once the
|
|
106
|
+
* cart is empty.
|
|
107
|
+
*/
|
|
108
|
+
export function syncSessionWithValidatedCart(
|
|
109
|
+
{
|
|
110
|
+
adoptedSalesChannel,
|
|
111
|
+
itemCount,
|
|
112
|
+
}: { adoptedSalesChannel: string | null | undefined; itemCount: number },
|
|
113
|
+
readSession: () => Session,
|
|
114
|
+
setSilent: (session: Session) => void
|
|
115
|
+
) {
|
|
116
|
+
syncSalesChannelFromOrderForm(adoptedSalesChannel, readSession, setSilent)
|
|
117
|
+
|
|
118
|
+
if (itemCount === 0) {
|
|
119
|
+
releaseAdoptedSalesChannel(readSession, setSilent)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The result is written verbatim into a Set-Cookie Domain attribute, so
|
|
2
|
+
// characters such as ";" or spaces would inject cookie attributes.
|
|
3
|
+
const HOSTNAME_PATTERN = /^[a-z0-9.-]+$/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An entry starting with "." matches subdomains only; any other entry matches
|
|
7
|
+
* the host itself and its subdomains. Matching is anchored on a label boundary,
|
|
8
|
+
* so "evil-localhost" never matches "localhost".
|
|
9
|
+
*/
|
|
10
|
+
export function isHostAllowed(host: string, allowList: string[]) {
|
|
11
|
+
const normalizedHost = host.toLowerCase()
|
|
12
|
+
|
|
13
|
+
if (!HOSTNAME_PATTERN.test(normalizedHost)) {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return allowList.some((entry) =>
|
|
18
|
+
entry.startsWith('.')
|
|
19
|
+
? normalizedHost.endsWith(entry)
|
|
20
|
+
: normalizedHost === entry || normalizedHost.endsWith(`.${entry}`)
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns the `x-forwarded-host` value only when exactly one is present.
|
|
26
|
+
*
|
|
27
|
+
* Which hop of a proxy chain is trustworthy is not known here: with several
|
|
28
|
+
* values the leftmost one is whatever the client sent. So a multi-value header
|
|
29
|
+
* (comma-joined by Node, or an array) is ignored and the caller falls back to
|
|
30
|
+
* `host`. A single value still assumes the ingress overwrites the header.
|
|
31
|
+
*/
|
|
32
|
+
export function singleForwardedHost(
|
|
33
|
+
header: string | string[] | undefined
|
|
34
|
+
): string | undefined {
|
|
35
|
+
if (Array.isArray(header)) {
|
|
36
|
+
return header.length === 1 ? singleForwardedHost(header[0]) : undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const value = header?.trim()
|
|
40
|
+
|
|
41
|
+
return value && !value.includes(',') ? value : undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Drops every Domain attribute so the cookie becomes host-only: the browser
|
|
46
|
+
* scopes it to the exact host it requested, whatever header picked this path.
|
|
47
|
+
*/
|
|
48
|
+
export function removeCookieDomain(setCookie: string): string {
|
|
49
|
+
return setCookie.replaceAll(/;\s*domain=[^;]*/gi, '')
|
|
50
|
+
}
|
package/src/utils/utilities.ts
CHANGED
|
@@ -110,9 +110,9 @@ export const toArray = <T>(x: T[] | T | undefined) =>
|
|
|
110
110
|
Array.isArray(x) ? x : x ? [x] : []
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Filters the channel object by removing the hasOnlyDefaultSalesChannel
|
|
114
|
-
*
|
|
115
|
-
* so we remove
|
|
113
|
+
* Filters the channel object by removing the hasOnlyDefaultSalesChannel, salesChannelSource and rejectedSalesChannel keys.
|
|
114
|
+
* These keys are session-only (useAuth and the ValidateSession/ValidateCart mutations),
|
|
115
|
+
* so we remove them from the session's channel object to avoid unnecessary cache invalidations and query executions
|
|
116
116
|
*
|
|
117
117
|
* @param channel - The channel string to filter.
|
|
118
118
|
* @returns The filtered channel string.
|
|
@@ -122,8 +122,12 @@ export function filterChannel(channel: string): string {
|
|
|
122
122
|
return '{}'
|
|
123
123
|
}
|
|
124
124
|
try {
|
|
125
|
-
const {
|
|
126
|
-
|
|
125
|
+
const {
|
|
126
|
+
hasOnlyDefaultSalesChannel,
|
|
127
|
+
salesChannelSource,
|
|
128
|
+
rejectedSalesChannel,
|
|
129
|
+
...filteredChannel
|
|
130
|
+
} = JSON.parse(channel)
|
|
127
131
|
return JSON.stringify(filteredChannel)
|
|
128
132
|
} catch {
|
|
129
133
|
console.warn('[filterChannel] Invalid channel JSON:', channel)
|
|
@@ -7,7 +7,7 @@ const mockSessionSet = vi.hoisted(() => vi.fn())
|
|
|
7
7
|
|
|
8
8
|
vi.mock('src/sdk/session', async () => ({
|
|
9
9
|
validateSession: mockValidateSession,
|
|
10
|
-
sessionStore: { set: mockSessionSet },
|
|
10
|
+
sessionStore: { set: mockSessionSet, read: vi.fn() },
|
|
11
11
|
toSessionInput: (await import('src/sdk/session/toSessionInput'))
|
|
12
12
|
.toSessionInput,
|
|
13
13
|
}))
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { act, renderHook } from '@testing-library/react'
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
+
|
|
8
|
+
const { mockRead, mockSet, mockValidateSession } = vi.hoisted(() => ({
|
|
9
|
+
mockRead: vi.fn(),
|
|
10
|
+
mockSet: vi.fn(),
|
|
11
|
+
mockValidateSession: vi.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
vi.mock('src/sdk/session', async () => ({
|
|
15
|
+
sessionStore: { read: mockRead, set: mockSet },
|
|
16
|
+
validateSession: mockValidateSession,
|
|
17
|
+
toSessionInput: (await import('src/sdk/session/toSessionInput'))
|
|
18
|
+
.toSessionInput,
|
|
19
|
+
}))
|
|
20
|
+
vi.mock('discovery.config', () => ({ deliveryPromise: { enabled: false } }))
|
|
21
|
+
vi.mock('src/sdk/product', () => ({ getProductCount: vi.fn() }))
|
|
22
|
+
|
|
23
|
+
import useRegion from '../../../src/components/region/RegionModal/useRegion'
|
|
24
|
+
|
|
25
|
+
// What `useSession()` hands to region components: `filterChannel` already
|
|
26
|
+
// stripped the session-only keys.
|
|
27
|
+
const filteredSession = {
|
|
28
|
+
locale: 'en-US',
|
|
29
|
+
currency: { code: 'USD', symbol: '$' },
|
|
30
|
+
country: 'USA',
|
|
31
|
+
channel: JSON.stringify({ salesChannel: '4', regionId: '' }),
|
|
32
|
+
postalCode: null,
|
|
33
|
+
} as any
|
|
34
|
+
|
|
35
|
+
const storedChannel = JSON.stringify({
|
|
36
|
+
salesChannel: '4',
|
|
37
|
+
regionId: '',
|
|
38
|
+
hasOnlyDefaultSalesChannel: false,
|
|
39
|
+
rejectedSalesChannel: '6',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('useRegion', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks()
|
|
45
|
+
mockRead.mockReturnValue({ ...filteredSession, channel: storedChannel })
|
|
46
|
+
mockValidateSession.mockImplementation(async (session) => session)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('validates the new postal code with the stored channel, keeping session-only keys', async () => {
|
|
50
|
+
const { result } = renderHook(() => useRegion())
|
|
51
|
+
|
|
52
|
+
await act(() =>
|
|
53
|
+
result.current.setRegion({
|
|
54
|
+
session: filteredSession,
|
|
55
|
+
postalCode: '10001',
|
|
56
|
+
errorMessage: 'error',
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(mockValidateSession).toHaveBeenCalledWith(
|
|
61
|
+
expect.objectContaining({ channel: storedChannel, postalCode: '10001' })
|
|
62
|
+
)
|
|
63
|
+
expect(mockSet).toHaveBeenCalledWith(
|
|
64
|
+
expect.objectContaining({ channel: storedChannel })
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -134,6 +134,14 @@ describe('getInitialSession (browser, localization enabled)', () => {
|
|
|
134
134
|
expect(channel.regionId).toBe('region-abc')
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
+
it('marks the URL-derived salesChannel as intentional', () => {
|
|
138
|
+
stubHref('https://store.example.com/en-US/some/path')
|
|
139
|
+
|
|
140
|
+
const result = getInitialSession(defaults)
|
|
141
|
+
|
|
142
|
+
expect(JSON.parse(result.channel ?? '{}').salesChannelSource).toBe('url')
|
|
143
|
+
})
|
|
144
|
+
|
|
137
145
|
it('preserves non-locale defaults (country, person, etc.)', () => {
|
|
138
146
|
stubHref('https://store.example.com/en-US/some/path')
|
|
139
147
|
|
|
@@ -165,6 +165,22 @@ describe('reconcileSessionLocale (browser, localization enabled)', () => {
|
|
|
165
165
|
expect(channel.customKey).toBe('keep-me')
|
|
166
166
|
})
|
|
167
167
|
|
|
168
|
+
it('marks the URL salesChannel as intentional, replacing an orderForm adoption', () => {
|
|
169
|
+
stubHref('https://store.example.com/pt-BR/some/path')
|
|
170
|
+
|
|
171
|
+
const payload: Session = {
|
|
172
|
+
...STALE,
|
|
173
|
+
channel:
|
|
174
|
+
'{"salesChannel":"4","salesChannelSource":"orderForm","rejectedSalesChannel":"6"}',
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const channel = JSON.parse(reconcileSessionLocale(payload).channel ?? '{}')
|
|
178
|
+
|
|
179
|
+
expect(channel.salesChannel).toBe('1')
|
|
180
|
+
expect(channel.salesChannelSource).toBe('url')
|
|
181
|
+
expect(channel).not.toHaveProperty('rejectedSalesChannel')
|
|
182
|
+
})
|
|
183
|
+
|
|
168
184
|
it('survives a malformed channel JSON in the payload (does not throw, still reconciles)', () => {
|
|
169
185
|
stubHref('https://store.example.com/pt-BR/some/path')
|
|
170
186
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import type { Session } from '@faststore/sdk'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
releaseAdoptedSalesChannel,
|
|
5
|
+
syncSalesChannelFromOrderForm,
|
|
6
|
+
syncSessionWithValidatedCart,
|
|
7
|
+
} from '../../../src/sdk/session/syncSalesChannelFromOrderForm'
|
|
4
8
|
|
|
5
9
|
const baseSession = (
|
|
6
10
|
salesChannel: string,
|
|
@@ -44,6 +48,7 @@ describe('syncSalesChannelFromOrderForm', () => {
|
|
|
44
48
|
expect(JSON.parse(next.channel ?? '{}')).toMatchObject({
|
|
45
49
|
salesChannel: '2',
|
|
46
50
|
hasOnlyDefaultSalesChannel: false,
|
|
51
|
+
salesChannelSource: 'orderForm',
|
|
47
52
|
})
|
|
48
53
|
})
|
|
49
54
|
|
|
@@ -123,3 +128,155 @@ describe('syncSalesChannelFromOrderForm', () => {
|
|
|
123
128
|
)
|
|
124
129
|
})
|
|
125
130
|
})
|
|
131
|
+
|
|
132
|
+
describe('releaseAdoptedSalesChannel', () => {
|
|
133
|
+
const withChannel = (channel: Record<string, unknown>) =>
|
|
134
|
+
baseSession('', true, JSON.stringify(channel))
|
|
135
|
+
|
|
136
|
+
it('drops the orderForm marker and keeps the rest of the channel', () => {
|
|
137
|
+
const setSilent = vi.fn()
|
|
138
|
+
const released = releaseAdoptedSalesChannel(
|
|
139
|
+
() =>
|
|
140
|
+
withChannel({
|
|
141
|
+
salesChannel: '4',
|
|
142
|
+
regionId: 'r1',
|
|
143
|
+
hasOnlyDefaultSalesChannel: false,
|
|
144
|
+
salesChannelSource: 'orderForm',
|
|
145
|
+
}),
|
|
146
|
+
setSilent
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
expect(released).toBe(true)
|
|
150
|
+
expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
|
|
151
|
+
salesChannel: '4',
|
|
152
|
+
regionId: 'r1',
|
|
153
|
+
hasOnlyDefaultSalesChannel: false,
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('drops a rejected SC together with the adoption marker', () => {
|
|
158
|
+
const setSilent = vi.fn()
|
|
159
|
+
|
|
160
|
+
releaseAdoptedSalesChannel(
|
|
161
|
+
() =>
|
|
162
|
+
withChannel({
|
|
163
|
+
salesChannel: '4',
|
|
164
|
+
salesChannelSource: 'orderForm',
|
|
165
|
+
rejectedSalesChannel: '6',
|
|
166
|
+
}),
|
|
167
|
+
setSilent
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
|
|
171
|
+
salesChannel: '4',
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('drops a rejected SC left after the adoption was dropped', () => {
|
|
176
|
+
const setSilent = vi.fn()
|
|
177
|
+
|
|
178
|
+
expect(
|
|
179
|
+
releaseAdoptedSalesChannel(
|
|
180
|
+
() => withChannel({ salesChannel: '4', rejectedSalesChannel: '6' }),
|
|
181
|
+
setSilent
|
|
182
|
+
)
|
|
183
|
+
).toBe(true)
|
|
184
|
+
expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
|
|
185
|
+
salesChannel: '4',
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('keeps the URL marker when dropping a rejected SC', () => {
|
|
190
|
+
const setSilent = vi.fn()
|
|
191
|
+
|
|
192
|
+
releaseAdoptedSalesChannel(
|
|
193
|
+
() =>
|
|
194
|
+
withChannel({
|
|
195
|
+
salesChannel: '3',
|
|
196
|
+
salesChannelSource: 'url',
|
|
197
|
+
rejectedSalesChannel: '6',
|
|
198
|
+
}),
|
|
199
|
+
setSilent
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
|
|
203
|
+
salesChannel: '3',
|
|
204
|
+
salesChannelSource: 'url',
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('is a no-op without the orderForm marker', () => {
|
|
209
|
+
const setSilent = vi.fn()
|
|
210
|
+
|
|
211
|
+
expect(
|
|
212
|
+
releaseAdoptedSalesChannel(
|
|
213
|
+
() => withChannel({ salesChannel: '4' }),
|
|
214
|
+
setSilent
|
|
215
|
+
)
|
|
216
|
+
).toBe(false)
|
|
217
|
+
expect(
|
|
218
|
+
releaseAdoptedSalesChannel(
|
|
219
|
+
() => withChannel({ salesChannel: '3', salesChannelSource: 'url' }),
|
|
220
|
+
setSilent
|
|
221
|
+
)
|
|
222
|
+
).toBe(false)
|
|
223
|
+
expect(
|
|
224
|
+
releaseAdoptedSalesChannel(() => baseSession('', true, '{'), setSilent)
|
|
225
|
+
).toBe(false)
|
|
226
|
+
expect(setSilent).not.toHaveBeenCalled()
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
describe('syncSessionWithValidatedCart', () => {
|
|
231
|
+
const store = (channel: Record<string, unknown>) => {
|
|
232
|
+
let session = baseSession('', true, JSON.stringify(channel))
|
|
233
|
+
const setSilent = vi.fn((next: Session) => {
|
|
234
|
+
session = next
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
read: () => session,
|
|
239
|
+
setSilent,
|
|
240
|
+
channel: () => JSON.parse(session.channel ?? '{}'),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
it('adopts the returned SC and keeps the marker while the cart has items', () => {
|
|
245
|
+
const s = store({ salesChannel: '2' })
|
|
246
|
+
|
|
247
|
+
syncSessionWithValidatedCart(
|
|
248
|
+
{ adoptedSalesChannel: '4', itemCount: 1 },
|
|
249
|
+
s.read,
|
|
250
|
+
s.setSilent
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
expect(s.channel()).toMatchObject({
|
|
254
|
+
salesChannel: '4',
|
|
255
|
+
salesChannelSource: 'orderForm',
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('releases the adoption once the validated cart is empty', () => {
|
|
260
|
+
const s = store({ salesChannel: '4', salesChannelSource: 'orderForm' })
|
|
261
|
+
|
|
262
|
+
syncSessionWithValidatedCart(
|
|
263
|
+
{ adoptedSalesChannel: null, itemCount: 0 },
|
|
264
|
+
s.read,
|
|
265
|
+
s.setSilent
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(s.channel()).toEqual({ salesChannel: '4' })
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('leaves the session untouched when nothing was adopted and the cart has items', () => {
|
|
272
|
+
const s = store({ salesChannel: '4', salesChannelSource: 'orderForm' })
|
|
273
|
+
|
|
274
|
+
syncSessionWithValidatedCart(
|
|
275
|
+
{ adoptedSalesChannel: undefined, itemCount: 2 },
|
|
276
|
+
s.read,
|
|
277
|
+
s.setSilent
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
expect(s.setSilent).not.toHaveBeenCalled()
|
|
281
|
+
})
|
|
282
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { filterChannel } from 'src/utils/utilities'
|
|
4
|
+
|
|
5
|
+
describe('filterChannel', () => {
|
|
6
|
+
it('strips session-only keys so they never reach queries or cache keys', () => {
|
|
7
|
+
expect(
|
|
8
|
+
JSON.parse(
|
|
9
|
+
filterChannel(
|
|
10
|
+
JSON.stringify({
|
|
11
|
+
salesChannel: '4',
|
|
12
|
+
regionId: 'r1',
|
|
13
|
+
hasOnlyDefaultSalesChannel: false,
|
|
14
|
+
salesChannelSource: 'orderForm',
|
|
15
|
+
rejectedSalesChannel: '6',
|
|
16
|
+
})
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
).toEqual({ salesChannel: '4', regionId: 'r1' })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('returns an empty object for an empty channel', () => {
|
|
23
|
+
expect(filterChannel('')).toBe('{}')
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
singleForwardedHost,
|
|
5
|
+
isHostAllowed,
|
|
6
|
+
removeCookieDomain,
|
|
7
|
+
} from '../../src/utils/trustedForwardedHost'
|
|
8
|
+
|
|
9
|
+
const ALLOW_LIST = ['localhost', '.vtex.app', '.localhost']
|
|
10
|
+
|
|
11
|
+
describe('isHostAllowed', () => {
|
|
12
|
+
it.each([
|
|
13
|
+
'localhost',
|
|
14
|
+
'store.vtex.app',
|
|
15
|
+
'sfj-1--acc.preview.vtex.app',
|
|
16
|
+
'a.localhost',
|
|
17
|
+
])('allows %s', (host) => {
|
|
18
|
+
expect(isHostAllowed(host, ALLOW_LIST)).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it.each([
|
|
22
|
+
'evil-localhost',
|
|
23
|
+
'notlocalhost',
|
|
24
|
+
'evil-vtex.app',
|
|
25
|
+
'vtex.app.evil.com',
|
|
26
|
+
'evil.com',
|
|
27
|
+
])('rejects %s', (host) => {
|
|
28
|
+
expect(isHostAllowed(host, ALLOW_LIST)).toBe(false)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('isHostAllowed normalization', () => {
|
|
33
|
+
it('is case-insensitive', () => {
|
|
34
|
+
expect(isHostAllowed('Store.VTEX.app', ALLOW_LIST)).toBe(true)
|
|
35
|
+
expect(isHostAllowed('LOCALHOST', ALLOW_LIST)).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it.each([
|
|
39
|
+
'x;max-age=0;y.vtex.app',
|
|
40
|
+
'a b.vtex.app',
|
|
41
|
+
'x"y.vtex.app',
|
|
42
|
+
'x=y.vtex.app',
|
|
43
|
+
'x,y.vtex.app',
|
|
44
|
+
'x\r\nSet-Cookie: evil=1.vtex.app',
|
|
45
|
+
'x\ny.vtex.app',
|
|
46
|
+
'x\ry.vtex.app',
|
|
47
|
+
])('rejects %s, which would inject cookie attributes', (host) => {
|
|
48
|
+
expect(isHostAllowed(host, ALLOW_LIST)).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('accepts punycode hostnames', () => {
|
|
52
|
+
expect(isHostAllowed('xn--lja-bma.vtex.app', ALLOW_LIST)).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('expects a hostname: a port is not stripped here', () => {
|
|
56
|
+
expect(isHostAllowed('store.vtex.app:8080', ALLOW_LIST)).toBe(false)
|
|
57
|
+
expect(isHostAllowed('evil.com:vtex.app', ALLOW_LIST)).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('singleForwardedHost', () => {
|
|
62
|
+
it('returns the value when there is exactly one', () => {
|
|
63
|
+
expect(singleForwardedHost('a.vtex.app')).toBe('a.vtex.app')
|
|
64
|
+
expect(singleForwardedHost(' a.vtex.app ')).toBe('a.vtex.app')
|
|
65
|
+
expect(singleForwardedHost(['a.vtex.app'])).toBe('a.vtex.app')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('returns undefined when missing or empty', () => {
|
|
69
|
+
expect(singleForwardedHost(undefined)).toBeUndefined()
|
|
70
|
+
expect(singleForwardedHost('')).toBeUndefined()
|
|
71
|
+
expect(singleForwardedHost([])).toBeUndefined()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('ignores multi-value headers instead of picking one', () => {
|
|
75
|
+
expect(singleForwardedHost('a.vtex.app, evil.com')).toBeUndefined()
|
|
76
|
+
expect(singleForwardedHost('evil.com, a.vtex.app')).toBeUndefined()
|
|
77
|
+
expect(singleForwardedHost(['a.vtex.app', 'evil.com'])).toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('removeCookieDomain', () => {
|
|
82
|
+
it('drops the Domain attribute and keeps the rest', () => {
|
|
83
|
+
expect(
|
|
84
|
+
removeCookieDomain(
|
|
85
|
+
'checkout.vtex.com=__ofid=abc; expires=Sun, 04 Apr 2027 18:31:26 GMT; domain=acc.vtexcommercestable.com.br; path=/; secure; samesite=lax; httponly'
|
|
86
|
+
)
|
|
87
|
+
).toBe(
|
|
88
|
+
'checkout.vtex.com=__ofid=abc; expires=Sun, 04 Apr 2027 18:31:26 GMT; path=/; secure; samesite=lax; httponly'
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('is case-insensitive and removes repeated Domain attributes', () => {
|
|
93
|
+
expect(removeCookieDomain('a=1; Domain=x.com; DOMAIN=y.com; Path=/')).toBe(
|
|
94
|
+
'a=1; Path=/'
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('does not touch a cookie whose name is domain', () => {
|
|
99
|
+
expect(removeCookieDomain('domain=1; path=/')).toBe('domain=1; path=/')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('keeps cookies without a Domain attribute unchanged', () => {
|
|
103
|
+
expect(removeCookieDomain('a=1; path=/')).toBe('a=1; path=/')
|
|
104
|
+
})
|
|
105
|
+
})
|