@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.
@@ -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
+ })