@decocms/apps-magento 8.1.0-next.0 → 8.1.0-next.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.
Files changed (47) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +3 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/magentoClient.d.ts +62 -0
  6. package/dist/magentoClient.d.ts.map +1 -0
  7. package/dist/magentoClient.js +96 -0
  8. package/dist/magentoClient.js.map +1 -0
  9. package/package.json +19 -27
  10. package/src/index.ts +2 -29
  11. package/src/README.md +0 -65
  12. package/src/__tests__/cart.test.ts +0 -139
  13. package/src/__tests__/client.test.ts +0 -252
  14. package/src/__tests__/features.test.ts +0 -51
  15. package/src/__tests__/graphql.test.ts +0 -183
  16. package/src/__tests__/magentoClient.test.ts +0 -166
  17. package/src/__tests__/newsletter-subscribe.test.ts +0 -86
  18. package/src/__tests__/product-stockAlert.test.ts +0 -80
  19. package/src/__tests__/stringifySearchCriteria.test.ts +0 -56
  20. package/src/__tests__/transform.test.ts +0 -346
  21. package/src/__tests__/user-loader.test.ts +0 -134
  22. package/src/__tests__/wishlist-actions.test.ts +0 -140
  23. package/src/__tests__/wishlist-loader.test.ts +0 -97
  24. package/src/actions/newsletter/subscribe.ts +0 -40
  25. package/src/actions/product/stockAlert.ts +0 -67
  26. package/src/actions/wishlist/addItem.ts +0 -57
  27. package/src/actions/wishlist/removeItem.ts +0 -59
  28. package/src/client.ts +0 -252
  29. package/src/loaders/cart.ts +0 -64
  30. package/src/loaders/features.ts +0 -16
  31. package/src/loaders/user.ts +0 -60
  32. package/src/loaders/wishlist.ts +0 -29
  33. package/src/middleware.ts +0 -19
  34. package/src/types.ts +0 -51
  35. package/src/utils/__tests__/instrumentation.test.ts +0 -59
  36. package/src/utils/cacheTimeControl.ts +0 -69
  37. package/src/utils/client/types.ts +0 -270
  38. package/src/utils/constants.ts +0 -126
  39. package/src/utils/fetchCache.ts +0 -55
  40. package/src/utils/graphql-types.ts +0 -99
  41. package/src/utils/graphql.ts +0 -155
  42. package/src/utils/instrumentedFetch.ts +0 -57
  43. package/src/utils/operationRouter.ts +0 -43
  44. package/src/utils/stringifySearchCriteria.ts +0 -62
  45. package/src/utils/transform.ts +0 -283
  46. package/src/utils/user.ts +0 -16
  47. package/tsconfig.json +0 -7
@@ -0,0 +1,2 @@
1
+ export { createMagentoClient, type MagentoClient, type MagentoClientConfig, type MagentoClientOptions, MagentoError, type MagentoRequestInit, } from "./magentoClient.ts";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,YAAY,EACZ,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // `@decocms/apps-magento`: the thin Magento client (see /next/upstream-clients).
2
+ export { createMagentoClient, MagentoError, } from "./magentoClient.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,OAAO,EACL,mBAAmB,EAInB,YAAY,GAEb,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The Magento client: a thin client for a Magento store's REST and GraphQL
3
+ * APIs, over the framework's instrumented fetch (provider "magento"). See
4
+ * /next/upstream-clients.
5
+ *
6
+ * Configuration comes in as arguments; the site reads its environment (or a
7
+ * `secret` block) where it creates the client. Converters, hooks, cart and
8
+ * session flows, caching and feature toggles belong to the site (platform
9
+ * templates), not here.
10
+ */
11
+ import { createInstrumentedFetch } from "@decocms/blocks/fetch";
12
+ export interface MagentoClientConfig {
13
+ /** The store's origin, e.g. `https://store.example.com`. Every request goes here. */
14
+ baseUrl: string;
15
+ /** Integration access token, sent as `Authorization: Bearer` unless a call opts out. */
16
+ apiKey?: string;
17
+ /** Optional value sent as `x-origin-header`, for stores behind an origin guard. */
18
+ originHeader?: string;
19
+ }
20
+ export interface MagentoClientOptions {
21
+ /** The fetch underneath, e.g. a fake in tests. Defaults to `globalThis.fetch`. */
22
+ fetch?: Parameters<typeof createInstrumentedFetch>[0]["fetch"];
23
+ }
24
+ export interface MagentoRequestInit extends RequestInit {
25
+ /** The operation label, the API's own name for the call (e.g. `getCart`), never a URL. */
26
+ operation: string;
27
+ /**
28
+ * Send the `apiKey` bearer token. Default true for `request`/`rest`, false
29
+ * for `graphql`. Never replaces an `Authorization` header the call sets
30
+ * (e.g. a customer token).
31
+ */
32
+ authenticated?: boolean;
33
+ }
34
+ /** Thrown on a non-2xx response or a GraphQL `errors` payload. Never carries bodies or tokens. */
35
+ export declare class MagentoError extends Error {
36
+ readonly operation: string;
37
+ readonly status: number;
38
+ /** How many GraphQL errors the response carried (0 for an HTTP failure). */
39
+ readonly graphqlErrors: number;
40
+ constructor(operation: string, status: number,
41
+ /** How many GraphQL errors the response carried (0 for an HTTP failure). */
42
+ graphqlErrors?: number);
43
+ }
44
+ export type MagentoClient = ReturnType<typeof createMagentoClient>;
45
+ export declare function createMagentoClient(config: MagentoClientConfig, options?: MagentoClientOptions): {
46
+ request: (path: string, init: MagentoRequestInit) => Promise<Response>;
47
+ /** A REST call (`/rest/<store>/V1/...`) returning its parsed JSON body. */
48
+ rest<T>(path: string, init: MagentoRequestInit): Promise<T>;
49
+ /**
50
+ * A GraphQL operation on `/graphql`. `operationName` is sent with the
51
+ * request and is the operation label. `headers` adds per-call headers,
52
+ * such as `Store` or a customer `Authorization` token. Storefront
53
+ * GraphQL is public, so the `apiKey` is sent only with
54
+ * `authenticated: true`, and never over a customer token.
55
+ */
56
+ graphql<TData, TVariables = Record<string, unknown>>(query: string, variables: TVariables | undefined, init: {
57
+ operationName: string;
58
+ headers?: Record<string, string>;
59
+ authenticated?: boolean;
60
+ }): Promise<TData>;
61
+ };
62
+ //# sourceMappingURL=magentoClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magentoClient.d.ts","sourceRoot":"","sources":["../src/magentoClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,kFAAkF;IAClF,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,kGAAkG;AAClG,qBAAa,YAAa,SAAQ,KAAK;IAEnC,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,aAAa;IAJxB,YACW,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM;IACvB,4EAA4E;IACnE,aAAa,SAAI,EAQ3B;CACF;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEnE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,GAAE,oBAAyB;oBAUL,MAAM,QAAQ,kBAAkB,KAAG,OAAO,CAAC,QAAQ,CAAC;IAiB/E,2EAA2E;SAChE,CAAC,QAAQ,MAAM,QAAQ,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAWjE;;;;;;OAMG;YACW,KAAK,EAAE,UAAU,mCACtB,MAAM,aACF,UAAU,GAAG,SAAS,QAC3B;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACzF,OAAO,CAAC,KAAK,CAAC;EAoBpB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The Magento client: a thin client for a Magento store's REST and GraphQL
3
+ * APIs, over the framework's instrumented fetch (provider "magento"). See
4
+ * /next/upstream-clients.
5
+ *
6
+ * Configuration comes in as arguments; the site reads its environment (or a
7
+ * `secret` block) where it creates the client. Converters, hooks, cart and
8
+ * session flows, caching and feature toggles belong to the site (platform
9
+ * templates), not here.
10
+ */
11
+ import { createInstrumentedFetch } from "@decocms/blocks/fetch";
12
+ /** Thrown on a non-2xx response or a GraphQL `errors` payload. Never carries bodies or tokens. */
13
+ export class MagentoError extends Error {
14
+ operation;
15
+ status;
16
+ graphqlErrors;
17
+ constructor(operation, status,
18
+ /** How many GraphQL errors the response carried (0 for an HTTP failure). */
19
+ graphqlErrors = 0) {
20
+ super(graphqlErrors > 0
21
+ ? `magento ${operation} returned ${graphqlErrors} GraphQL error(s)`
22
+ : `magento ${operation} failed with HTTP ${status}`);
23
+ this.operation = operation;
24
+ this.status = status;
25
+ this.graphqlErrors = graphqlErrors;
26
+ this.name = "MagentoError";
27
+ }
28
+ }
29
+ export function createMagentoClient(config, options = {}) {
30
+ const instrumented = createInstrumentedFetch({ provider: "magento", fetch: options.fetch });
31
+ const origin = new URL(config.baseUrl).origin;
32
+ /**
33
+ * A request to a path on the store, e.g. `/rest/default/V1/carts/mine` or
34
+ * `/customer/section/load`, returning the raw response. Paths only: the
35
+ * store's credentials never go to another origin.
36
+ */
37
+ async function request(path, init) {
38
+ const { operation, authenticated = true, ...rest } = init;
39
+ const url = new URL(path, origin);
40
+ if (url.origin !== origin) {
41
+ throw new Error(`magento ${operation}: only paths on the configured store are allowed`);
42
+ }
43
+ const headers = new Headers(rest.headers);
44
+ if (authenticated && config.apiKey && !headers.has("authorization")) {
45
+ headers.set("authorization", `Bearer ${config.apiKey}`);
46
+ }
47
+ if (config.originHeader)
48
+ headers.set("x-origin-header", config.originHeader);
49
+ return instrumented(url, { ...rest, headers, operation });
50
+ }
51
+ return {
52
+ request,
53
+ /** A REST call (`/rest/<store>/V1/...`) returning its parsed JSON body. */
54
+ async rest(path, init) {
55
+ const headers = new Headers(init.headers);
56
+ if (init.body !== undefined && !headers.has("content-type")) {
57
+ headers.set("content-type", "application/json");
58
+ }
59
+ const response = await request(path, { ...init, headers });
60
+ if (!response.ok)
61
+ throw new MagentoError(init.operation, response.status);
62
+ if (response.status === 204)
63
+ return undefined;
64
+ return (await response.json());
65
+ },
66
+ /**
67
+ * A GraphQL operation on `/graphql`. `operationName` is sent with the
68
+ * request and is the operation label. `headers` adds per-call headers,
69
+ * such as `Store` or a customer `Authorization` token. Storefront
70
+ * GraphQL is public, so the `apiKey` is sent only with
71
+ * `authenticated: true`, and never over a customer token.
72
+ */
73
+ async graphql(query, variables, init) {
74
+ const { operationName, ...rest } = init;
75
+ const headers = new Headers(rest.headers);
76
+ headers.set("content-type", "application/json");
77
+ const response = await request("/graphql", {
78
+ operation: operationName,
79
+ authenticated: rest.authenticated ?? false,
80
+ method: "POST",
81
+ headers,
82
+ body: JSON.stringify({ query, variables, operationName }),
83
+ });
84
+ if (!response.ok)
85
+ throw new MagentoError(operationName, response.status);
86
+ const body = (await response.json());
87
+ if (body.errors?.length) {
88
+ throw new MagentoError(operationName, response.status, body.errors.length);
89
+ }
90
+ if (body.data === undefined)
91
+ throw new MagentoError(operationName, response.status);
92
+ return body.data;
93
+ },
94
+ };
95
+ }
96
+ //# sourceMappingURL=magentoClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magentoClient.js","sourceRoot":"","sources":["../src/magentoClient.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,44 +1,36 @@
1
1
  {
2
2
  "name": "@decocms/apps-magento",
3
- "version": "8.1.0-next.0",
3
+ "version": "8.1.0-next.2",
4
4
  "type": "module",
5
- "description": "Deco commerce app: Magento integration",
5
+ "description": "Thin Magento (Adobe Commerce) API client for Deco sites",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/decocms/blocks.git",
9
9
  "directory": "packages/apps-magento"
10
10
  },
11
- "main": "./src/index.ts",
11
+ "files": [
12
+ "dist",
13
+ "src",
14
+ "!src/**/*.test.ts",
15
+ "!src/**/__tests__"
16
+ ],
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
12
19
  "exports": {
13
- ".": "./src/index.ts",
14
- "./client": "./src/client.ts",
15
- "./types": "./src/types.ts",
16
- "./middleware": "./src/middleware.ts",
17
- "./loaders/*": "./src/loaders/*.ts",
18
- "./actions/*": "./src/actions/*.ts",
19
- "./utils/*": "./src/utils/*.ts",
20
- "./hooks/*": "./src/hooks/*.ts"
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "source": "./src/index.ts",
23
+ "default": "./dist/index.js"
24
+ }
21
25
  },
22
26
  "scripts": {
23
- "build": "tsc",
27
+ "build": "node ../../scripts/tsc.mjs --clean dist -p tsconfig.build.json",
28
+ "prepack": "npm run build",
24
29
  "test": "vitest run --root ../.. packages/apps-magento/",
25
- "typecheck": "tsc --noEmit",
26
- "lint:unused": "knip"
30
+ "typecheck": "node ../../scripts/tsc.mjs -p tsconfig.json"
27
31
  },
28
32
  "dependencies": {
29
- "@decocms/blocks": "8.1.0-next.0",
30
- "@decocms/apps-commerce": "8.1.0-next.0",
31
- "@decocms/tanstack": "8.1.0-next.0"
32
- },
33
- "peerDependencies": {
34
- "react": "^19.0.0",
35
- "react-dom": "^19.0.0"
36
- },
37
- "devDependencies": {
38
- "@types/react": "^19.0.0",
39
- "@types/react-dom": "^19.0.0",
40
- "knip": "^5.86.0",
41
- "typescript": "^5.9.0"
33
+ "@decocms/blocks": "8.1.0-next.2"
42
34
  },
43
35
  "publishConfig": {
44
36
  "registry": "https://registry.npmjs.org",
package/src/index.ts CHANGED
@@ -1,16 +1,4 @@
1
- // v8: the thin Magento client (see /next/upstream-clients).
2
-
3
- // v7 (kept for v7 consumers until the v7 modules are dropped)
4
- /**
5
- * Magento app entry point for @decocms/apps.
6
- * Re-exports client config + initializer.
7
- *
8
- * For actions/loaders/utils, use sub-path imports:
9
- * import { features } from "@decocms/apps/magento/loaders/features"
10
- * import { cart } from "@decocms/apps/magento/loaders/cart"
11
- * import { magentoFetch } from "@decocms/apps/magento/client"
12
- */
13
- export * from "./client";
1
+ // `@decocms/apps-magento`: the thin Magento client (see /next/upstream-clients).
14
2
  export {
15
3
  createMagentoClient,
16
4
  type MagentoClient,
@@ -18,19 +6,4 @@ export {
18
6
  type MagentoClientOptions,
19
7
  MagentoError,
20
8
  type MagentoRequestInit,
21
- } from "./magentoClient";
22
- export type { MagentoCart } from "./types";
23
- export {
24
- clearFetchCache,
25
- type FetchCacheOptions,
26
- getFetchCacheStats,
27
- magentoCachedFetch,
28
- } from "./utils/fetchCache";
29
- // Observability wiring — sites call `setMagentoFetch(createMagentoFetch())` at
30
- // boot to route every Magento egress call through the instrumented fetch
31
- // (upstream latency/status), and use `magentoCachedFetch` for cacheable GETs
32
- // (SWR hit/miss → `deco.cache.requests{layer="swr",profile="magento"}`).
33
- export {
34
- type CreateMagentoFetchOptions,
35
- createMagentoFetch,
36
- } from "./utils/instrumentedFetch";
9
+ } from "./magentoClient.ts";
package/src/README.md DELETED
@@ -1,65 +0,0 @@
1
- # Magento app — initial scaffold
2
-
3
- This folder ports the Magento integration from `deco-cx/apps/magento`
4
- (Fresh/Deno) to `@decocms/apps/magento` (TanStack Start/Node), following
5
- the same shape as the existing `vtex/` and `shopify/` packages.
6
-
7
- ## Status
8
-
9
- **Initial scaffold** — covers the configure/client surface and 2 reference
10
- loaders (`features`, `cart`) so that downstream sites can wire magento at
11
- all. The remaining 20+ loaders/actions exist as production-grade code in
12
- the original deco-cx/apps repo and need adaptation passes (Deno → Node,
13
- ctx-based to client-based state access, cookie helpers from
14
- `@decocms/start/sdk/cookie`).
15
-
16
- A real-world consumer (a production Magento storefront) is wiring magento
17
- in-site today using a thin adapter that wraps the legacy `magento/mod.ts`
18
- shape. Their adapter is the migration target — once this package covers
19
- the surface area they need, the in-site copy goes away.
20
-
21
- ## What's here
22
-
23
- - `client.ts` — `configureMagento({ baseUrl, apiKey, storeId, ... })` +
24
- `getMagentoConfig()` global accessor. Mirrors the `configureVtex`
25
- pattern.
26
- - `types.ts` — request/response shapes shared between loaders.
27
- - `loaders/features.ts` — returns the feature flags block. The simplest
28
- loader, used as a smoke test.
29
- - `loaders/cart.ts` — fetches the customer's active cart by cookie.
30
- Pulls cookies via `@decocms/start/sdk/cookie`.
31
- - `middleware.ts` — passthrough today; real-world cart-id reconciliation
32
- lives in the consumer site for now.
33
- - `index.ts` — re-export entry.
34
-
35
- ## Pending port (PR follow-ups)
36
-
37
- | Path | Original location |
38
- |---|---|
39
- | `loaders/product/{detailsPage,detailsPageGQL,listingPage,list,relatedProducts}.ts` | `deco-cx/apps/magento/loaders/product/*` |
40
- | `loaders/{proxy,user,wishlist}.ts` | `deco-cx/apps/magento/loaders/*` |
41
- | `loaders/routes/getRouteType.ts` | idem |
42
- | `actions/cart/{addCoupon,addItem,removeCoupon,removeItem,setSimulation,simulation,updateItem}.ts` | `deco-cx/apps/magento/actions/cart/*` |
43
- | `actions/newsletter/subscribe.ts` | idem |
44
- | `actions/product/stockAlert.ts` | idem |
45
- | `actions/wishlist/{addItem,removeItem}.ts` | idem |
46
- | `utils/{clientGraphql,client,transform,cache,graphql,cart}.ts` | `deco-cx/apps/magento/utils/*` |
47
- | `hooks/{useCart,useUser,useWishlist}.ts` | `deco-cx/apps/magento/hooks/*` (refactor to react-query, like `vtex/hooks/`) |
48
- | `inline-loaders/*` | new, follow `vtex/inline-loaders/` shape |
49
-
50
- **Site-specific extensions (Livelo, Amasty)** in the deco-cx/apps repo
51
- should stay out of this package — they belong in consumer sites via the
52
- `ExtensionOf` pattern. Preserving that pattern in the generic loaders
53
- above is a hard requirement of this port.
54
-
55
- ## Why a stub now
56
-
57
- A production Magento storefront's migration hit a HIGH parity finding:
58
-
59
- ```
60
- invoke(magento/loaders/features) failed: handler not found
61
- ```
62
-
63
- …because no `@decocms/apps/magento/*` resolver existed. The site is
64
- working around it locally; this PR begins the upstream fix so future
65
- magento sites don't have to repeat the in-site adapter.
@@ -1,139 +0,0 @@
1
- /**
2
- * Tests for the cart loader.
3
- *
4
- * Parity goals against deco-cx/apps/magento/loaders/cart.ts (Fresh/Deno,
5
- * prod):
6
- *
7
- * - Reads `dataservices_cart_id` cookie when caller doesn't supply
8
- * `cartId` in props (matches `getCartCookie(req.headers)` in prod).
9
- * - Honors props.cartId override (matches `_cartId ?? getCartCookie()`).
10
- * - Hits /rest/:site/V1/carts/:cartId — the /rest/ prefix is what the
11
- * Magento REST API exposes; the legacy clientAdmin's typed key was
12
- * "GET /rest/:site/V1/carts/:cartId".
13
- * - Encodes site + cartId so a malicious cookie can't escape the path
14
- * segment and reach another admin endpoint with the Bearer token.
15
- * - Returns null when no cookie (anonymous visitor — matches prod's
16
- * `if (!cartId) return null`).
17
- * - Returns null on 404 (expired cookie — prod hits this via the same
18
- * branch but throws on other non-2xx; we surface a plain Error).
19
- *
20
- * NOT covered by this initial port (and intentionally not tested here):
21
- * - Pre-cart `GET /rest/:site/V1/carts/mine` warm-up for logged-in
22
- * users (the original does it inside a try/catch and discards the
23
- * result — it's a cache primer, not a correctness invariant).
24
- * - Parallel /totals fetch + image-pipeline transform — those land in
25
- * follow-up PRs that port utils/cart.ts + utils/cache.ts.
26
- */
27
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
28
- import { configureMagento } from "../client";
29
- import cart from "../loaders/cart";
30
-
31
- function mockResponse(body: unknown, status = 200): Response {
32
- return new Response(JSON.stringify(body), {
33
- status,
34
- headers: { "content-type": "application/json" },
35
- });
36
- }
37
-
38
- function requestWithCookie(cookie: string): Request {
39
- const r = new Request("http://localhost/");
40
- // Headers in a freshly-constructed Request enter "request" guard mode
41
- // which silently drops `cookie`. Build a Headers manually then attach.
42
- const headers = new Headers();
43
- headers.set("cookie", cookie);
44
- Object.defineProperty(r, "headers", { value: headers, configurable: true });
45
- return r;
46
- }
47
-
48
- describe("cart loader", () => {
49
- let fetchSpy: ReturnType<typeof vi.spyOn>;
50
-
51
- beforeEach(() => {
52
- configureMagento({
53
- baseUrl: "https://loja.example.com/",
54
- apiKey: "secret",
55
- storeId: 1,
56
- site: "example",
57
- });
58
- fetchSpy = vi.spyOn(globalThis, "fetch");
59
- });
60
-
61
- afterEach(() => {
62
- vi.restoreAllMocks();
63
- });
64
-
65
- it("returns null when no cart cookie is present", async () => {
66
- const req = new Request("http://localhost/");
67
- const result = await cart(undefined, req);
68
- expect(result).toBeNull();
69
- expect(fetchSpy).not.toHaveBeenCalled();
70
- });
71
-
72
- it("reads cartId from the dataservices_cart_id cookie (JSON-encoded)", async () => {
73
- fetchSpy.mockResolvedValue(mockResponse({ id: "abc123", items: [] }));
74
- const req = requestWithCookie('dataservices_cart_id="abc123"');
75
- await cart(undefined, req);
76
- const [target] = fetchSpy.mock.calls[0] as [URL];
77
- expect(target.toString()).toBe("https://loja.example.com/rest/example/V1/carts/abc123");
78
- });
79
-
80
- it("reads cartId from the cookie when it's not JSON-quoted", async () => {
81
- fetchSpy.mockResolvedValue(mockResponse({ id: "raw-id", items: [] }));
82
- const req = requestWithCookie("dataservices_cart_id=raw-id");
83
- await cart(undefined, req);
84
- const [target] = fetchSpy.mock.calls[0] as [URL];
85
- expect(target.toString()).toBe("https://loja.example.com/rest/example/V1/carts/raw-id");
86
- });
87
-
88
- it("honors props.cartId override (cookie ignored)", async () => {
89
- fetchSpy.mockResolvedValue(mockResponse({ id: "from-props", items: [] }));
90
- const req = requestWithCookie('dataservices_cart_id="from-cookie"');
91
- await cart({ cartId: "from-props" }, req);
92
- const [target] = fetchSpy.mock.calls[0] as [URL];
93
- expect(target.toString()).toContain("/rest/example/V1/carts/from-props");
94
- });
95
-
96
- it("URL-encodes the cartId to prevent path injection", async () => {
97
- // A malicious cookie value tries to break out of the path segment
98
- // and reach a different admin endpoint with the Bearer attached.
99
- fetchSpy.mockResolvedValue(mockResponse(null, 404));
100
- const req = requestWithCookie('dataservices_cart_id="../admin/leak?secret=1"');
101
- await cart(undefined, req);
102
- const [target] = fetchSpy.mock.calls[0] as [URL];
103
- // Encoded: %2F (slash), %3F (?), %3D (=). The trailing
104
- // "/admin/leak?secret=1" stays inside the path segment.
105
- expect(target.pathname).toMatch(
106
- /^\/rest\/example\/V1\/carts\/(\.\.|%2E%2E)%2Fadmin%2Fleak%3Fsecret%3D1$/i,
107
- );
108
- });
109
-
110
- it("returns null on 404 (expired cart cookie)", async () => {
111
- fetchSpy.mockResolvedValue(mockResponse(null, 404));
112
- const req = requestWithCookie('dataservices_cart_id="stale"');
113
- const result = await cart(undefined, req);
114
- expect(result).toBeNull();
115
- });
116
-
117
- it("throws on non-404 errors", async () => {
118
- fetchSpy.mockResolvedValue(mockResponse(null, 500));
119
- const req = requestWithCookie('dataservices_cart_id="abc"');
120
- await expect(cart(undefined, req)).rejects.toThrow(/cart loader: 500/);
121
- });
122
-
123
- it("attaches Bearer + x-origin-header (same-origin) on the cart fetch", async () => {
124
- configureMagento({
125
- baseUrl: "https://loja.example.com/",
126
- apiKey: "secret",
127
- storeId: 1,
128
- site: "example",
129
- originHeader: "origin-secret",
130
- });
131
- fetchSpy.mockResolvedValue(mockResponse({ id: "abc", items: [] }));
132
- const req = requestWithCookie('dataservices_cart_id="abc"');
133
- await cart(undefined, req);
134
- const [, init] = fetchSpy.mock.calls[0] as [URL, RequestInit];
135
- const headers = init.headers as Headers;
136
- expect(headers.get("authorization")).toBe("Bearer secret");
137
- expect(headers.get("x-origin-header")).toBe("origin-secret");
138
- });
139
- });