@thorprovider/adapters 5.0.9 → 5.0.11
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 +897 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -1
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.mts +348 -11
- package/dist/index.d.ts +348 -11
- package/dist/index.js +1393 -496
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1386 -498
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/publish.log +93 -0
- package/src/core/errors.ts +31 -0
- package/src/factory/index.ts +16 -6
- package/src/factory/provider-types.test.ts +32 -0
- package/src/factory/provider-types.ts +176 -0
- package/src/index.ts +6 -4
- package/src/providers/index.ts +1 -1
- package/src/providers/medusa/available-filters.test.ts +280 -0
- package/src/providers/medusa/catalog-facet-support.test.ts +715 -0
- package/src/providers/medusa/catalog-query.test.ts +422 -0
- package/src/providers/medusa/catalog-sort.test.ts +175 -0
- package/src/providers/medusa/index.test.ts +480 -0
- package/src/providers/medusa/index.ts +1490 -518
- package/src/providers/medusa/live-smoke.test.ts +68 -0
- package/src/providers/medusa/transforms.test.ts +177 -0
- package/src/providers/medusa/transforms.ts +145 -66
- package/src/providers/mock/catalog-query.test.ts +355 -0
- package/src/providers/mock/index.test.ts +47 -0
- package/src/providers/mock/index.ts +43 -1
- package/src/providers/mock/mock.ts +324 -2
- package/src/types/completion.ts +57 -0
- package/src/types/index.ts +7 -0
|
@@ -174,6 +174,140 @@ describe('MedusaProvider — Products', () => {
|
|
|
174
174
|
})
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Catalog pagination bound + directed lookup (change B, group 4)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
describe('MedusaProvider — Catalog pagination and directed lookup', () => {
|
|
181
|
+
function captureListUrl(): { urls: string[] } {
|
|
182
|
+
const urls: string[] = []
|
|
183
|
+
server.use(
|
|
184
|
+
http.get(`${BASE}/store/products`, ({ request }) => {
|
|
185
|
+
urls.push(request.url)
|
|
186
|
+
const url = new URL(request.url)
|
|
187
|
+
if (url.searchParams.get('handle') || url.searchParams.get('id')) {
|
|
188
|
+
return HttpResponse.json({
|
|
189
|
+
products: [MEDUSA_PRODUCT],
|
|
190
|
+
count: 1,
|
|
191
|
+
offset: 0,
|
|
192
|
+
limit: 1,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
return HttpResponse.json({ products: [], count: 0, offset: 0, limit: 50 })
|
|
196
|
+
}),
|
|
197
|
+
)
|
|
198
|
+
return { urls }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
it('getProducts uses the configured default bound (50), not an unbounded scan', async () => {
|
|
202
|
+
const { urls } = captureListUrl()
|
|
203
|
+
const provider = makeProvider()
|
|
204
|
+
|
|
205
|
+
await provider.getProducts()
|
|
206
|
+
|
|
207
|
+
expect(urls).toHaveLength(1)
|
|
208
|
+
expect(new URL(urls[0]).searchParams.get('limit')).toBe('50')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('getProducts honors an explicit configured bound', async () => {
|
|
212
|
+
const { urls } = captureListUrl()
|
|
213
|
+
const provider = new MedusaProvider({
|
|
214
|
+
baseUrl: BASE,
|
|
215
|
+
publishableKey: PUB_KEY,
|
|
216
|
+
currencyCode: CURRENCY,
|
|
217
|
+
catalogPageSize: 25,
|
|
218
|
+
debug: false,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
await provider.getProducts()
|
|
222
|
+
|
|
223
|
+
expect(new URL(urls[0]).searchParams.get('limit')).toBe('25')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('getProducts paginates per request with first + after (count/offset)', async () => {
|
|
227
|
+
const { urls } = captureListUrl()
|
|
228
|
+
const provider = makeProvider()
|
|
229
|
+
|
|
230
|
+
await provider.getProducts({ first: 10, after: '20' })
|
|
231
|
+
|
|
232
|
+
const params = new URL(urls[0]).searchParams
|
|
233
|
+
expect(params.get('limit')).toBe('10')
|
|
234
|
+
expect(params.get('offset')).toBe('20')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('getProduct resolves by handle with a single directed query (limit 1), never a scan', async () => {
|
|
238
|
+
const { urls } = captureListUrl()
|
|
239
|
+
const provider = makeProvider()
|
|
240
|
+
|
|
241
|
+
const product = await provider.getProduct('test-product')
|
|
242
|
+
|
|
243
|
+
expect(product?.handle).toBe('test-product')
|
|
244
|
+
expect(urls).toHaveLength(1)
|
|
245
|
+
const params = new URL(urls[0]).searchParams
|
|
246
|
+
expect(params.get('handle')).toBe('test-product')
|
|
247
|
+
expect(params.get('limit')).toBe('1')
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('getProductById resolves by id with a directed query, independent of the first page', async () => {
|
|
251
|
+
const { urls } = captureListUrl()
|
|
252
|
+
const provider = makeProvider()
|
|
253
|
+
|
|
254
|
+
const product = await provider.getProductById('prod_01')
|
|
255
|
+
|
|
256
|
+
expect(product?.id).toBe('prod_01')
|
|
257
|
+
expect(urls).toHaveLength(1)
|
|
258
|
+
const params = new URL(urls[0]).searchParams
|
|
259
|
+
expect(params.get('id')).toBe('prod_01')
|
|
260
|
+
expect(params.get('limit')).toBe('1')
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('a product beyond a >200 dataset resolves by handle without scanning the listing', async () => {
|
|
264
|
+
// 250-product regression dataset: the first bounded page never contains the
|
|
265
|
+
// target. A directed handle lookup still resolves it in one request.
|
|
266
|
+
const urls: string[] = []
|
|
267
|
+
const dataset = Array.from({ length: 250 }, (_, i) => ({
|
|
268
|
+
...MEDUSA_PRODUCT,
|
|
269
|
+
id: `prod_${i}`,
|
|
270
|
+
handle: `prod-${i}`,
|
|
271
|
+
}))
|
|
272
|
+
const target = { ...MEDUSA_PRODUCT, id: 'prod_249', handle: 'prod-249' }
|
|
273
|
+
server.use(
|
|
274
|
+
http.get(`${BASE}/store/products`, ({ request }) => {
|
|
275
|
+
urls.push(request.url)
|
|
276
|
+
const url = new URL(request.url)
|
|
277
|
+
const handle = url.searchParams.get('handle')
|
|
278
|
+
if (handle) {
|
|
279
|
+
const hit = dataset.find((p) => p.handle === handle)
|
|
280
|
+
return HttpResponse.json({
|
|
281
|
+
products: hit ? [hit] : [],
|
|
282
|
+
count: hit ? 1 : 0,
|
|
283
|
+
offset: 0,
|
|
284
|
+
limit: 1,
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
const limit = Number(url.searchParams.get('limit') ?? 50)
|
|
288
|
+
return HttpResponse.json({
|
|
289
|
+
products: dataset.slice(0, limit),
|
|
290
|
+
count: dataset.length,
|
|
291
|
+
offset: 0,
|
|
292
|
+
limit,
|
|
293
|
+
})
|
|
294
|
+
}),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
const provider = makeProvider()
|
|
298
|
+
const firstPage = await provider.getProducts()
|
|
299
|
+
expect(firstPage).toHaveLength(50)
|
|
300
|
+
expect(firstPage.some((p) => p.handle === target.handle)).toBe(false)
|
|
301
|
+
|
|
302
|
+
const resolved = await provider.getProduct('prod-249')
|
|
303
|
+
expect(resolved?.handle).toBe('prod-249')
|
|
304
|
+
|
|
305
|
+
const directed = urls.filter((u) => new URL(u).searchParams.get('handle'))
|
|
306
|
+
expect(directed).toHaveLength(1)
|
|
307
|
+
expect(new URL(directed[0]).searchParams.get('limit')).toBe('1')
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
177
311
|
// ---------------------------------------------------------------------------
|
|
178
312
|
// Cart
|
|
179
313
|
// ---------------------------------------------------------------------------
|
|
@@ -206,6 +340,42 @@ describe('MedusaProvider — Cart', () => {
|
|
|
206
340
|
expect(cart.id).toBe('cart_01')
|
|
207
341
|
})
|
|
208
342
|
|
|
343
|
+
it('createCart passes an explicit salesChannelId in the payload', async () => {
|
|
344
|
+
server.use(
|
|
345
|
+
http.post(`${BASE}/store/carts`, async ({ request }) => {
|
|
346
|
+
const body = await request.json() as any
|
|
347
|
+
expect(body.sales_channel_id).toBe('sc_explicit')
|
|
348
|
+
return HttpResponse.json({ cart: MEDUSA_CART })
|
|
349
|
+
}),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const provider = makeProvider()
|
|
353
|
+
await provider.createCart({ regionId: 'reg_01', salesChannelId: 'sc_explicit' })
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('createCart falls back to MEDUSA_SALES_CHANNEL_ID then the public var', async () => {
|
|
357
|
+
const originalServer = process.env.MEDUSA_SALES_CHANNEL_ID
|
|
358
|
+
const originalPublic = process.env.NEXT_PUBLIC_SALES_CHANNEL_ID
|
|
359
|
+
process.env.MEDUSA_SALES_CHANNEL_ID = 'sc_server'
|
|
360
|
+
process.env.NEXT_PUBLIC_SALES_CHANNEL_ID = 'sc_public'
|
|
361
|
+
try {
|
|
362
|
+
server.use(
|
|
363
|
+
http.post(`${BASE}/store/carts`, async ({ request }) => {
|
|
364
|
+
const body = await request.json() as any
|
|
365
|
+
expect(body.sales_channel_id).toBe('sc_server')
|
|
366
|
+
return HttpResponse.json({ cart: MEDUSA_CART })
|
|
367
|
+
}),
|
|
368
|
+
)
|
|
369
|
+
const provider = makeProvider()
|
|
370
|
+
await provider.createCart({ regionId: 'reg_01' })
|
|
371
|
+
} finally {
|
|
372
|
+
if (originalServer === undefined) delete process.env.MEDUSA_SALES_CHANNEL_ID
|
|
373
|
+
else process.env.MEDUSA_SALES_CHANNEL_ID = originalServer
|
|
374
|
+
if (originalPublic === undefined) delete process.env.NEXT_PUBLIC_SALES_CHANNEL_ID
|
|
375
|
+
else process.env.NEXT_PUBLIC_SALES_CHANNEL_ID = originalPublic
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
|
|
209
379
|
it('getCart returns cart when found', async () => {
|
|
210
380
|
server.use(
|
|
211
381
|
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
@@ -235,6 +405,7 @@ describe('MedusaProvider — Cart', () => {
|
|
|
235
405
|
thumbnail: 'https://img.example.com/cover.png',
|
|
236
406
|
quantity: 2,
|
|
237
407
|
unit_price: 60,
|
|
408
|
+
total: 120,
|
|
238
409
|
},
|
|
239
410
|
],
|
|
240
411
|
total: 120,
|
|
@@ -258,6 +429,93 @@ describe('MedusaProvider — Cart', () => {
|
|
|
258
429
|
expect(line?.merchandise.product.title).toBe('Cover Samsung Note 10 Plus')
|
|
259
430
|
expect(line?.merchandise.product.featuredImage.url).toBe('https://img.example.com/cover.png')
|
|
260
431
|
expect(line?.cost.totalAmount.amount).toBe('120.00')
|
|
432
|
+
expect(line?.cost.unitPrice?.amount).toBe('60.00')
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('getCart does not invent a line total from unit_price × quantity', async () => {
|
|
436
|
+
const cartWithoutLineTotal = {
|
|
437
|
+
...MEDUSA_CART,
|
|
438
|
+
items: [
|
|
439
|
+
{
|
|
440
|
+
id: 'cali_01',
|
|
441
|
+
variant_id: 'variant_01',
|
|
442
|
+
quantity: 2,
|
|
443
|
+
unit_price: 60,
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
total: 0,
|
|
447
|
+
subtotal: 0,
|
|
448
|
+
}
|
|
449
|
+
server.use(
|
|
450
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
451
|
+
HttpResponse.json({ cart: cartWithoutLineTotal }),
|
|
452
|
+
),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
const provider = makeProvider()
|
|
456
|
+
const cart = await provider.getCart('cart_01')
|
|
457
|
+
|
|
458
|
+
// No backend line total → empty amount, never a locally multiplied `120`.
|
|
459
|
+
expect(cart?.lines?.[0]?.cost.totalAmount.amount).toBe('')
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('getCart carries the backend context, financial fields and item subtotal', async () => {
|
|
463
|
+
let retrieveUrl = ''
|
|
464
|
+
const cartWithTotals = {
|
|
465
|
+
...MEDUSA_CART,
|
|
466
|
+
sales_channel_id: 'sc_01',
|
|
467
|
+
region_id: 'reg_01',
|
|
468
|
+
currency_code: 'usd',
|
|
469
|
+
item_subtotal: 22.22,
|
|
470
|
+
subtotal: 27.22,
|
|
471
|
+
discount_total: 10,
|
|
472
|
+
tax_total: 5,
|
|
473
|
+
shipping_total: 5,
|
|
474
|
+
total: 27.22,
|
|
475
|
+
items: [
|
|
476
|
+
{
|
|
477
|
+
id: 'cali_01',
|
|
478
|
+
variant_id: 'variant_01',
|
|
479
|
+
product_title: 'B Acceptance Product',
|
|
480
|
+
variant_title: 'Large',
|
|
481
|
+
quantity: 1,
|
|
482
|
+
unit_price: 22.22,
|
|
483
|
+
total: 22.22,
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
shipping_methods: [{ id: 'sm_01', shipping_option_id: 'so_01' }],
|
|
487
|
+
completed_at: null,
|
|
488
|
+
}
|
|
489
|
+
server.use(
|
|
490
|
+
http.get(`${BASE}/store/carts/cart_01`, ({ request }) => {
|
|
491
|
+
retrieveUrl = request.url
|
|
492
|
+
return HttpResponse.json({ cart: cartWithTotals })
|
|
493
|
+
}),
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
const provider = makeProvider()
|
|
497
|
+
const cart = await provider.getCart('cart_01')
|
|
498
|
+
|
|
499
|
+
// The retrieve requests the explicit financial line fields.
|
|
500
|
+
const fields = new URL(retrieveUrl).searchParams.get('fields') ?? ''
|
|
501
|
+
expect(fields).toContain('+items.total')
|
|
502
|
+
expect(fields).toContain('+items.unit_price')
|
|
503
|
+
|
|
504
|
+
expect(cart?.salesChannelId).toBe('sc_01')
|
|
505
|
+
expect(cart?.regionId).toBe('reg_01')
|
|
506
|
+
expect(cart?.currencyCode).toBe('USD')
|
|
507
|
+
expect(cart?.selectedShippingMethodId).toBe('sm_01')
|
|
508
|
+
expect(cart?.selectedShippingOptionId).toBe('so_01')
|
|
509
|
+
expect(cart?.cost).toMatchObject({
|
|
510
|
+
// item_subtotal (lines only), not native subtotal (which includes shipping)
|
|
511
|
+
subtotalAmount: { amount: '22.22', currencyCode: 'USD' },
|
|
512
|
+
discountAmount: { amount: '10.00', currencyCode: 'USD' },
|
|
513
|
+
totalTaxAmount: { amount: '5.00', currencyCode: 'USD' },
|
|
514
|
+
shippingAmount: { amount: '5.00', currencyCode: 'USD' },
|
|
515
|
+
totalAmount: { amount: '27.22', currencyCode: 'USD' },
|
|
516
|
+
})
|
|
517
|
+
expect(cart?.lines[0]?.cost.unitPrice?.amount).toBe('22.22')
|
|
518
|
+
expect(cart?.lines[0]?.cost.totalAmount.amount).toBe('22.22')
|
|
261
519
|
})
|
|
262
520
|
|
|
263
521
|
it('getCart returns undefined when cart not found (404)', async () => {
|
|
@@ -430,3 +688,225 @@ describe('MedusaProvider — Error propagation', () => {
|
|
|
430
688
|
await expect(provider.createCart()).rejects.toThrow()
|
|
431
689
|
})
|
|
432
690
|
})
|
|
691
|
+
|
|
692
|
+
// ---------------------------------------------------------------------------
|
|
693
|
+
// Completion — discriminated native result, never blindly retried
|
|
694
|
+
// ---------------------------------------------------------------------------
|
|
695
|
+
const MEDUSA_ORDER = {
|
|
696
|
+
id: 'order_01',
|
|
697
|
+
display_id: 1001,
|
|
698
|
+
email: 'buyer@example.com',
|
|
699
|
+
status: 'pending',
|
|
700
|
+
payment_status: 'awaiting',
|
|
701
|
+
fulfillment_status: 'not_fulfilled',
|
|
702
|
+
currency_code: 'usd',
|
|
703
|
+
item_subtotal: 22.22,
|
|
704
|
+
subtotal: 27.22,
|
|
705
|
+
items: [
|
|
706
|
+
{
|
|
707
|
+
id: 'ordli_01',
|
|
708
|
+
title: 'B Acceptance Product',
|
|
709
|
+
product_title: 'B Acceptance Product',
|
|
710
|
+
variant_title: 'Large',
|
|
711
|
+
variant_id: 'variant_01',
|
|
712
|
+
product_id: 'prod_01',
|
|
713
|
+
quantity: 1,
|
|
714
|
+
unit_price: 22.22,
|
|
715
|
+
total: 22.22,
|
|
716
|
+
thumbnail: 'https://example.com/p.png',
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
total: 27.22,
|
|
720
|
+
tax_total: 0,
|
|
721
|
+
shipping_total: 5,
|
|
722
|
+
discount_total: 0,
|
|
723
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
724
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
describe('MedusaProvider — Completion', () => {
|
|
728
|
+
it('completeCart requests explicit order item fields and maps populated items', async () => {
|
|
729
|
+
let completeUrl = ''
|
|
730
|
+
server.use(
|
|
731
|
+
http.post(`${BASE}/store/carts/cart_01/complete`, ({ request }) => {
|
|
732
|
+
completeUrl = request.url
|
|
733
|
+
return HttpResponse.json({ type: 'order', order: MEDUSA_ORDER })
|
|
734
|
+
}),
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
const provider = makeProvider()
|
|
738
|
+
const result = await provider.completeCart('cart_01')
|
|
739
|
+
|
|
740
|
+
expect(new URL(completeUrl).searchParams.get('fields')).toBe('*items')
|
|
741
|
+
expect(result.type).toBe('order')
|
|
742
|
+
if (result.type === 'order') {
|
|
743
|
+
expect(result.order.id).toBe('order_01')
|
|
744
|
+
expect(result.order.items).toHaveLength(1)
|
|
745
|
+
expect(result.order.items[0]).toMatchObject({
|
|
746
|
+
id: 'ordli_01',
|
|
747
|
+
title: 'B Acceptance Product',
|
|
748
|
+
variantTitle: 'Large',
|
|
749
|
+
quantity: 1,
|
|
750
|
+
})
|
|
751
|
+
expect(result.order.items[0].price.amount).toBe('22.22')
|
|
752
|
+
expect(result.order.items[0].total.amount).toBe('22.22')
|
|
753
|
+
// Subtotal prefers the backend item_subtotal (lines only), not the
|
|
754
|
+
// native subtotal that folds in shipping.
|
|
755
|
+
expect(result.order.subtotal.amount).toBe('22.22')
|
|
756
|
+
expect(result.order.shipping?.amount).toBe('5.00')
|
|
757
|
+
expect(result.order.total.amount).toBe('27.22')
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
it('completeCart preserves a cart-with-error discriminator', async () => {
|
|
762
|
+
server.use(
|
|
763
|
+
http.post(`${BASE}/store/carts/cart_01/complete`, () =>
|
|
764
|
+
HttpResponse.json({
|
|
765
|
+
type: 'cart',
|
|
766
|
+
cart: MEDUSA_CART,
|
|
767
|
+
error: {
|
|
768
|
+
message: 'Payment not authorized',
|
|
769
|
+
name: 'PaymentError',
|
|
770
|
+
type: 'invalid_data',
|
|
771
|
+
},
|
|
772
|
+
}),
|
|
773
|
+
),
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
const provider = makeProvider()
|
|
777
|
+
const result = await provider.completeCart('cart_01')
|
|
778
|
+
|
|
779
|
+
expect(result.type).toBe('cart')
|
|
780
|
+
if (result.type === 'cart') {
|
|
781
|
+
expect(result.error.message).toBe('Payment not authorized')
|
|
782
|
+
expect(result.cart.id).toBe('cart_01')
|
|
783
|
+
}
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
it('completeCart returns unresolved and does not retry a failed mutation', async () => {
|
|
787
|
+
let calls = 0
|
|
788
|
+
server.use(
|
|
789
|
+
http.post(`${BASE}/store/carts/cart_01/complete`, () => {
|
|
790
|
+
calls += 1
|
|
791
|
+
return HttpResponse.json({ message: 'Gateway Timeout' }, { status: 504 })
|
|
792
|
+
}),
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
const provider = makeProvider()
|
|
796
|
+
const result = await provider.completeCart('cart_01')
|
|
797
|
+
|
|
798
|
+
expect(result.type).toBe('unresolved')
|
|
799
|
+
expect(calls).toBe(1)
|
|
800
|
+
})
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
// ---------------------------------------------------------------------------
|
|
804
|
+
// Payment session compatibility (reuse vs replace)
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
describe('MedusaProvider — Payment session compatibility', () => {
|
|
807
|
+
const BASE_CART_WITH_PAYMENT = {
|
|
808
|
+
...MEDUSA_CART,
|
|
809
|
+
items: [{ id: 'li_01', quantity: 1 }],
|
|
810
|
+
email: 'buyer@example.com',
|
|
811
|
+
shipping_address: { country_code: 'gb' },
|
|
812
|
+
shipping_methods: [{ id: 'sm_01' }],
|
|
813
|
+
total: 103,
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
it('reuses a compatible session without creating a new one', async () => {
|
|
817
|
+
let posts = 0
|
|
818
|
+
server.use(
|
|
819
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
820
|
+
HttpResponse.json({
|
|
821
|
+
cart: {
|
|
822
|
+
...BASE_CART_WITH_PAYMENT,
|
|
823
|
+
payment_collection: {
|
|
824
|
+
id: 'paycol_01',
|
|
825
|
+
payment_sessions: [
|
|
826
|
+
{ id: 'ps_01', provider_id: 'pp_system_default', amount: 103, status: 'pending' },
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
}),
|
|
831
|
+
),
|
|
832
|
+
http.post(`${BASE}/store/payment-collections/paycol_01/payment-sessions`, () => {
|
|
833
|
+
posts += 1
|
|
834
|
+
return HttpResponse.json({ payment_collection: {} })
|
|
835
|
+
}),
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
const provider = makeProvider()
|
|
839
|
+
await provider.initiatePaymentSession('cart_01', 'pp_system_default')
|
|
840
|
+
|
|
841
|
+
expect(posts).toBe(0)
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
it('replaces a session whose provider/amount is incompatible', async () => {
|
|
845
|
+
let posts = 0
|
|
846
|
+
server.use(
|
|
847
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
848
|
+
HttpResponse.json({
|
|
849
|
+
cart: {
|
|
850
|
+
...BASE_CART_WITH_PAYMENT,
|
|
851
|
+
payment_collection: {
|
|
852
|
+
id: 'paycol_01',
|
|
853
|
+
payment_sessions: [
|
|
854
|
+
{ id: 'ps_old', provider_id: 'pp_stripe_stripe', amount: 50, status: 'pending' },
|
|
855
|
+
],
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
}),
|
|
859
|
+
),
|
|
860
|
+
http.post(`${BASE}/store/payment-collections/paycol_01/payment-sessions`, () => {
|
|
861
|
+
posts += 1
|
|
862
|
+
return HttpResponse.json({ payment_collection: {} })
|
|
863
|
+
}),
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
const provider = makeProvider()
|
|
867
|
+
await provider.initiatePaymentSession('cart_01', 'pp_system_default')
|
|
868
|
+
|
|
869
|
+
expect(posts).toBe(1)
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
it('rejects payment before a shipping method is selected', async () => {
|
|
873
|
+
server.use(
|
|
874
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
875
|
+
HttpResponse.json({
|
|
876
|
+
cart: { ...BASE_CART_WITH_PAYMENT, shipping_methods: [] },
|
|
877
|
+
}),
|
|
878
|
+
),
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
const provider = makeProvider()
|
|
882
|
+
await expect(
|
|
883
|
+
provider.initiatePaymentSession('cart_01', 'pp_system_default'),
|
|
884
|
+
).rejects.toThrow(/Shipping method is required/)
|
|
885
|
+
})
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
// ---------------------------------------------------------------------------
|
|
889
|
+
// Shipping truthfulness — no fabricated amount or placeholder ETA
|
|
890
|
+
// ---------------------------------------------------------------------------
|
|
891
|
+
describe('MedusaProvider — Shipping truthfulness', () => {
|
|
892
|
+
it('does not fabricate a zero amount or an "N/A" ETA', async () => {
|
|
893
|
+
server.use(
|
|
894
|
+
http.get(`${BASE}/store/carts/cart_01`, () =>
|
|
895
|
+
HttpResponse.json({ cart: { ...MEDUSA_CART, region_id: 'reg_01' } }),
|
|
896
|
+
),
|
|
897
|
+
http.get(`${BASE}/store/shipping-options`, () =>
|
|
898
|
+
HttpResponse.json({
|
|
899
|
+
shipping_options: [
|
|
900
|
+
{ id: 'so_01', name: 'Standard Shipping', data: {} },
|
|
901
|
+
],
|
|
902
|
+
}),
|
|
903
|
+
),
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
const provider = makeProvider()
|
|
907
|
+
const methods = await provider.getShippingMethods('cart_01')
|
|
908
|
+
|
|
909
|
+
expect(methods[0].price.amount).toBe('')
|
|
910
|
+
expect(methods[0].estimatedDays).toBeUndefined()
|
|
911
|
+
})
|
|
912
|
+
})
|