@thorprovider/adapters 5.0.0 → 5.0.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/AGENTS.md +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +53 -631
- package/dist/index.d.mts +406 -194
- package/dist/index.d.ts +406 -194
- package/dist/index.js +3092 -604
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3081 -602
- 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/backend/factory.ts +11 -1
- package/src/factory/index.ts +52 -74
- package/src/index.ts +17 -3
- package/src/providers/bigcommerce/bigcommerce.ts +6 -0
- package/src/providers/bigcommerce/index.ts +315 -0
- package/src/providers/bigcommerce/transforms.ts +227 -0
- package/src/providers/index.ts +16 -1
- package/src/providers/magento/index.ts +387 -0
- package/src/providers/magento/magento.ts +5 -0
- package/src/providers/magento/queries.ts +267 -0
- package/src/providers/magento/transforms.ts +249 -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 +402 -0
- package/src/providers/shopify/index.ts +444 -0
- package/src/providers/shopify/mutations.ts +247 -0
- package/src/providers/shopify/queries.ts +324 -0
- package/src/providers/shopify/shopify.ts +7 -0
- package/src/providers/shopify/transforms.ts +275 -0
- package/src/providers/spree/index.ts +344 -0
- package/src/providers/spree/spree.ts +5 -0
- package/src/providers/spree/transforms.ts +254 -0
- package/src/providers/woocommerce/index.ts +310 -0
- package/src/providers/woocommerce/transforms.ts +254 -0
- package/src/providers/woocommerce/woocommerce.ts +5 -0
- package/src/types/capabilities.types.ts +250 -0
- package/vitest.live.config.ts +18 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adobe Commerce (Magento) Transform Functions
|
|
3
|
+
* Converts Magento GraphQL / REST API responses to @thorprovider/types
|
|
4
|
+
*
|
|
5
|
+
* Magento returns price as { regularPrice: { amount: { value, currency } } }
|
|
6
|
+
* Images have url + label directly.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Product, ProductVariant, ProductOption, Image, Collection, Cart, CartItem, Order, OrderItem, Customer, Address, Money } from '@thorprovider/types';
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// Money / Price Helpers
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
function toMoney(value: number | undefined, currencyCode: string): Money {
|
|
16
|
+
return { amount: (value ?? 0).toFixed(2), currencyCode };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function extractPrice(priceRange: any, currencyCode: string): { min: Money; max: Money } {
|
|
20
|
+
const minPrice = priceRange?.minimum_price?.regular_price?.value ?? priceRange?.minimum_price?.final_price?.value ?? 0;
|
|
21
|
+
const maxPrice = priceRange?.maximum_price?.regular_price?.value ?? priceRange?.maximum_price?.final_price?.value ?? minPrice;
|
|
22
|
+
const currency = priceRange?.minimum_price?.regular_price?.currency ?? currencyCode;
|
|
23
|
+
return {
|
|
24
|
+
min: toMoney(minPrice, currency),
|
|
25
|
+
max: toMoney(maxPrice, currency),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// Product Transforms
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
export function transformProduct(data: any, currencyCode: string = 'USD'): Product {
|
|
34
|
+
const id = String(data.id ?? data.uid ?? '');
|
|
35
|
+
const { min, max } = extractPrice(data.price_range, currencyCode);
|
|
36
|
+
|
|
37
|
+
// Images
|
|
38
|
+
const images: Image[] = (data.media_gallery ?? data.media_gallery_entries ?? []).map((img: any) => ({
|
|
39
|
+
url: img.url ?? (img.file ? `${img.file}` : ''),
|
|
40
|
+
altText: img.label ?? data.name ?? '',
|
|
41
|
+
width: undefined,
|
|
42
|
+
height: undefined,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Small image fallback
|
|
46
|
+
if (images.length === 0 && data.small_image?.url) {
|
|
47
|
+
images.push({ url: data.small_image.url, altText: data.small_image.label ?? data.name ?? '' });
|
|
48
|
+
}
|
|
49
|
+
if (images.length === 0 && data.image?.url) {
|
|
50
|
+
images.push({ url: data.image.url, altText: data.image.label ?? data.name ?? '' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Variants from configurable_options / variants
|
|
54
|
+
const variants: ProductVariant[] = [];
|
|
55
|
+
if (data.variants && Array.isArray(data.variants)) {
|
|
56
|
+
for (const v of data.variants) {
|
|
57
|
+
const product = v.product ?? {};
|
|
58
|
+
const vPrice = product.price_range?.minimum_price?.final_price ?? {};
|
|
59
|
+
variants.push({
|
|
60
|
+
id: String(product.id ?? product.uid ?? product.sku ?? ''),
|
|
61
|
+
title: product.name ?? product.sku ?? '',
|
|
62
|
+
availableForSale: product.stock_status === 'IN_STOCK',
|
|
63
|
+
price: toMoney(vPrice.value ?? min.amount, vPrice.currency ?? currencyCode),
|
|
64
|
+
compareAtPrice: undefined,
|
|
65
|
+
selectedOptions: (v.attributes ?? []).map((attr: any) => ({
|
|
66
|
+
name: attr.label ?? '',
|
|
67
|
+
value: attr.value_index != null ? String(attr.value_index) : '',
|
|
68
|
+
})),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Default variant if simple product
|
|
74
|
+
if (variants.length === 0) {
|
|
75
|
+
variants.push({
|
|
76
|
+
id: data.sku ?? id,
|
|
77
|
+
title: data.name ?? '',
|
|
78
|
+
availableForSale: data.stock_status === 'IN_STOCK',
|
|
79
|
+
price: min,
|
|
80
|
+
compareAtPrice: undefined,
|
|
81
|
+
selectedOptions: [],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Options
|
|
86
|
+
const options: ProductOption[] = (data.configurable_options ?? []).map((opt: any) => ({
|
|
87
|
+
id: String(opt.attribute_id ?? opt.attribute_code ?? ''),
|
|
88
|
+
name: opt.label ?? opt.attribute_code ?? '',
|
|
89
|
+
values: (opt.values ?? []).map((v: any) => v.label ?? v.store_label ?? String(v.value_index ?? '')),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
handle: data.url_key ?? data.sku ?? id,
|
|
95
|
+
title: data.name ?? '',
|
|
96
|
+
description: data.short_description?.html ?? data.description?.html ?? '',
|
|
97
|
+
descriptionHtml: data.description?.html ?? '',
|
|
98
|
+
availableForSale: data.stock_status === 'IN_STOCK',
|
|
99
|
+
priceRange: { minVariantPrice: min, maxVariantPrice: max },
|
|
100
|
+
variants,
|
|
101
|
+
options,
|
|
102
|
+
images,
|
|
103
|
+
featuredImage: images[0] ?? { url: '', altText: data.name ?? '' },
|
|
104
|
+
tags: [],
|
|
105
|
+
seo: {
|
|
106
|
+
title: data.meta_title ?? data.name ?? '',
|
|
107
|
+
description: data.meta_description ?? '',
|
|
108
|
+
},
|
|
109
|
+
updatedAt: data.updated_at ?? new Date().toISOString(),
|
|
110
|
+
createdAt: data.created_at ?? new Date().toISOString(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================
|
|
115
|
+
// Collection Transforms (Magento Categories)
|
|
116
|
+
// ============================================
|
|
117
|
+
|
|
118
|
+
export function transformCollection(data: any): Collection {
|
|
119
|
+
return {
|
|
120
|
+
id: String(data.id ?? data.uid ?? ''),
|
|
121
|
+
handle: data.url_key ?? data.url_path ?? String(data.id ?? data.uid ?? ''),
|
|
122
|
+
title: data.name ?? '',
|
|
123
|
+
description: data.description ?? '',
|
|
124
|
+
seo: {
|
|
125
|
+
title: data.meta_title ?? data.name ?? '',
|
|
126
|
+
description: data.meta_description ?? '',
|
|
127
|
+
},
|
|
128
|
+
updatedAt: data.updated_at ?? new Date().toISOString(),
|
|
129
|
+
path: `/collections/${data.url_key ?? data.url_path ?? data.id}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// Cart Transforms (Magento Cart / Quote)
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
export function transformCart(data: any, currencyCode: string = 'USD'): Cart {
|
|
138
|
+
const items: CartItem[] = (data.items ?? []).map((item: any): CartItem => {
|
|
139
|
+
const product = item.product ?? {};
|
|
140
|
+
const priceVal = item.prices?.price?.value ?? product.price_range?.minimum_price?.final_price?.value ?? 0;
|
|
141
|
+
const totalVal = item.prices?.row_total?.value ?? priceVal * (item.quantity ?? 1);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
id: String(item.id ?? item.uid ?? ''),
|
|
145
|
+
quantity: item.quantity ?? 1,
|
|
146
|
+
cost: {
|
|
147
|
+
totalAmount: toMoney(totalVal, item.prices?.price?.currency ?? currencyCode),
|
|
148
|
+
},
|
|
149
|
+
merchandise: {
|
|
150
|
+
id: String(product.sku ?? item.id ?? ''),
|
|
151
|
+
title: product.name ?? item.product?.name ?? '',
|
|
152
|
+
selectedOptions: (item.configurable_options ?? []).map((opt: any) => ({
|
|
153
|
+
name: opt.option_label ?? '',
|
|
154
|
+
value: opt.value_label ?? '',
|
|
155
|
+
})),
|
|
156
|
+
product: {
|
|
157
|
+
id: String(product.id ?? product.uid ?? ''),
|
|
158
|
+
handle: product.url_key ?? '',
|
|
159
|
+
title: product.name ?? '',
|
|
160
|
+
featuredImage: {
|
|
161
|
+
url: product.small_image?.url ?? product.thumbnail?.url ?? '',
|
|
162
|
+
altText: product.name ?? '',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const totalVal = data.prices?.grand_total?.value ?? 0;
|
|
170
|
+
const subtotalVal = data.prices?.subtotal_excluding_tax?.value ?? data.prices?.subtotal_including_tax?.value ?? totalVal;
|
|
171
|
+
const taxVal = (data.prices?.applied_taxes ?? []).reduce((sum: number, t: any) => sum + (t.amount?.value ?? 0), 0);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
id: String(data.id ?? ''),
|
|
175
|
+
checkoutUrl: '',
|
|
176
|
+
totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
|
|
177
|
+
cost: {
|
|
178
|
+
subtotalAmount: toMoney(subtotalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
179
|
+
totalAmount: toMoney(totalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
180
|
+
totalTaxAmount: toMoney(taxVal, currencyCode),
|
|
181
|
+
},
|
|
182
|
+
lines: items,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================
|
|
187
|
+
// Customer Transforms
|
|
188
|
+
// ============================================
|
|
189
|
+
|
|
190
|
+
export function transformCustomer(data: any): Customer {
|
|
191
|
+
return {
|
|
192
|
+
id: String(data.id ?? ''),
|
|
193
|
+
email: data.email ?? '',
|
|
194
|
+
firstName: data.firstname ?? data.first_name ?? '',
|
|
195
|
+
lastName: data.lastname ?? data.last_name ?? '',
|
|
196
|
+
phone: data.phone ?? '',
|
|
197
|
+
hasAccount: true,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function transformAddress(addr: any): Address {
|
|
202
|
+
return {
|
|
203
|
+
id: String(addr.id ?? ''),
|
|
204
|
+
firstName: addr.firstname ?? '',
|
|
205
|
+
lastName: addr.lastname ?? '',
|
|
206
|
+
company: addr.company ?? '',
|
|
207
|
+
address1: Array.isArray(addr.street) ? addr.street[0] ?? '' : addr.street ?? '',
|
|
208
|
+
address2: Array.isArray(addr.street) ? addr.street[1] ?? '' : '',
|
|
209
|
+
city: addr.city ?? '',
|
|
210
|
+
province: addr.region?.region ?? '',
|
|
211
|
+
postalCode: addr.postcode ?? '',
|
|
212
|
+
countryCode: addr.country_code ?? addr.country_id ?? '',
|
|
213
|
+
phone: addr.telephone ?? '',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ============================================
|
|
218
|
+
// Order Transforms
|
|
219
|
+
// ============================================
|
|
220
|
+
|
|
221
|
+
export function transformOrder(data: any, currencyCode: string = 'USD'): Order {
|
|
222
|
+
const items: OrderItem[] = (data.items ?? []).map((item: any) => ({
|
|
223
|
+
id: String(item.id ?? ''),
|
|
224
|
+
title: item.product_name ?? '',
|
|
225
|
+
quantity: item.quantity_ordered ?? item.quantity ?? 1,
|
|
226
|
+
price: toMoney(item.product_sale_price?.value ?? item.price ?? 0, currencyCode),
|
|
227
|
+
total: toMoney((item.product_sale_price?.value ?? item.price ?? 0) * (item.quantity_ordered ?? item.quantity ?? 1), currencyCode),
|
|
228
|
+
sku: item.product_sku ?? item.sku,
|
|
229
|
+
productId: String(item.product_id ?? ''),
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id: String(data.id ?? data.order_number ?? ''),
|
|
234
|
+
orderNumber: data.order_number ?? data.number ?? String(data.id ?? ''),
|
|
235
|
+
email: data.email ?? '',
|
|
236
|
+
status: (data.status === 'complete' ? 'completed' : data.status === 'canceled' ? 'cancelled' : data.status === 'closed' ? 'refunded' : 'pending') as any,
|
|
237
|
+
paymentStatus: (data.payment_status === 'paid' ? 'paid' : 'pending') as any,
|
|
238
|
+
fulfillmentStatus: (data.shipment_status === 'shipped' ? 'fulfilled' : 'unfulfilled') as any,
|
|
239
|
+
items,
|
|
240
|
+
total: toMoney(data.total?.grand_total?.value ?? data.grand_total ?? 0, currencyCode),
|
|
241
|
+
subtotal: toMoney(data.total?.subtotal?.value ?? data.subtotal ?? 0, currencyCode),
|
|
242
|
+
tax: toMoney(data.total?.total_tax?.value ?? data.tax_amount ?? 0, currencyCode),
|
|
243
|
+
shipping: toMoney(data.total?.total_shipping?.value ?? data.shipping_amount ?? 0, currencyCode),
|
|
244
|
+
shippingAddress: data.shipping_address ? transformAddress(data.shipping_address) : undefined,
|
|
245
|
+
billingAddress: data.billing_address ? transformAddress(data.billing_address) : undefined,
|
|
246
|
+
createdAt: data.order_date ?? data.created_at ?? new Date().toISOString(),
|
|
247
|
+
updatedAt: data.updated_at ?? data.order_date ?? new Date().toISOString(),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -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
|
})
|