@thorprovider/adapters 4.2.1 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +18 -0
- package/.turbo/turbo-test.log +1 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +6 -0
- package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
- package/README.md +3 -3
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.mts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -1
- package/docs/RETRY_SOLUTION_SUMMARY.md +26 -11
- package/docs/authentication.md +89 -62
- package/docs/capabilities-system.md +70 -38
- package/docs/category-customization.md +37 -29
- package/docs/fetch-reliability-solution.md +127 -278
- package/docs/getting-started.md +43 -29
- package/docs/profile-management.md +68 -38
- package/docs/services-overview.md +4 -2
- package/docs/update-documentation.md +10 -8
- package/package.json +3 -2
- package/publish.log +93 -0
- package/src/index.ts +4 -0
- package/src/providers/medusa/admin.test.ts +404 -0
- package/src/providers/medusa/admin.ts +442 -0
- package/src/providers/medusa/auth.test.ts +95 -0
- package/src/providers/medusa/index.test.ts +392 -0
- package/src/providers/medusa/live-smoke.test.ts +412 -0
- package/src/resilience/README.md +70 -48
- package/vitest.live.config.ts +18 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for MedusaProvider (main storefront provider).
|
|
3
|
+
*
|
|
4
|
+
* Uses msw to intercept HTTP requests — no real backend required.
|
|
5
|
+
* Follows the same pattern as auth.test.ts and admin.test.ts.
|
|
6
|
+
*
|
|
7
|
+
* Coverage:
|
|
8
|
+
* - Products: getProduct, getProducts (success + 404 + 503)
|
|
9
|
+
* - Cart: createCart, getCart (success + 404), addToCart
|
|
10
|
+
* - Shipping: getShippingMethods
|
|
11
|
+
* - Regions: getRegions
|
|
12
|
+
* - Checkout: getPaymentMethods, completeCheckout
|
|
13
|
+
*/
|
|
14
|
+
import { describe, expect, it } from 'vitest'
|
|
15
|
+
import { http, HttpResponse } from 'msw'
|
|
16
|
+
import { server } from '../stelorder/client.test-helpers'
|
|
17
|
+
import { MedusaProvider } from './index'
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Test constants
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const BASE = 'https://medusa.example.com'
|
|
23
|
+
const PUB_KEY = 'pk_test_abc123'
|
|
24
|
+
const CURRENCY = 'usd'
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Factory
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
function makeProvider() {
|
|
30
|
+
return new MedusaProvider({
|
|
31
|
+
baseUrl: BASE,
|
|
32
|
+
publishableKey: PUB_KEY,
|
|
33
|
+
currencyCode: CURRENCY,
|
|
34
|
+
debug: false,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Minimal Medusa response fixtures
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
const MEDUSA_PRODUCT = {
|
|
42
|
+
id: 'prod_01',
|
|
43
|
+
handle: 'test-product',
|
|
44
|
+
title: 'Test Product',
|
|
45
|
+
subtitle: null,
|
|
46
|
+
description: 'A test product',
|
|
47
|
+
status: 'published',
|
|
48
|
+
thumbnail: 'https://example.com/thumb.jpg',
|
|
49
|
+
images: [{ id: 'img_01', url: 'https://example.com/img.jpg', metadata: null }],
|
|
50
|
+
tags: [],
|
|
51
|
+
collection: null,
|
|
52
|
+
categories: [],
|
|
53
|
+
options: [{ id: 'opt_01', title: 'Size', product_id: 'prod_01', values: [{ id: 'optval_01', value: 'One Size', option_id: 'opt_01' }] }],
|
|
54
|
+
variants: [
|
|
55
|
+
{
|
|
56
|
+
id: 'variant_01',
|
|
57
|
+
title: 'Default',
|
|
58
|
+
product_id: 'prod_01',
|
|
59
|
+
sku: 'SKU-001',
|
|
60
|
+
inventory_quantity: 10,
|
|
61
|
+
prices: [{ id: 'price_01', amount: 2500, currency_code: 'usd' }],
|
|
62
|
+
calculated_price: { calculated_amount: 2500, currency_code: 'usd' },
|
|
63
|
+
options: [{ id: 'optval_01', value: 'One Size' }],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
metadata: null,
|
|
67
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
68
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const MEDUSA_CART = {
|
|
72
|
+
id: 'cart_01',
|
|
73
|
+
currency_code: 'usd',
|
|
74
|
+
region_id: 'reg_01',
|
|
75
|
+
region: { id: 'reg_01', currency_code: 'usd' },
|
|
76
|
+
items: [],
|
|
77
|
+
total: 0,
|
|
78
|
+
subtotal: 0,
|
|
79
|
+
tax_total: 0,
|
|
80
|
+
shipping_total: 0,
|
|
81
|
+
discount_total: 0,
|
|
82
|
+
shipping_address: null,
|
|
83
|
+
billing_address: null,
|
|
84
|
+
shipping_methods: [],
|
|
85
|
+
payment_collection: null,
|
|
86
|
+
metadata: null,
|
|
87
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
88
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const MEDUSA_REGION = {
|
|
92
|
+
id: 'reg_01',
|
|
93
|
+
name: 'North America',
|
|
94
|
+
currency_code: 'usd',
|
|
95
|
+
countries: [{ iso_2: 'us', display_name: 'United States', name: 'UNITED STATES', iso_3: 'usa', num_code: '840', region_id: 'reg_01', metadata: null, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z', deleted_at: null }],
|
|
96
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
97
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
98
|
+
deleted_at: null,
|
|
99
|
+
metadata: null,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Products
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
describe('MedusaProvider — Products', () => {
|
|
106
|
+
it('getProduct returns transformed product when found', async () => {
|
|
107
|
+
server.use(
|
|
108
|
+
http.get(`${BASE}/store/products`, () =>
|
|
109
|
+
HttpResponse.json({ products: [MEDUSA_PRODUCT], count: 1, offset: 0, limit: 1 }),
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const provider = makeProvider()
|
|
114
|
+
const product = await provider.getProduct('test-product')
|
|
115
|
+
|
|
116
|
+
expect(product).not.toBeUndefined()
|
|
117
|
+
expect(product!.id).toBe('prod_01')
|
|
118
|
+
expect(product!.handle).toBe('test-product')
|
|
119
|
+
expect(product!.title).toBe('Test Product')
|
|
120
|
+
expect(product!.variants).toHaveLength(1)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('getProduct returns undefined when not found', async () => {
|
|
124
|
+
server.use(
|
|
125
|
+
http.get(`${BASE}/store/products`, () =>
|
|
126
|
+
HttpResponse.json({ products: [], count: 0, offset: 0, limit: 1 }),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const provider = makeProvider()
|
|
131
|
+
const product = await provider.getProduct('does-not-exist')
|
|
132
|
+
|
|
133
|
+
expect(product).toBeUndefined()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('getProduct throws ProviderAPIError on 503', async () => {
|
|
137
|
+
server.use(
|
|
138
|
+
http.get(`${BASE}/store/products`, () =>
|
|
139
|
+
HttpResponse.json({ message: 'Service unavailable' }, { status: 503 }),
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const provider = makeProvider()
|
|
144
|
+
|
|
145
|
+
await expect(provider.getProduct('any-handle')).rejects.toThrow()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('getProducts returns array of transformed products', async () => {
|
|
149
|
+
server.use(
|
|
150
|
+
http.get(`${BASE}/store/products`, () =>
|
|
151
|
+
HttpResponse.json({ products: [MEDUSA_PRODUCT], count: 1, offset: 0, limit: 100 }),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const provider = makeProvider()
|
|
156
|
+
const products = await provider.getProducts({ first: 10 })
|
|
157
|
+
|
|
158
|
+
expect(Array.isArray(products)).toBe(true)
|
|
159
|
+
expect(products).toHaveLength(1)
|
|
160
|
+
expect(products[0].handle).toBe('test-product')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('getProducts returns empty array when backend returns none', async () => {
|
|
164
|
+
server.use(
|
|
165
|
+
http.get(`${BASE}/store/products`, () =>
|
|
166
|
+
HttpResponse.json({ products: [], count: 0, offset: 0, limit: 100 }),
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
const provider = makeProvider()
|
|
171
|
+
const products = await provider.getProducts()
|
|
172
|
+
|
|
173
|
+
expect(products).toHaveLength(0)
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Cart
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
describe('MedusaProvider — Cart', () => {
|
|
181
|
+
it('createCart returns a cart with an id', async () => {
|
|
182
|
+
server.use(
|
|
183
|
+
http.post(`${BASE}/store/carts`, () =>
|
|
184
|
+
HttpResponse.json({ cart: MEDUSA_CART }),
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
const provider = makeProvider()
|
|
189
|
+
const cart = await provider.createCart()
|
|
190
|
+
|
|
191
|
+
expect(cart.id).toBe('cart_01')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('createCart with regionId passes region_id in payload', async () => {
|
|
195
|
+
server.use(
|
|
196
|
+
http.post(`${BASE}/store/carts`, async ({ request }) => {
|
|
197
|
+
const body = await request.json() as any
|
|
198
|
+
expect(body.region_id).toBe('reg_test')
|
|
199
|
+
return HttpResponse.json({ cart: { ...MEDUSA_CART, region_id: 'reg_test' } })
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const provider = makeProvider()
|
|
204
|
+
const cart = await provider.createCart({ regionId: 'reg_test' })
|
|
205
|
+
|
|
206
|
+
expect(cart.id).toBe('cart_01')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('getCart returns cart when found', async () => {
|
|
210
|
+
server.use(
|
|
211
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
212
|
+
HttpResponse.json({ cart: MEDUSA_CART }),
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const provider = makeProvider()
|
|
217
|
+
const cart = await provider.getCart('cart_01')
|
|
218
|
+
|
|
219
|
+
expect(cart).not.toBeUndefined()
|
|
220
|
+
expect(cart!.id).toBe('cart_01')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('getCart returns undefined when cart not found (404)', async () => {
|
|
224
|
+
server.use(
|
|
225
|
+
http.get(`${BASE}/store/carts/cart_missing`, () =>
|
|
226
|
+
HttpResponse.json({ message: 'Not found' }, { status: 404 }),
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const provider = makeProvider()
|
|
231
|
+
const cart = await provider.getCart('cart_missing')
|
|
232
|
+
|
|
233
|
+
expect(cart).toBeUndefined()
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('createCart throws ProviderAPIError on server error', async () => {
|
|
237
|
+
server.use(
|
|
238
|
+
http.post(`${BASE}/store/carts`, () =>
|
|
239
|
+
HttpResponse.json({ message: 'Internal Server Error' }, { status: 500 }),
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
const provider = makeProvider()
|
|
244
|
+
|
|
245
|
+
await expect(provider.createCart()).rejects.toThrow()
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Regions
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
describe('MedusaProvider — Regions', () => {
|
|
253
|
+
it('getRegions returns array of regions', async () => {
|
|
254
|
+
server.use(
|
|
255
|
+
http.get(`${BASE}/store/regions`, () =>
|
|
256
|
+
HttpResponse.json({ regions: [MEDUSA_REGION], count: 1, offset: 0, limit: 100 }),
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const provider = makeProvider()
|
|
261
|
+
const regions = await provider.getRegions()
|
|
262
|
+
|
|
263
|
+
expect(Array.isArray(regions)).toBe(true)
|
|
264
|
+
expect(regions).toHaveLength(1)
|
|
265
|
+
expect(regions[0].id).toBe('reg_01')
|
|
266
|
+
expect(regions[0].name).toBe('North America')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('getRegions returns empty array when no regions', async () => {
|
|
270
|
+
server.use(
|
|
271
|
+
http.get(`${BASE}/store/regions`, () =>
|
|
272
|
+
HttpResponse.json({ regions: [], count: 0, offset: 0, limit: 100 }),
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const provider = makeProvider()
|
|
277
|
+
const regions = await provider.getRegions()
|
|
278
|
+
|
|
279
|
+
expect(regions).toHaveLength(0)
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Shipping
|
|
285
|
+
// getShippingMethods first retrieves the cart (to get region_id), then fetches
|
|
286
|
+
// shipping options for that cart via GET /store/shipping-options?cart_id=...
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
describe('MedusaProvider — Shipping', () => {
|
|
289
|
+
const CART_WITH_REGION = { ...MEDUSA_CART, region_id: 'reg_01' }
|
|
290
|
+
|
|
291
|
+
const MEDUSA_SHIPPING_OPTION = {
|
|
292
|
+
id: 'so_01',
|
|
293
|
+
name: 'Standard Shipping',
|
|
294
|
+
amount: 499,
|
|
295
|
+
price_type: 'flat',
|
|
296
|
+
provider_id: 'manual',
|
|
297
|
+
service_zone: { id: 'sz_01', name: 'Default' },
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
it('getShippingMethods returns available methods', async () => {
|
|
301
|
+
server.use(
|
|
302
|
+
// Step 1: cart retrieve
|
|
303
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
304
|
+
HttpResponse.json({ cart: CART_WITH_REGION }),
|
|
305
|
+
),
|
|
306
|
+
// Step 2: shipping options
|
|
307
|
+
http.get(`${BASE}/store/shipping-options`, () =>
|
|
308
|
+
HttpResponse.json({ shipping_options: [MEDUSA_SHIPPING_OPTION] }),
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
const provider = makeProvider()
|
|
313
|
+
const methods = await provider.getShippingMethods('cart_01')
|
|
314
|
+
|
|
315
|
+
expect(Array.isArray(methods)).toBe(true)
|
|
316
|
+
expect(methods).toHaveLength(1)
|
|
317
|
+
expect(methods[0].id).toBe('so_01')
|
|
318
|
+
expect(methods[0].name).toBe('Standard Shipping')
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('getShippingMethods returns empty array when none available', async () => {
|
|
322
|
+
server.use(
|
|
323
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
324
|
+
HttpResponse.json({ cart: CART_WITH_REGION }),
|
|
325
|
+
),
|
|
326
|
+
http.get(`${BASE}/store/shipping-options`, () =>
|
|
327
|
+
HttpResponse.json({ shipping_options: [] }),
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
const provider = makeProvider()
|
|
332
|
+
const methods = await provider.getShippingMethods('cart_01')
|
|
333
|
+
|
|
334
|
+
expect(methods).toHaveLength(0)
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Payment methods
|
|
340
|
+
// getPaymentMethods fetches the cart first (to get region_id), then calls
|
|
341
|
+
// GET /store/payment-providers?region_id=...
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
describe('MedusaProvider — Payment', () => {
|
|
344
|
+
it('getPaymentMethods returns payment providers', async () => {
|
|
345
|
+
server.use(
|
|
346
|
+
// Step 1: cart retrieve to get region_id
|
|
347
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
348
|
+
HttpResponse.json({ cart: { ...MEDUSA_CART, region_id: 'reg_01' } }),
|
|
349
|
+
),
|
|
350
|
+
// Step 2: payment providers
|
|
351
|
+
http.get(`${BASE}/store/payment-providers`, () =>
|
|
352
|
+
HttpResponse.json({
|
|
353
|
+
payment_providers: [{ id: 'pp_manual', name: 'Manual', is_enabled: true }],
|
|
354
|
+
}),
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
const provider = makeProvider()
|
|
359
|
+
const methods = await provider.getPaymentMethods({ cartId: 'cart_01' })
|
|
360
|
+
|
|
361
|
+
expect(Array.isArray(methods)).toBe(true)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// Error propagation
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
describe('MedusaProvider — Error propagation', () => {
|
|
369
|
+
it('getProducts throws when backend returns 503', async () => {
|
|
370
|
+
server.use(
|
|
371
|
+
http.get(`${BASE}/store/products`, () =>
|
|
372
|
+
HttpResponse.json({ message: 'Service Unavailable' }, { status: 503 }),
|
|
373
|
+
),
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
const provider = makeProvider()
|
|
377
|
+
|
|
378
|
+
await expect(provider.getProducts()).rejects.toThrow()
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('createCart throws when backend returns 500', async () => {
|
|
382
|
+
server.use(
|
|
383
|
+
http.post(`${BASE}/store/carts`, () =>
|
|
384
|
+
HttpResponse.json({ message: 'Internal Server Error' }, { status: 500 }),
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
const provider = makeProvider()
|
|
389
|
+
|
|
390
|
+
await expect(provider.createCart()).rejects.toThrow()
|
|
391
|
+
})
|
|
392
|
+
})
|