@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.1
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/package.json +7 -52
- package/src/index.ts +10 -28
- package/src/utils/constants.ts +2 -56
- package/src/utils/cookieSanitizer.ts +59 -59
- package/src/utils/operationRouter.ts +75 -65
- package/src/utils/types.ts +709 -1557
- package/src/vtexClient.ts +354 -343
- package/src/README.md +0 -6
- package/src/__conformance__/upstream.test.ts +0 -158
- package/src/__tests__/client-segment-cookie.test.ts +0 -255
- package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
- package/src/__tests__/schemas.test.ts +0 -71
- package/src/actions/address.ts +0 -250
- package/src/actions/analytics/sendEvent.ts +0 -86
- package/src/actions/auth.ts +0 -333
- package/src/actions/checkout.ts +0 -659
- package/src/actions/index.ts +0 -11
- package/src/actions/masterData.ts +0 -168
- package/src/actions/misc.ts +0 -188
- package/src/actions/newsletter.ts +0 -105
- package/src/actions/orders.ts +0 -34
- package/src/actions/profile.ts +0 -201
- package/src/actions/session.ts +0 -85
- package/src/actions/trigger.ts +0 -43
- package/src/actions/wishlist.ts +0 -114
- package/src/client.ts +0 -724
- package/src/commerceLoaders.ts +0 -267
- package/src/hooks/__tests__/createCart.render.test.ts +0 -114
- package/src/hooks/__tests__/createCart.test.ts +0 -92
- package/src/hooks/__tests__/createUseCart.test.ts +0 -184
- package/src/hooks/__tests__/createUseUser.test.ts +0 -48
- package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
- package/src/hooks/cartQuery.ts +0 -204
- package/src/hooks/createCart.ts +0 -427
- package/src/hooks/createUseCart.ts +0 -360
- package/src/hooks/createUseUser.ts +0 -153
- package/src/hooks/createUseWishlist.ts +0 -242
- package/src/hooks/index.ts +0 -25
- package/src/hooks/useAutocomplete.ts +0 -83
- package/src/hooks/useCart.ts +0 -243
- package/src/hooks/useUser.ts +0 -78
- package/src/hooks/useWishlist.ts +0 -119
- package/src/invoke.ts +0 -235
- package/src/loaders/ProductDetailsPage.ts +0 -1
- package/src/loaders/ProductList.ts +0 -1
- package/src/loaders/ProductListingPage.ts +0 -1
- package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
- package/src/loaders/address.ts +0 -115
- package/src/loaders/autocomplete.ts +0 -56
- package/src/loaders/brands.ts +0 -44
- package/src/loaders/cart/attachments.ts +0 -53
- package/src/loaders/cart/full.ts +0 -67
- package/src/loaders/cart/gifts.ts +0 -43
- package/src/loaders/cart/orderFormId.ts +0 -31
- package/src/loaders/cart/shipping.test.ts +0 -125
- package/src/loaders/cart/shipping.ts +0 -136
- package/src/loaders/cart/summary.ts +0 -36
- package/src/loaders/cart.ts +0 -52
- package/src/loaders/catalog.ts +0 -163
- package/src/loaders/collections.ts +0 -55
- package/src/loaders/index.ts +0 -20
- package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
- package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
- package/src/loaders/intelligentSearch/productList.ts +0 -170
- package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
- package/src/loaders/intelligentSearch/suggestions.ts +0 -44
- package/src/loaders/legacy/productDetailsPage.ts +0 -1
- package/src/loaders/legacy/productList.ts +0 -1
- package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
- package/src/loaders/legacy.ts +0 -710
- package/src/loaders/logistics.ts +0 -111
- package/src/loaders/minicart.ts +0 -78
- package/src/loaders/navbar.ts +0 -26
- package/src/loaders/orders.ts +0 -92
- package/src/loaders/pageType.ts +0 -68
- package/src/loaders/paths/PDPDefaultPath.ts +0 -51
- package/src/loaders/payment.ts +0 -97
- package/src/loaders/productListFull.ts +0 -157
- package/src/loaders/profile.ts +0 -138
- package/src/loaders/promotion.ts +0 -30
- package/src/loaders/search.ts +0 -124
- package/src/loaders/session.ts +0 -83
- package/src/loaders/user.ts +0 -87
- package/src/loaders/wishlist.ts +0 -89
- package/src/loaders/wishlistProducts.ts +0 -69
- package/src/loaders/workflow/products.ts +0 -62
- package/src/loaders/workflow.ts +0 -316
- package/src/logo.png +0 -0
- package/src/manifest.gen.ts +0 -89
- package/src/middleware.cacheHeaders.test.ts +0 -44
- package/src/middleware.ts +0 -240
- package/src/mod.ts +0 -173
- package/src/registry.ts +0 -9
- package/src/schemas.gen.ts +0 -3191
- package/src/schemas.ts +0 -30
- package/src/types.ts +0 -248
- package/src/utils/__tests__/cartProjection.test.ts +0 -118
- package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
- package/src/utils/__tests__/fetch.test.ts +0 -80
- package/src/utils/__tests__/fetchCache.test.ts +0 -155
- package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
- package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
- package/src/utils/__tests__/minicart.test.ts +0 -184
- package/src/utils/__tests__/operationRouter.test.ts +0 -227
- package/src/utils/__tests__/resilience.test.ts +0 -205
- package/src/utils/__tests__/resourceRange.test.ts +0 -32
- package/src/utils/__tests__/simulationCache.test.ts +0 -40
- package/src/utils/__tests__/sitemap.test.ts +0 -185
- package/src/utils/__tests__/slugify.test.ts +0 -31
- package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
- package/src/utils/__tests__/transform.test.ts +0 -1111
- package/src/utils/accountLoaders.ts +0 -203
- package/src/utils/authHelpers.ts +0 -85
- package/src/utils/batch.ts +0 -18
- package/src/utils/buildOfferShelf.test.ts +0 -59
- package/src/utils/cartProjection.ts +0 -96
- package/src/utils/cookies.ts +0 -167
- package/src/utils/enrichment.ts +0 -556
- package/src/utils/fetch.ts +0 -111
- package/src/utils/fetchCache.ts +0 -55
- package/src/utils/index.ts +0 -19
- package/src/utils/instrumentedFetch.ts +0 -98
- package/src/utils/intelligentSearch.ts +0 -96
- package/src/utils/legacy.ts +0 -139
- package/src/utils/minicart.ts +0 -139
- package/src/utils/pickAndOmit.ts +0 -25
- package/src/utils/proxy.ts +0 -495
- package/src/utils/resilience.ts +0 -352
- package/src/utils/resourceRange.ts +0 -10
- package/src/utils/segment.ts +0 -152
- package/src/utils/similars.ts +0 -35
- package/src/utils/simulationCache.ts +0 -73
- package/src/utils/sitemap.ts +0 -283
- package/src/utils/slugCache.ts +0 -28
- package/src/utils/slugify.ts +0 -13
- package/src/utils/transform.ts +0 -1691
- package/src/utils/vtexId.ts +0 -151
- package/src/vtexClient.test.ts +0 -204
- package/tsconfig.json +0 -7
package/src/utils/vtexId.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VTEX authentication cookie parser.
|
|
3
|
-
*
|
|
4
|
-
* Parses the VtexIdclientAutCookie JWT to detect login state
|
|
5
|
-
* without making API calls. Only decodes the payload (no signature
|
|
6
|
-
* verification -- we only need presence and expiry, not auth).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export interface VtexAuthInfo {
|
|
10
|
-
isLoggedIn: boolean;
|
|
11
|
-
email?: string;
|
|
12
|
-
account?: string;
|
|
13
|
-
/** Unix timestamp (seconds) when the token expires. */
|
|
14
|
-
exp?: number;
|
|
15
|
-
/** Whether the token is expired. */
|
|
16
|
-
isExpired: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const VTEX_AUTH_COOKIE = "VtexIdclientAutCookie";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Extract the VtexIdclientAutCookie value from a cookie string.
|
|
23
|
-
*
|
|
24
|
-
* Matches BOTH the base cookie `VtexIdclientAutCookie=` and the account-suffixed
|
|
25
|
-
* variant `VtexIdclientAutCookie_{account}=`. VTEX frequently sets ONLY the
|
|
26
|
-
* suffixed variant on the storefront domain — matching just the base name makes
|
|
27
|
-
* genuinely logged-in users look anonymous, which defeats any logged-in cache
|
|
28
|
-
* bypass built on top of this and can leak personalized/cached responses.
|
|
29
|
-
*/
|
|
30
|
-
export function extractVtexAuthCookie(cookieHeader: string): string | null {
|
|
31
|
-
const match = cookieHeader.match(
|
|
32
|
-
new RegExp(`(?:^|;\\s*)${VTEX_AUTH_COOKIE}(?:_[^=;\\s]+)?=([^;]+)`),
|
|
33
|
-
);
|
|
34
|
-
return match?.[1] ?? null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Decode a JWT payload without verification.
|
|
39
|
-
* Only reads the middle segment (claims).
|
|
40
|
-
*/
|
|
41
|
-
function decodeJwtPayload(token: string): Record<string, unknown> | null {
|
|
42
|
-
try {
|
|
43
|
-
const parts = token.split(".");
|
|
44
|
-
if (parts.length !== 3) return null;
|
|
45
|
-
const payload = parts[1];
|
|
46
|
-
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
47
|
-
return JSON.parse(decoded);
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Parse a VTEX auth cookie token into structured auth info.
|
|
55
|
-
*/
|
|
56
|
-
export function parseVtexAuthToken(token: string): VtexAuthInfo {
|
|
57
|
-
const payload = decodeJwtPayload(token);
|
|
58
|
-
if (!payload) {
|
|
59
|
-
// Opaque (non-JWT) token. VTEX auth cookies are frequently opaque strings
|
|
60
|
-
// rather than JWTs — we cannot read `exp`, but the mere presence of the
|
|
61
|
-
// cookie means the user IS authenticated. Treating it as logged-out (the
|
|
62
|
-
// previous behavior) makes real sessions look anonymous and defeats any
|
|
63
|
-
// logged-in cache bypass. Fail towards "logged in" (i.e. do-not-cache).
|
|
64
|
-
return { isLoggedIn: true, isExpired: false };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const exp = typeof payload.exp === "number" ? payload.exp : undefined;
|
|
68
|
-
const isExpired = exp != null ? exp * 1000 < Date.now() : false;
|
|
69
|
-
const email =
|
|
70
|
-
typeof payload.sub === "string"
|
|
71
|
-
? payload.sub
|
|
72
|
-
: typeof payload.userId === "string"
|
|
73
|
-
? payload.userId
|
|
74
|
-
: undefined;
|
|
75
|
-
|
|
76
|
-
const account = typeof payload.account === "string" ? payload.account : undefined;
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
isLoggedIn: !isExpired,
|
|
80
|
-
email,
|
|
81
|
-
account,
|
|
82
|
-
exp,
|
|
83
|
-
isExpired,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Check if a request has a valid (non-expired) VTEX auth cookie.
|
|
89
|
-
*/
|
|
90
|
-
export function isVtexLoggedIn(request: Request): boolean {
|
|
91
|
-
const cookies = request.headers.get("cookie") ?? "";
|
|
92
|
-
const token = extractVtexAuthCookie(cookies);
|
|
93
|
-
if (!token) return false;
|
|
94
|
-
return parseVtexAuthToken(token).isLoggedIn;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Build a complete auth cookie header string from either a raw token
|
|
99
|
-
* or an already-formatted cookie string.
|
|
100
|
-
*
|
|
101
|
-
* VTEX requires both `VtexIdclientAutCookie` and the account-suffixed
|
|
102
|
-
* variant `VtexIdclientAutCookie_{account}` for authenticated GraphQL
|
|
103
|
-
* calls to myvtex.com.
|
|
104
|
-
*
|
|
105
|
-
* If `authCookie` already contains `=` (i.e. it's a full cookie string),
|
|
106
|
-
* it's returned as-is. Otherwise the token is wrapped in both cookie names.
|
|
107
|
-
*/
|
|
108
|
-
export function buildAuthCookieHeader(authCookie: string, account: string): string {
|
|
109
|
-
if (authCookie.includes("=")) return authCookie;
|
|
110
|
-
return `${VTEX_AUTH_COOKIE}=${authCookie}; ${VTEX_AUTH_COOKIE}_${account}=${authCookie}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface CookiePayload {
|
|
114
|
-
sub?: string;
|
|
115
|
-
account?: string;
|
|
116
|
-
audience?: string;
|
|
117
|
-
sess?: string;
|
|
118
|
-
exp?: number;
|
|
119
|
-
userId?: string;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Parse VTEX auth cookies from request headers.
|
|
124
|
-
*
|
|
125
|
-
* Returns the serialized cookie string (for forwarding) and the decoded
|
|
126
|
-
* JWT payload. Compatible with the legacy deco-cx/apps parseCookie API.
|
|
127
|
-
*/
|
|
128
|
-
export function parseCookie(
|
|
129
|
-
headers: Headers,
|
|
130
|
-
account: string,
|
|
131
|
-
): { cookie: string; payload: CookiePayload | undefined } {
|
|
132
|
-
const cookieHeader = headers.get("cookie") ?? "";
|
|
133
|
-
|
|
134
|
-
const base = extractVtexAuthCookie(cookieHeader);
|
|
135
|
-
const suffixedRe = new RegExp(`(?:^|;\\s*)${VTEX_AUTH_COOKIE}_${account}=([^;]+)`);
|
|
136
|
-
const suffixedMatch = cookieHeader.match(suffixedRe);
|
|
137
|
-
const suffixed = suffixedMatch?.[1] ?? null;
|
|
138
|
-
|
|
139
|
-
const token = base ?? suffixed;
|
|
140
|
-
const payload = token
|
|
141
|
-
? ((decodeJwtPayload(token) as CookiePayload | null) ?? undefined)
|
|
142
|
-
: undefined;
|
|
143
|
-
|
|
144
|
-
const parts: string[] = [];
|
|
145
|
-
if (base) parts.push(`${VTEX_AUTH_COOKIE}=${base}`);
|
|
146
|
-
if (suffixed) parts.push(`${VTEX_AUTH_COOKIE}_${account}=${suffixed}`);
|
|
147
|
-
|
|
148
|
-
return { cookie: parts.join("; "), payload };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export { VTEX_AUTH_COOKIE };
|
package/src/vtexClient.test.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
// @vitest-environment node
|
|
2
|
-
/**
|
|
3
|
-
* The VTEX upstream client (/next/upstream-clients): typed calls over the
|
|
4
|
-
* framework's instrumented fetch, labeled provider "vtex" with a named
|
|
5
|
-
* operation, retries and a circuit breaker on by default, per-shopper inputs
|
|
6
|
-
* as arguments, and errors that carry no body, URL, token or cookie.
|
|
7
|
-
*/
|
|
8
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
9
|
-
|
|
10
|
-
const instrumented = vi.hoisted(() => ({
|
|
11
|
-
options: [] as Record<string, unknown>[],
|
|
12
|
-
operations: [] as (string | undefined)[],
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
vi.mock("@decocms/blocks/fetch", async (importActual) => {
|
|
16
|
-
const actual = await importActual<typeof import("@decocms/blocks/fetch")>();
|
|
17
|
-
return {
|
|
18
|
-
...actual,
|
|
19
|
-
createInstrumentedFetch: (options: Parameters<typeof actual.createInstrumentedFetch>[0]) => {
|
|
20
|
-
instrumented.options.push(options as unknown as Record<string, unknown>);
|
|
21
|
-
const request = actual.createInstrumentedFetch(options);
|
|
22
|
-
return (input: string | URL | Request, init?: RequestInit & { operation?: string }) => {
|
|
23
|
-
instrumented.operations.push(init?.operation);
|
|
24
|
-
return request(input, init);
|
|
25
|
-
};
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
import { createVtexClient, VtexError } from ".";
|
|
31
|
-
|
|
32
|
-
type Call = { url: string; init: RequestInit };
|
|
33
|
-
|
|
34
|
-
/** An upstream answering with `statuses` in order, recording each request. */
|
|
35
|
-
function upstream(statuses: number[] = [], headers: Record<string, string | string[]> = {}) {
|
|
36
|
-
const calls: Call[] = [];
|
|
37
|
-
const fetch = vi.fn(async (input: string | URL | Request, init: RequestInit = {}) => {
|
|
38
|
-
calls.push({ url: String(input), init });
|
|
39
|
-
const response = new Response('{"secret-body":"token-123"}', { status: statuses.shift() ?? 200 });
|
|
40
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
41
|
-
for (const v of Array.isArray(value) ? value : [value]) response.headers.append(name, v);
|
|
42
|
-
}
|
|
43
|
-
return response;
|
|
44
|
-
});
|
|
45
|
-
return { calls, fetch: fetch as unknown as typeof globalThis.fetch };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const header = (call: Call | undefined, name: string) => new Headers(call?.init.headers).get(name);
|
|
49
|
-
|
|
50
|
-
afterEach(() => {
|
|
51
|
-
instrumented.options.length = 0;
|
|
52
|
-
instrumented.operations.length = 0;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("createVtexClient", () => {
|
|
56
|
-
it("uses the instrumented fetch as provider vtex, with retries and a circuit breaker on", () => {
|
|
57
|
-
createVtexClient({ account: "store" });
|
|
58
|
-
expect(instrumented.options[0]).toMatchObject({
|
|
59
|
-
provider: "vtex",
|
|
60
|
-
retry: { attempts: 2, backoffMs: 150 },
|
|
61
|
-
circuitBreaker: { failures: 5, cooldownMs: 5000 },
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("turns retries and the circuit breaker off when asked", () => {
|
|
66
|
-
createVtexClient({ account: "store", retry: false, circuitBreaker: false });
|
|
67
|
-
expect(instrumented.options[0]?.retry).toBeUndefined();
|
|
68
|
-
expect(instrumented.options[0]?.circuitBreaker).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("searches Intelligent Search with facets, region, locale and sales channel as arguments", async () => {
|
|
72
|
-
const { calls, fetch } = upstream();
|
|
73
|
-
const vtex = createVtexClient({
|
|
74
|
-
account: "store",
|
|
75
|
-
appKey: "key",
|
|
76
|
-
appToken: "token",
|
|
77
|
-
salesChannel: "2",
|
|
78
|
-
locale: "pt-BR",
|
|
79
|
-
fetch,
|
|
80
|
-
});
|
|
81
|
-
await vtex.search.products({
|
|
82
|
-
query: "linen shirt",
|
|
83
|
-
count: 12,
|
|
84
|
-
facets: [{ key: "category-1", value: "shirts" }],
|
|
85
|
-
regionId: "v2.ABC",
|
|
86
|
-
});
|
|
87
|
-
const url = new URL(calls[0]!.url);
|
|
88
|
-
expect(url.origin).toBe("https://store.vtexcommercestable.com.br");
|
|
89
|
-
expect(url.pathname).toBe("/api/io/_v/api/intelligent-search/product_search/category-1/shirts");
|
|
90
|
-
expect(Object.fromEntries(url.searchParams)).toEqual({
|
|
91
|
-
query: "linen shirt",
|
|
92
|
-
count: "12",
|
|
93
|
-
locale: "pt-BR",
|
|
94
|
-
regionId: "v2.ABC",
|
|
95
|
-
sc: "2",
|
|
96
|
-
});
|
|
97
|
-
expect(header(calls[0], "x-vtex-api-appkey")).toBe("key");
|
|
98
|
-
expect(header(calls[0], "x-vtex-api-apptoken")).toBe("token");
|
|
99
|
-
expect(header(calls[0], "cookie")).toBeNull();
|
|
100
|
-
expect(instrumented.operations).toEqual(["intelligent-search.product_search"]);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("names every operation from the VTEX API, never a URL", async () => {
|
|
104
|
-
const { fetch } = upstream();
|
|
105
|
-
const vtex = createVtexClient({ account: "store", fetch });
|
|
106
|
-
await vtex.catalog.pageType("/shirts/linen");
|
|
107
|
-
await vtex.catalog.products({ fq: ["productId:1", "productId:2"], from: 0, to: 9 });
|
|
108
|
-
await vtex.checkout.simulation({ items: [{ id: "1", quantity: 1, seller: "1" }] });
|
|
109
|
-
await vtex.checkout.regions({ postalCode: "01000-000", country: "BRA" });
|
|
110
|
-
await vtex.sessions.get();
|
|
111
|
-
expect(instrumented.operations).toEqual([
|
|
112
|
-
"catalog.pagetype",
|
|
113
|
-
"catalog.products.search",
|
|
114
|
-
"checkout.simulation",
|
|
115
|
-
"checkout.regions",
|
|
116
|
-
"sessions.get",
|
|
117
|
-
]);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("retries an idempotent call and fails fast once the breaker opens", async () => {
|
|
121
|
-
const { calls, fetch } = upstream([503, 200]);
|
|
122
|
-
const vtex = createVtexClient({ account: "store", fetch, retry: { attempts: 1, backoffMs: 0 } });
|
|
123
|
-
await expect(vtex.catalog.categoryTree()).resolves.toBeDefined();
|
|
124
|
-
expect(calls).toHaveLength(2);
|
|
125
|
-
|
|
126
|
-
const down = upstream([500, 500, 500]);
|
|
127
|
-
const failing = createVtexClient({
|
|
128
|
-
account: "store",
|
|
129
|
-
fetch: down.fetch,
|
|
130
|
-
retry: false,
|
|
131
|
-
circuitBreaker: { failures: 2, cooldownMs: 60_000 },
|
|
132
|
-
});
|
|
133
|
-
await expect(failing.catalog.categoryTree()).rejects.toBeInstanceOf(VtexError);
|
|
134
|
-
await expect(failing.catalog.categoryTree()).rejects.toBeInstanceOf(VtexError);
|
|
135
|
-
await expect(failing.catalog.categoryTree()).rejects.toThrow(/circuit open/);
|
|
136
|
-
expect(down.calls).toHaveLength(2);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("never retries a cart write", async () => {
|
|
140
|
-
const { calls, fetch } = upstream([503, 200]);
|
|
141
|
-
const vtex = createVtexClient({ account: "store", fetch, retry: { attempts: 3, backoffMs: 0 } });
|
|
142
|
-
await expect(vtex.checkout.addItems("of1", [{ id: 1, quantity: 1, seller: "1" }])).rejects.toThrow(
|
|
143
|
-
VtexError,
|
|
144
|
-
);
|
|
145
|
-
expect(calls).toHaveLength(1);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("forwards the shopper's cookie, sanitized, and hands back VTEX's Set-Cookie values", async () => {
|
|
149
|
-
const { calls, fetch } = upstream([], {
|
|
150
|
-
"set-cookie": ["checkout.vtex.com=__ofid=of1; Domain=store.vtexcommercestable.com.br; Path=/"],
|
|
151
|
-
});
|
|
152
|
-
const vtex = createVtexClient({ account: "store", appKey: "key", appToken: "token", fetch });
|
|
153
|
-
const result = await vtex.checkout.orderForm({
|
|
154
|
-
cookie: "checkout.vtex.com=__ofid=of1; tag=cateçoria",
|
|
155
|
-
});
|
|
156
|
-
expect(header(calls[0], "cookie")).toBe("checkout.vtex.com=__ofid=of1");
|
|
157
|
-
expect(calls[0]?.init.method).toBe("POST");
|
|
158
|
-
// Every orderForm section, and no app credentials next to a shopper's cookie.
|
|
159
|
-
expect(calls[0]?.init.body).toBe("{}");
|
|
160
|
-
expect(header(calls[0], "x-vtex-api-appkey")).toBeNull();
|
|
161
|
-
expect(header(calls[0], "x-vtex-api-apptoken")).toBeNull();
|
|
162
|
-
expect(result.setCookies).toEqual([
|
|
163
|
-
"checkout.vtex.com=__ofid=of1; Domain=store.vtexcommercestable.com.br; Path=/",
|
|
164
|
-
]);
|
|
165
|
-
expect(result.data).toEqual({ "secret-body": "token-123" });
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("throws errors that carry the operation and status, never the body, URL or credentials", async () => {
|
|
169
|
-
const { fetch } = upstream([400]);
|
|
170
|
-
const vtex = createVtexClient({ account: "store", appKey: "key", appToken: "token-123", fetch });
|
|
171
|
-
const error = await vtex.search.products({ query: "q" }).catch((e: unknown) => e);
|
|
172
|
-
expect(error).toBeInstanceOf(VtexError);
|
|
173
|
-
expect(error).toMatchObject({ operation: "intelligent-search.product_search", status: 400 });
|
|
174
|
-
expect((error as Error).message).toBe("vtex intelligent-search.product_search failed with HTTP 400");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("narrows the orderForm sections only when asked", async () => {
|
|
178
|
-
const { calls, fetch } = upstream();
|
|
179
|
-
const vtex = createVtexClient({ account: "store", fetch });
|
|
180
|
-
await vtex.checkout.orderForm({ sections: ["items", "totalizers"] });
|
|
181
|
-
expect(calls[0]?.init.body).toBe('{"expectedOrderFormSections":["items","totalizers"]}');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("keeps caller paths inside their endpoint", async () => {
|
|
185
|
-
const { calls, fetch } = upstream();
|
|
186
|
-
const vtex = createVtexClient({ account: "store", appKey: "key", appToken: "token", fetch });
|
|
187
|
-
for (const path of [
|
|
188
|
-
"../../api/dataentities/CL/search",
|
|
189
|
-
"/shirts/%2e%2e/%2E%2E/api/dataentities/CL/search",
|
|
190
|
-
"shirts/./linen",
|
|
191
|
-
]) {
|
|
192
|
-
await expect(vtex.catalog.pageType(path)).rejects.toThrow(/invalid path segment/);
|
|
193
|
-
}
|
|
194
|
-
await expect(vtex.catalog.products({ term: "a/../../../api/x" })).rejects.toThrow(
|
|
195
|
-
/invalid path segment/,
|
|
196
|
-
);
|
|
197
|
-
expect(calls).toHaveLength(0);
|
|
198
|
-
|
|
199
|
-
await vtex.catalog.pageType("/shirts/linen?_where=x#y");
|
|
200
|
-
const url = new URL(calls[0]!.url);
|
|
201
|
-
expect(url.pathname).toBe("/api/catalog_system/pub/portal/pagetype/shirts/linen%3F_where%3Dx%23y");
|
|
202
|
-
expect(url.search).toBe("");
|
|
203
|
-
});
|
|
204
|
-
});
|