@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.
@@ -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 '@thorprovider/adapters'` (no subpath exports)
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)
@@ -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-02-28
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
- ### Cart Operations
43
+ ### Order Management
42
44
 
43
45
  | Capability | Type | Description |
44
46
  |------------|------|-------------|
45
- | `cartPromoCode` | `boolean` | Can apply discount/promo codes to cart. |
46
- | `cartGiftCards` | `boolean` | Supports gift card application. |
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
- ### Product & Discovery
50
+ ### Address Management
51
51
 
52
52
  | Capability | Type | Description |
53
53
  |------------|------|-------------|
54
- | `productVariants` | `boolean` | Backend supports multiple variants per product. |
55
- | `productCategories` | `boolean` | Supports nested categories or collections. |
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
- ## Type Definitions
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
- // Cart
76
- cartPromoCode: boolean;
77
- cartGiftCards: boolean;
78
- cartNotes: boolean;
79
- cartCustomAttributes: boolean;
80
-
81
- // Products
82
- productVariants: boolean;
83
- productCategories: boolean;
84
- productInventory: boolean;
85
- productDigital: boolean;
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
- // Auth Integration
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
  ```
@@ -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+ (PnP mode)
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 (v2.0)
38
- **Status:** ✅ Production Ready
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
- ### WooCommerce
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/update-documentation.md
9
- updated: 2026-02-28
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/update-documentation.md](../../../docs/update-documentation.md)
16
+ Global guidelines: [docs/standards/documentation.md](../../../docs/standards/documentation.md)
15
17
 
16
18
  ## Prerequisites
17
19
 
18
- - Read [global guidelines](../../../docs/update-documentation.md) for doc types and templates
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 [Adapter Strategy](../../ARCHITECTURE.md) (Strategy Pattern)
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 update-documentation.md](../../../docs/update-documentation.md)
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": "4.3.0",
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.11.3",
30
- "@medusajs/types": "2.11.3",
31
- "@thorprovider/types": "^3.0.0",
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
  },
@@ -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.linkCustomerToChannel
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 { http, HttpResponse } from 'msw'
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
- store: { customer: {} },
26
- } as any
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: (_key: string) => Promise.resolve(stored),
37
+ getItem: () => Promise.resolve(stored),
37
38
  setItem: (_key: string, value: string) => { stored = value; return Promise.resolve() },
38
- removeItem: (_key: string) => { stored = null; return Promise.resolve() },
39
+ removeItem: () => { stored = null; return Promise.resolve() },
39
40
  }
40
41
  }
41
42
 
42
- function makeAuthConfig(token: string | null = TOKEN) {
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 = TOKEN) {
51
- return new MedusaAuthProvider(makeSdkStub(), makeAuthConfig(token), false, BASE, PUB_KEY)
51
+ function makeProvider(token: string | null = 'jwt-bearer-token') {
52
+ return new MedusaAuthProvider(makeSdkStub(), makeAuthConfig(token), false)
52
53
  }
53
54
 
54
- // ---------------------------------------------------------------------------
55
- // Tests
56
- // ---------------------------------------------------------------------------
57
-
58
- describe('MedusaAuthProvider.linkCustomerToChannel', () => {
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
- it('POST without Authorization header when no token in storage', async () => {
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
- it('returns { linked: true, reason: "already_linked" } on duplicate link', async () => {
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
- const result = await makeProvider().linkCustomerToChannel()
93
- expect(result.linked).toBe(true)
94
- expect(result.reason).toBe('already_linked')
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 NOT_AUTHENTICATED on 401', async () => {
98
- server.use(
99
- http.post(`${BASE}/store/thor/customers/me/channels`, () =>
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
- await expect(makeProvider().linkCustomerToChannel()).rejects.toMatchObject({
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
- provider: 'NOT_AUTHENTICATED',
107
- status: 401,
81
+ code: 'PROVIDER_API_ERROR',
82
+ provider: 'LOGIN_FAILED',
83
+ message: 'Authentication failed: bad credentials',
108
84
  })
109
85
  })
110
86
 
111
- it('throws ProviderAPIError with CHANNEL_RESOLUTION_FAILED on 400', async () => {
112
- server.use(
113
- http.post(`${BASE}/store/thor/customers/me/channels`, () =>
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
- await expect(makeProvider().linkCustomerToChannel()).rejects.toMatchObject({
119
- name: 'ProviderAPIError',
120
- provider: 'CHANNEL_RESOLUTION_FAILED',
121
- status: 400,
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('throws ProviderAPIError with LINK_CHANNEL_FAILED on unexpected status', async () => {
126
- server.use(
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(makeProvider().linkCustomerToChannel()).rejects.toMatchObject({
133
+ await expect(provider.resetPassword('token', 'new-password')).rejects.toMatchObject({
133
134
  name: 'ProviderAPIError',
134
- provider: 'LINK_CHANNEL_FAILED',
135
+ provider: 'MEDUSA_AUTH_PROVIDER',
136
+ status: 400,
135
137
  })
136
138
  })
137
139
  })