@thorprovider/adapters 4.3.0 → 5.0.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/AGENTS.md +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +53 -631
- package/dist/index.d.mts +52 -161
- package/dist/index.d.ts +52 -161
- package/dist/index.js +52 -395
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +49 -395
- package/dist/index.mjs.map +1 -1
- package/docs/RETRY_SOLUTION_SUMMARY.md +1 -1
- package/docs/authentication.md +0 -26
- package/docs/capabilities-system.md +66 -36
- package/docs/getting-started.md +1 -1
- package/docs/providers-overview.md +16 -13
- package/docs/update-documentation.md +10 -8
- package/package.json +6 -5
- package/quick-reference.md +0 -33
- package/src/index.ts +4 -0
- package/src/providers/medusa/auth.test.ts +94 -92
- package/src/providers/medusa/auth.ts +72 -123
- package/src/providers/medusa/index.test.ts +392 -0
- package/src/providers/medusa/index.ts +1 -21
- package/src/providers/medusa/live-smoke.test.ts +412 -0
- package/src/providers/shopify/auth.ts +0 -6
- package/vitest.live.config.ts +18 -0
|
@@ -17,7 +17,7 @@ Errores frecuentes `Failed to fetch products: fetch failed` en el V0 starter al
|
|
|
17
17
|
|
|
18
18
|
### 1. Módulo de Retry Logic (`fetch-utils.ts`)
|
|
19
19
|
- **Ubicación**: `packages/adapters/src/core/fetch-utils.ts`
|
|
20
|
-
- **Importación**: `import { fetchWithRetry } from '
|
|
20
|
+
- **Importación**: utilidad interna — no está re-exportada desde `@thorprovider/adapters`. Úsala dentro del paquete como `import { fetchWithRetry } from '../core/fetch-utils'`.
|
|
21
21
|
- **Funcionalidad**:
|
|
22
22
|
- Reintentos automáticos con exponential backoff
|
|
23
23
|
- Timeout configurable (10s por defecto)
|
package/docs/authentication.md
CHANGED
|
@@ -186,33 +186,7 @@ await commerce.auth.deleteAddress(address.id);
|
|
|
186
186
|
| Login works but profile fails | Ensure `commerce.auth.getCurrentCustomer()` is called after login |
|
|
187
187
|
| Infinite login loop | Check cookie path and domain settings on the backend |
|
|
188
188
|
|
|
189
|
-
## Multi-Tenant: Linking Customer to Sales Channel
|
|
190
|
-
|
|
191
|
-
After login or registration, call `linkCustomerToChannel()` to associate the customer with the storefront's sales channel. This is **required** for the customer to appear in the admin panel of the correct tenant and is idempotent.
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
if (!commerce.auth) throw new Error('Auth not available');
|
|
195
|
-
|
|
196
|
-
const { customer } = await commerce.auth.login({
|
|
197
|
-
email: 'user@example.com',
|
|
198
|
-
password: 'SecurePassword123!',
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Link the customer to the sales channel resolved from the publishable key
|
|
202
|
-
const { linked, reason } = await commerce.auth.linkCustomerToChannel();
|
|
203
|
-
// linked === true → new link created
|
|
204
|
-
// linked === true, reason === 'already_linked' → idempotent — no action needed
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
**Throws:**
|
|
208
|
-
- `ProviderAPIError('NOT_AUTHENTICATED', 401)` — login must succeed first
|
|
209
|
-
- `ProviderAPIError('CHANNEL_RESOLUTION_FAILED', 400)` — publishable key doesn't map to a channel
|
|
210
|
-
- `ProviderAPIError('CONFIGURATION_ERROR')` — provider instantiated without `baseUrl`/`publishableKey`
|
|
211
|
-
|
|
212
|
-
**Provider support:** Medusa only. Shopify and other stubs return `{ linked: false, reason: 'not_applicable' }`.
|
|
213
|
-
|
|
214
189
|
## Next Steps
|
|
215
190
|
|
|
216
191
|
- [Profile Management](profile-management.md)
|
|
217
192
|
- [Services Overview](services-overview.md)
|
|
218
|
-
- [Multi-Tenant Channel Scoping](multi-tenant-channel-scoping.md)
|
|
@@ -6,7 +6,7 @@ tags: [architecture, capabilities, backend, detection]
|
|
|
6
6
|
related:
|
|
7
7
|
- providers-overview.md
|
|
8
8
|
- services-overview.md
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-05-23
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Capabilities System
|
|
@@ -37,27 +37,52 @@ if (commerce.capabilities.updateEmail) {
|
|
|
37
37
|
| `changePasswordViaEmail` | `boolean` | Supports `requestPasswordReset` method. |
|
|
38
38
|
| `deleteAccount` | `boolean` | Supports `deleteAccount` method. |
|
|
39
39
|
| `uploadAvatar` | `boolean` | Supports native file upload for avatars. |
|
|
40
|
+
| `avatarViaMetadata` | `boolean` | Can store avatar URL in customer metadata/attributes. |
|
|
41
|
+
| `customAttributes` | `boolean` | Supports custom customer attributes/metadata. |
|
|
40
42
|
|
|
41
|
-
###
|
|
43
|
+
### Order Management
|
|
42
44
|
|
|
43
45
|
| Capability | Type | Description |
|
|
44
46
|
|------------|------|-------------|
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `cartNotes` | `boolean` | Supports order/cart notes. |
|
|
48
|
-
| `cartCustomAttributes` | `boolean` | Supports metadata/attributes at cart level. |
|
|
47
|
+
| `fetchOrders` | `boolean` | Can fetch customer order history. |
|
|
48
|
+
| `cancelOrder` | `boolean` | Can cancel orders before fulfillment. |
|
|
49
49
|
|
|
50
|
-
###
|
|
50
|
+
### Address Management
|
|
51
51
|
|
|
52
52
|
| Capability | Type | Description |
|
|
53
53
|
|------------|------|-------------|
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `productInventory` | `boolean` | Real-time inventory tracking available. |
|
|
57
|
-
| `productDigital` | `boolean` | Supports digital/downloadable products. |
|
|
58
|
-
| `productReviews` | `boolean` | Native review system support. |
|
|
54
|
+
| `manageAddresses` | `boolean` | Can add/update/delete shipping addresses. |
|
|
55
|
+
| `defaultAddress` | `boolean` | Can set a default shipping address. |
|
|
59
56
|
|
|
60
|
-
|
|
57
|
+
### Cart & Checkout
|
|
58
|
+
|
|
59
|
+
| Capability | Type | Description |
|
|
60
|
+
|------------|------|-------------|
|
|
61
|
+
| `applyDiscounts` | `boolean` | Can apply discount codes to cart. |
|
|
62
|
+
| `shippingCalculation` | `boolean` | Can calculate shipping rates before checkout. |
|
|
63
|
+
| `guestCheckout` | `boolean` | Supports checkout without an account. |
|
|
64
|
+
| `savedPaymentMethods` | `boolean` | Can save payment methods for future use. |
|
|
65
|
+
| `listPaymentProviders` | `boolean` | Can list available payment providers dynamically. |
|
|
66
|
+
| `regionSpecificPayments` | `boolean` | Payment providers vary by region. |
|
|
67
|
+
|
|
68
|
+
### Discovery & Search
|
|
69
|
+
|
|
70
|
+
| Capability | Type | Description |
|
|
71
|
+
|------------|------|-------------|
|
|
72
|
+
| `advancedSearch` | `boolean` | Supports full-text / filtered product search. |
|
|
73
|
+
| `facetedFilters` | `boolean` | Supports faceted filtering by attributes. |
|
|
74
|
+
|
|
75
|
+
### Social & Loyalty
|
|
76
|
+
|
|
77
|
+
| Capability | Type | Description |
|
|
78
|
+
|------------|------|-------------|
|
|
79
|
+
| `productReviews` | `boolean` | Native product reviews/ratings system. |
|
|
80
|
+
| `wishlist` | `boolean` | Native wishlist/favorites feature. |
|
|
81
|
+
| `loyaltyProgram` | `boolean` | Native loyalty/rewards program. |
|
|
82
|
+
|
|
83
|
+
## Type Definition (source of truth)
|
|
84
|
+
|
|
85
|
+
> Canonical source: `packages/types/src/commerce-provider.ts` — `BackendCapabilities`
|
|
61
86
|
|
|
62
87
|
```typescript
|
|
63
88
|
export interface BackendCapabilities {
|
|
@@ -68,28 +93,33 @@ export interface BackendCapabilities {
|
|
|
68
93
|
changePasswordViaEmail: boolean;
|
|
69
94
|
deleteAccount: boolean;
|
|
70
95
|
uploadAvatar: boolean;
|
|
71
|
-
|
|
72
|
-
// Avatar Extensions
|
|
73
96
|
avatarViaMetadata: boolean;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
customAttributes: boolean;
|
|
98
|
+
|
|
99
|
+
// Orders
|
|
100
|
+
fetchOrders: boolean;
|
|
101
|
+
cancelOrder: boolean;
|
|
102
|
+
|
|
103
|
+
// Addresses
|
|
104
|
+
manageAddresses: boolean;
|
|
105
|
+
defaultAddress: boolean;
|
|
106
|
+
|
|
107
|
+
// Cart / Checkout
|
|
108
|
+
applyDiscounts: boolean;
|
|
109
|
+
shippingCalculation: boolean;
|
|
110
|
+
guestCheckout: boolean;
|
|
111
|
+
savedPaymentMethods: boolean;
|
|
112
|
+
listPaymentProviders: boolean;
|
|
113
|
+
regionSpecificPayments: boolean;
|
|
114
|
+
|
|
115
|
+
// Discovery
|
|
116
|
+
advancedSearch: boolean;
|
|
117
|
+
facetedFilters: boolean;
|
|
118
|
+
|
|
119
|
+
// Social / Loyalty
|
|
86
120
|
productReviews: boolean;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
authJWT: boolean;
|
|
90
|
-
authSession: boolean;
|
|
91
|
-
authOAuth: boolean;
|
|
92
|
-
authPasswordless: boolean;
|
|
121
|
+
wishlist: boolean;
|
|
122
|
+
loyaltyProgram: boolean;
|
|
93
123
|
}
|
|
94
124
|
```
|
|
95
125
|
|
|
@@ -111,15 +141,15 @@ return (
|
|
|
111
141
|
async function handleAvatarUpdate(file: File) {
|
|
112
142
|
if (caps.uploadAvatar) {
|
|
113
143
|
return await commerce.auth.uploadAvatar(file);
|
|
114
|
-
}
|
|
115
|
-
|
|
144
|
+
}
|
|
145
|
+
|
|
116
146
|
if (caps.avatarViaMetadata) {
|
|
117
147
|
const url = await externalUpload(file);
|
|
118
148
|
return await commerce.auth.updateCustomer({
|
|
119
149
|
metadata: { avatar_url: url }
|
|
120
150
|
});
|
|
121
151
|
}
|
|
122
|
-
|
|
152
|
+
|
|
123
153
|
throw new Error('Avatar updates not supported on this backend');
|
|
124
154
|
}
|
|
125
155
|
```
|
package/docs/getting-started.md
CHANGED
|
@@ -17,7 +17,7 @@ Quick setup guide for integrating Thor adapters into your project. Start with `m
|
|
|
17
17
|
## Prerequisites
|
|
18
18
|
|
|
19
19
|
- Node.js 18+
|
|
20
|
-
- Yarn 4+ (
|
|
20
|
+
- Yarn 4+ (this monorepo currently uses the `node-modules` linker)
|
|
21
21
|
- Optional: access to a Medusa.js v2 backend
|
|
22
22
|
- Optional: Medusa Publishable API Key
|
|
23
23
|
|
|
@@ -30,20 +30,26 @@ Thor Adapters eliminate these problems by wrapping the data layer in a consisten
|
|
|
30
30
|
|
|
31
31
|
## Current Support Matrix
|
|
32
32
|
|
|
33
|
+
The `createProvider` factory currently accepts these provider identifiers:
|
|
34
|
+
|
|
35
|
+
- `mock`
|
|
36
|
+
- `medusa`
|
|
37
|
+
- `stelorder`
|
|
38
|
+
- `shopify`
|
|
39
|
+
- `bigcommerce`
|
|
40
|
+
- `woocommerce`
|
|
41
|
+
- `spree`
|
|
42
|
+
- `magento`
|
|
43
|
+
|
|
44
|
+
The package docs explicitly describe these currently documented integrations:
|
|
45
|
+
|
|
33
46
|
### Mock
|
|
34
|
-
**Status:** ✅ Production Ready
|
|
35
47
|
Fixture-backed provider for local development, previews, design-mode storefront work, and backend-optional scaffolds.
|
|
36
48
|
|
|
37
|
-
### Medusa
|
|
38
|
-
|
|
39
|
-
Our primary integration, highly optimized for headless performance.
|
|
40
|
-
|
|
41
|
-
### Shopify
|
|
42
|
-
**Status:** 🚧 In Development (Phase 1)
|
|
43
|
-
Focusing on Storefront API integration for product discovery and cart.
|
|
49
|
+
### Medusa
|
|
50
|
+
Primary storefront integration used by the checked-in `@thorprovider/core` package.
|
|
44
51
|
|
|
45
52
|
### Stel Order
|
|
46
|
-
**Status:** ✅ ERP Adapter (v1)
|
|
47
53
|
Single-company ERP integration for catalog, categories, inventory visibility, customers (STEL clients), sales orders, and payment/delivery option listing.
|
|
48
54
|
|
|
49
55
|
**Scope & Limitations:**
|
|
@@ -54,9 +60,7 @@ Single-company ERP integration for catalog, categories, inventory visibility, cu
|
|
|
54
60
|
- **No variants:** STEL products are single-SKU; the adapter creates one synthetic variant per product
|
|
55
61
|
- **Client-side search:** STEL has no fulltext search; `getProducts({ query })` filters in-memory after fetch
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
**Status:** 🗓️ Planned
|
|
59
|
-
Targeting the REST API for hybrid headless setups.
|
|
63
|
+
For the other accepted provider identifiers, verify source-level capability coverage before treating them as production-ready.
|
|
60
64
|
|
|
61
65
|
## Conceptual Foundation
|
|
62
66
|
|
|
@@ -102,4 +106,3 @@ graph TD
|
|
|
102
106
|
|
|
103
107
|
- [Reference: Capabilities System](capabilities-system.md)
|
|
104
108
|
- [How-to: Getting Started](getting-started.md)
|
|
105
|
-
- [Workflow: Add New Provider](../../../docs/workflows/legacy/add-provider.md)
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
# Update Documentation
|
|
2
|
-
|
|
3
1
|
---
|
|
2
|
+
title: Update Documentation
|
|
3
|
+
purpose: Guidelines for updating @thorprovider/adapters documentation correctly
|
|
4
4
|
type: how-to
|
|
5
5
|
tags: [documentation, maintenance, adapters, standards, copilot]
|
|
6
6
|
related:
|
|
7
7
|
- ../../../docs/templates/README.md
|
|
8
|
-
- ../../../docs/
|
|
9
|
-
updated: 2026-
|
|
8
|
+
- ../../../docs/standards/documentation.md
|
|
9
|
+
updated: 2026-05-23
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
# Update Documentation
|
|
13
|
+
|
|
12
14
|
**Specific guidelines for updating @thorprovider/adapters documentation.**
|
|
13
15
|
|
|
14
|
-
Global guidelines: [docs/
|
|
16
|
+
Global guidelines: [docs/standards/documentation.md](../../../docs/standards/documentation.md)
|
|
15
17
|
|
|
16
18
|
## Prerequisites
|
|
17
19
|
|
|
18
|
-
- Read [global guidelines](../../../docs/
|
|
20
|
+
- Read [global guidelines](../../../docs/standards/documentation.md) for doc types and templates
|
|
19
21
|
- Understand the [Providers System](providers-overview.md) and [Capabilities System](capabilities-system.md)
|
|
20
|
-
- Ensure understanding of the [
|
|
22
|
+
- Ensure understanding of the [Architecture Overview](../../../docs/architecture/overview.md) (Strategy Pattern)
|
|
21
23
|
|
|
22
24
|
## Step-by-Step Process for Adapters
|
|
23
25
|
|
|
@@ -109,5 +111,5 @@ async function performTask() {
|
|
|
109
111
|
|
|
110
112
|
## Next Steps
|
|
111
113
|
|
|
112
|
-
- [global
|
|
114
|
+
- [global documentation standards](../../../docs/standards/documentation.md)
|
|
113
115
|
- [How-to: Getting Started](getting-started.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thorprovider/adapters",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Framework-agnostic commerce adapters for Thor Commerce ecosystem - Multi-platform support with unified API",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
"dev": "tsup --watch",
|
|
23
23
|
"type-check": "tsc --noEmit",
|
|
24
24
|
"test": "vitest run --config vitest.config.ts",
|
|
25
|
-
"test:live": "vitest run --config vitest.config.ts src/providers/stelorder/live-smoke.test.ts",
|
|
25
|
+
"test:live": "vitest run --config vitest.live.config.ts src/providers/stelorder/live-smoke.test.ts",
|
|
26
|
+
"test:live:medusa": "vitest run --config vitest.live.config.ts src/providers/medusa/live-smoke.test.ts",
|
|
26
27
|
"generate:agent-index": "node ../../scripts/generate-package-agents-index.cjs packages/adapters @thorprovider/adapters"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@medusajs/js-sdk": "2.
|
|
30
|
-
"@medusajs/types": "2.
|
|
31
|
-
"@thorprovider/types": "^3.
|
|
30
|
+
"@medusajs/js-sdk": "2.16.0",
|
|
31
|
+
"@medusajs/types": "2.16.0",
|
|
32
|
+
"@thorprovider/types": "^3.14.4",
|
|
32
33
|
"bcryptjs": "^3.0.3",
|
|
33
34
|
"jose": "^6.2.2"
|
|
34
35
|
},
|
package/quick-reference.md
CHANGED
|
@@ -52,39 +52,6 @@ Framework-agnostic business logic SDK for e-commerce operations. Provides unifie
|
|
|
52
52
|
- **Collections**: Product collections/categories
|
|
53
53
|
- **Customer**: Authentication, profile management
|
|
54
54
|
- **Orders**: Order history, details, tracking
|
|
55
|
-
- **Auth — Multi-Tenant**: `linkCustomerToChannel()` — links authenticated customer to sales channel (Medusa only)
|
|
56
|
-
|
|
57
|
-
### Admin Client (Medusa only)
|
|
58
|
-
`provider.admin` — available when `adminConfig.apiKey` is set. Exposes 15 methods for the `/admin/thor/` endpoints:
|
|
59
|
-
```typescript
|
|
60
|
-
const commerce = createProvider({ provider: 'medusa', config: { ..., adminConfig: { apiKey: '...' } } })
|
|
61
|
-
if (!commerce.admin) throw new Error('Admin not configured')
|
|
62
|
-
|
|
63
|
-
// Sales Channel — Customers / API Keys / Categories
|
|
64
|
-
await commerce.admin.getChannelCustomers(salesChannelId, { limit, offset })
|
|
65
|
-
await commerce.admin.getChannelApiKeys(salesChannelId)
|
|
66
|
-
await commerce.admin.getChannelCategories(salesChannelId, { limit, offset, include_descendants })
|
|
67
|
-
|
|
68
|
-
// Category ↔ Sales Channel assignments
|
|
69
|
-
await commerce.admin.getCategorySalesChannels(categoryId)
|
|
70
|
-
await commerce.admin.assignCategoryToChannels(categoryId, { sales_channel_ids: [...] })
|
|
71
|
-
await commerce.admin.unassignCategoryFromChannels(categoryId, { sales_channel_ids: [...] })
|
|
72
|
-
|
|
73
|
-
// Collection ↔ Sales Channel assignments
|
|
74
|
-
await commerce.admin.getCollectionSalesChannels(collectionId)
|
|
75
|
-
await commerce.admin.assignCollectionToChannels(collectionId, { sales_channel_ids: [...] })
|
|
76
|
-
await commerce.admin.unassignCollectionFromChannels(collectionId, { sales_channel_ids: [...] })
|
|
77
|
-
|
|
78
|
-
// Storefront Config (visual theme, SEO defaults)
|
|
79
|
-
await commerce.admin.getStorefrontConfig(salesChannelId)
|
|
80
|
-
await commerce.admin.updateStorefrontConfig(salesChannelId, { theme_accent_color, logo_url, currency_code, seo_defaults })
|
|
81
|
-
|
|
82
|
-
// Site Designer Config (page builder config with history)
|
|
83
|
-
await commerce.admin.getSiteConfig(salesChannelId)
|
|
84
|
-
await commerce.admin.updateSiteConfig(salesChannelId, { config: { ... } })
|
|
85
|
-
await commerce.admin.getSiteConfigHistory(salesChannelId, { limit, offset })
|
|
86
|
-
await commerce.admin.restoreSiteConfig(salesChannelId, version)
|
|
87
|
-
```
|
|
88
55
|
|
|
89
56
|
### Provider Architecture
|
|
90
57
|
```
|
package/src/index.ts
CHANGED
|
@@ -59,9 +59,13 @@ export {
|
|
|
59
59
|
ConfigurationError,
|
|
60
60
|
ResourceNotFoundError,
|
|
61
61
|
ProviderAPIError,
|
|
62
|
+
extractErrorMessage,
|
|
62
63
|
createLogger,
|
|
64
|
+
fetchWithRetry,
|
|
65
|
+
createFetchWithRetry,
|
|
63
66
|
type Logger,
|
|
64
67
|
type LogLevel,
|
|
68
|
+
type RetryConfig,
|
|
65
69
|
} from './core';
|
|
66
70
|
|
|
67
71
|
// ============================================
|
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Tests for MedusaAuthProvider
|
|
3
|
-
*
|
|
4
|
-
* Uses msw to intercept fetch, a minimal Medusa SDK stub, and a custom
|
|
5
|
-
* storage stub to inject a pre-set JWT token.
|
|
2
|
+
* @fileoverview Tests for MedusaAuthProvider
|
|
6
3
|
*/
|
|
7
4
|
|
|
8
|
-
import { describe, expect, it } from 'vitest'
|
|
9
|
-
import
|
|
10
|
-
import { server } from '../stelorder/client.test-helpers'
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
6
|
+
import Medusa from '@medusajs/js-sdk'
|
|
11
7
|
import { MedusaAuthProvider } from './auth'
|
|
12
8
|
|
|
13
9
|
const BASE = 'https://medusa.example.com'
|
|
14
|
-
const PUB_KEY = 'pk_test_123'
|
|
15
|
-
const TOKEN = 'jwt-bearer-token'
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
// Minimal SDK stub — we only test the linkCustomerToChannel raw-fetch path,
|
|
19
|
-
// so the SDK itself does not need to do anything.
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
function makeSdkStub(): any {
|
|
11
|
+
function makeSdkStub() {
|
|
23
12
|
return {
|
|
24
|
-
auth: {
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
auth: {
|
|
14
|
+
login: vi.fn(),
|
|
15
|
+
logout: vi.fn(),
|
|
16
|
+
register: vi.fn(),
|
|
17
|
+
resetPassword: vi.fn(),
|
|
18
|
+
updateProvider: vi.fn(),
|
|
19
|
+
},
|
|
20
|
+
store: {
|
|
21
|
+
customer: {
|
|
22
|
+
retrieve: vi.fn(),
|
|
23
|
+
create: vi.fn(),
|
|
24
|
+
listAddress: vi.fn(),
|
|
25
|
+
createAddress: vi.fn(),
|
|
26
|
+
updateAddress: vi.fn(),
|
|
27
|
+
deleteAddress: vi.fn(),
|
|
28
|
+
update: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} as unknown as Medusa
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Custom storage stub — provides a pre-set token to the auth provider
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
34
|
function makeStorageWithToken(token: string | null) {
|
|
34
35
|
let stored: string | null = token
|
|
35
36
|
return {
|
|
36
|
-
getItem: (
|
|
37
|
+
getItem: () => Promise.resolve(stored),
|
|
37
38
|
setItem: (_key: string, value: string) => { stored = value; return Promise.resolve() },
|
|
38
|
-
removeItem: (
|
|
39
|
+
removeItem: () => { stored = null; return Promise.resolve() },
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function makeAuthConfig(token: string | null =
|
|
43
|
+
function makeAuthConfig(token: string | null = 'jwt-bearer-token') {
|
|
43
44
|
return {
|
|
44
45
|
method: 'jwt' as const,
|
|
45
46
|
storage: 'memory' as const,
|
|
@@ -47,91 +48,92 @@ function makeAuthConfig(token: string | null = TOKEN) {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function makeProvider(token: string | null =
|
|
51
|
-
return new MedusaAuthProvider(makeSdkStub(), makeAuthConfig(token), false
|
|
51
|
+
function makeProvider(token: string | null = 'jwt-bearer-token') {
|
|
52
|
+
return new MedusaAuthProvider(makeSdkStub(), makeAuthConfig(token), false)
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
it('POST /store/thor/customers/me/channels with Authorization header when token present', async () => {
|
|
60
|
-
server.use(
|
|
61
|
-
http.post(`${BASE}/store/thor/customers/me/channels`, ({ request }) => {
|
|
62
|
-
expect(request.headers.get('x-publishable-api-key')).toBe(PUB_KEY)
|
|
63
|
-
expect(request.headers.get('Authorization')).toBe(`Bearer ${TOKEN}`)
|
|
64
|
-
return HttpResponse.json({ linked: true })
|
|
65
|
-
}),
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
const result = await makeProvider().linkCustomerToChannel()
|
|
69
|
-
expect(result.linked).toBe(true)
|
|
70
|
-
})
|
|
55
|
+
describe('MedusaAuthProvider', () => {
|
|
56
|
+
it('stores jwt token and returns customer on login', async () => {
|
|
57
|
+
const sdk = makeSdkStub()
|
|
58
|
+
sdk.auth.login = vi.fn().mockResolvedValue('jwt-bearer-token')
|
|
59
|
+
sdk.store.customer.retrieve = vi.fn().mockResolvedValue({ customer: { id: 'cus_1', email: 'test@example.com' } })
|
|
71
60
|
|
|
72
|
-
|
|
73
|
-
server.use(
|
|
74
|
-
http.post(`${BASE}/store/thor/customers/me/channels`, ({ request }) => {
|
|
75
|
-
expect(request.headers.get('Authorization')).toBeNull()
|
|
76
|
-
expect(request.headers.get('x-publishable-api-key')).toBe(PUB_KEY)
|
|
77
|
-
return HttpResponse.json({ linked: true })
|
|
78
|
-
}),
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
const result = await makeProvider(null).linkCustomerToChannel()
|
|
82
|
-
expect(result.linked).toBe(true)
|
|
83
|
-
})
|
|
61
|
+
const provider = new MedusaAuthProvider(sdk, makeAuthConfig('jwt-bearer-token'), false)
|
|
84
62
|
|
|
85
|
-
|
|
86
|
-
server.use(
|
|
87
|
-
http.post(`${BASE}/store/thor/customers/me/channels`, () =>
|
|
88
|
-
HttpResponse.json({ linked: true, reason: 'already_linked' }),
|
|
89
|
-
),
|
|
90
|
-
)
|
|
63
|
+
const result = await provider.login({ email: 'test@example.com', password: 'secret' })
|
|
91
64
|
|
|
92
|
-
|
|
93
|
-
expect(result.
|
|
94
|
-
expect(
|
|
65
|
+
expect(result.token).toBe('jwt-bearer-token')
|
|
66
|
+
expect(result.customer?.email).toBe('test@example.com')
|
|
67
|
+
expect(sdk.auth.login).toHaveBeenCalledWith('customer', 'emailpass', {
|
|
68
|
+
email: 'test@example.com',
|
|
69
|
+
password: 'secret',
|
|
70
|
+
})
|
|
95
71
|
})
|
|
96
72
|
|
|
97
|
-
it('throws ProviderAPIError with
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
HttpResponse.json({ message: 'Unauthorized' }, { status: 401 }),
|
|
101
|
-
),
|
|
102
|
-
)
|
|
73
|
+
it('throws ProviderAPIError with login code on auth failure', async () => {
|
|
74
|
+
const sdk = makeSdkStub()
|
|
75
|
+
sdk.auth.login = vi.fn().mockRejectedValue(new Error('bad credentials'))
|
|
103
76
|
|
|
104
|
-
|
|
77
|
+
const provider = new MedusaAuthProvider(sdk, makeAuthConfig(), false)
|
|
78
|
+
|
|
79
|
+
await expect(provider.login({ email: 'test@example.com', password: 'secret' })).rejects.toMatchObject({
|
|
105
80
|
name: 'ProviderAPIError',
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
code: 'PROVIDER_API_ERROR',
|
|
82
|
+
provider: 'LOGIN_FAILED',
|
|
83
|
+
message: 'Authentication failed: bad credentials',
|
|
108
84
|
})
|
|
109
85
|
})
|
|
110
86
|
|
|
111
|
-
it('
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
HttpResponse.json({ message: 'No sales channel found for publishable key' }, { status: 400 }),
|
|
115
|
-
),
|
|
116
|
-
)
|
|
87
|
+
it('returns requiresAction when login requires verification (v2.16.0)', async () => {
|
|
88
|
+
const sdk = makeSdkStub()
|
|
89
|
+
sdk.auth.login = vi.fn().mockResolvedValue({ verification_required: true, token: 'verify-token' })
|
|
117
90
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
91
|
+
const provider = new MedusaAuthProvider(sdk, makeAuthConfig(), false)
|
|
92
|
+
const result = await provider.login({ email: 'test@example.com', password: 'secret' })
|
|
93
|
+
|
|
94
|
+
expect(result.requiresAction).toBe(true)
|
|
95
|
+
expect(result.token).toBe('verify-token')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('stores jwt token and returns customer on register (v2.16.0)', async () => {
|
|
99
|
+
const sdk = makeSdkStub()
|
|
100
|
+
sdk.auth.register = vi.fn().mockResolvedValue(undefined)
|
|
101
|
+
sdk.auth.login = vi.fn().mockResolvedValue('jwt-bearer-token')
|
|
102
|
+
sdk.store.customer.create = vi.fn().mockResolvedValue({
|
|
103
|
+
customer: { id: 'cus_1', email: 'test@example.com', first_name: 'Test', last_name: 'User' },
|
|
104
|
+
})
|
|
105
|
+
sdk.store.customer.retrieve = vi.fn().mockResolvedValue({
|
|
106
|
+
customer: { id: 'cus_1', email: 'test@example.com', first_name: 'Test', last_name: 'User' },
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const provider = new MedusaAuthProvider(sdk, makeAuthConfig('jwt-bearer-token'), false)
|
|
110
|
+
|
|
111
|
+
const result = await provider.register({
|
|
112
|
+
email: 'test@example.com',
|
|
113
|
+
password: 'secret',
|
|
114
|
+
first_name: 'Test',
|
|
115
|
+
last_name: 'User',
|
|
122
116
|
})
|
|
117
|
+
|
|
118
|
+
expect(sdk.auth.register).toHaveBeenCalledWith('customer', 'emailpass', {
|
|
119
|
+
email: 'test@example.com',
|
|
120
|
+
password: 'secret',
|
|
121
|
+
})
|
|
122
|
+
expect(sdk.auth.login).toHaveBeenCalledWith('customer', 'emailpass', {
|
|
123
|
+
email: 'test@example.com',
|
|
124
|
+
password: 'secret',
|
|
125
|
+
})
|
|
126
|
+
expect(result.token).toBe('jwt-bearer-token')
|
|
127
|
+
expect(result.customer?.email).toBe('test@example.com')
|
|
123
128
|
})
|
|
124
129
|
|
|
125
|
-
it('
|
|
126
|
-
|
|
127
|
-
http.post(`${BASE}/store/thor/customers/me/channels`, () =>
|
|
128
|
-
HttpResponse.json({ message: 'Service unavailable' }, { status: 503 }),
|
|
129
|
-
),
|
|
130
|
-
)
|
|
130
|
+
it('requires email when resetting password', async () => {
|
|
131
|
+
const provider = makeProvider()
|
|
131
132
|
|
|
132
|
-
await expect(
|
|
133
|
+
await expect(provider.resetPassword('token', 'new-password')).rejects.toMatchObject({
|
|
133
134
|
name: 'ProviderAPIError',
|
|
134
|
-
provider: '
|
|
135
|
+
provider: 'MEDUSA_AUTH_PROVIDER',
|
|
136
|
+
status: 400,
|
|
135
137
|
})
|
|
136
138
|
})
|
|
137
139
|
})
|