@thorprovider/adapters 4.2.0 → 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 -18
- package/.turbo/turbo-test.log +1 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +12 -0
- package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
- package/README.md +22 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.mts +140 -2
- package/dist/index.d.ts +140 -2
- package/dist/index.js +1075 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1070 -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 +100 -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/providers-overview.md +14 -0
- package/docs/services-overview.md +20 -2
- package/docs/update-documentation.md +10 -8
- package/package.json +11 -6
- package/publish.log +93 -0
- package/quick-reference.md +1 -0
- package/src/factory/index.ts +4 -0
- package/src/index.ts +7 -0
- package/src/providers/index.ts +3 -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/providers/stelorder/client.test-helpers.ts +3 -0
- package/src/providers/stelorder/client.test.ts +58 -0
- package/src/providers/stelorder/client.ts +326 -0
- package/src/providers/stelorder/index.test.ts +48 -0
- package/src/providers/stelorder/index.ts +685 -0
- package/src/providers/stelorder/live-smoke.test.ts +54 -0
- package/src/providers/stelorder/transforms.test.ts +103 -0
- package/src/providers/stelorder/transforms.ts +631 -0
- package/src/resilience/README.md +70 -48
- package/src/types/capabilities.types.ts +51 -0
- package/vitest.config.ts +13 -0
- package/vitest.live.config.ts +18 -0
- package/vitest.setup.ts +6 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Medusa Provider — Live Smoke Tests
|
|
3
|
+
*
|
|
4
|
+
* Exercises the adapter against a real Medusa backend (dev.erpthor.com).
|
|
5
|
+
* Guarded by MEDUSA_SMOKE_TEST=1 environment variable.
|
|
6
|
+
*
|
|
7
|
+
* Environment variables (all required when MEDUSA_SMOKE_TEST=1):
|
|
8
|
+
* MEDUSA_BASE_URL - Backend URL (e.g. https://dev.erpthor.com)
|
|
9
|
+
* MEDUSA_PUBLISHABLE_KEY - Publishable API key
|
|
10
|
+
* MEDUSA_SECRET_KEY - Secret admin API key (sk_...) for SDK Basic auth
|
|
11
|
+
* MEDUSA_SALES_CHANNEL_ID - Sales channel ID for scoped queries
|
|
12
|
+
* MEDUSA_ADMIN_EMAIL - Admin email for JWT auth (required for Thor admin endpoints)
|
|
13
|
+
* MEDUSA_ADMIN_PASSWORD - Admin password
|
|
14
|
+
*
|
|
15
|
+
* Optional:
|
|
16
|
+
* MEDUSA_TEST_CUSTOMER_EMAIL - Existing customer email for auth tests
|
|
17
|
+
* MEDUSA_TEST_CUSTOMER_PASSWORD - Existing customer password
|
|
18
|
+
*
|
|
19
|
+
* Known issue: MedusaAdminClient uses x-medusa-access-token header which Medusa v2
|
|
20
|
+
* does not support. The backend requires Authorization: Bearer <JWT>. Group D tests
|
|
21
|
+
* work around this by obtaining a JWT via login and testing Thor admin endpoints directly.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { describe, expect, it, beforeAll } from 'vitest'
|
|
25
|
+
import { MedusaProvider } from './index'
|
|
26
|
+
import { MedusaAdminClient } from './admin'
|
|
27
|
+
|
|
28
|
+
const runLive = process.env.MEDUSA_SMOKE_TEST === '1';
|
|
29
|
+
|
|
30
|
+
function requiredEnv(name: string): string {
|
|
31
|
+
const value = process.env[name];
|
|
32
|
+
if (!value) throw new Error(`Missing required env var: ${name}`);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function optionalEnv(name: string): string | undefined {
|
|
37
|
+
return process.env[name] || undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!runLive) {
|
|
41
|
+
describe('Medusa live smoke', () => {
|
|
42
|
+
it.skip('disabled unless MEDUSA_SMOKE_TEST=1', () => {});
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
const baseUrl = requiredEnv('MEDUSA_BASE_URL');
|
|
46
|
+
const publishableKey = requiredEnv('MEDUSA_PUBLISHABLE_KEY');
|
|
47
|
+
const secretKey = requiredEnv('MEDUSA_SECRET_KEY');
|
|
48
|
+
const salesChannelId = requiredEnv('MEDUSA_SALES_CHANNEL_ID');
|
|
49
|
+
const adminEmail = requiredEnv('MEDUSA_ADMIN_EMAIL');
|
|
50
|
+
const adminPassword = requiredEnv('MEDUSA_ADMIN_PASSWORD');
|
|
51
|
+
|
|
52
|
+
// --------------------------------------------------------------------------
|
|
53
|
+
// Group A: Thor Plugin Store Endpoints (custom /store/thor/*)
|
|
54
|
+
// --------------------------------------------------------------------------
|
|
55
|
+
describe('Group A: Thor Store Plugin Endpoints', () => {
|
|
56
|
+
let provider: MedusaProvider;
|
|
57
|
+
|
|
58
|
+
beforeAll(() => {
|
|
59
|
+
provider = new MedusaProvider({
|
|
60
|
+
baseUrl,
|
|
61
|
+
publishableKey,
|
|
62
|
+
currencyCode: 'USD',
|
|
63
|
+
adminConfig: { apiKey: secretKey },
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('GET /store/thor/capabilities', async () => {
|
|
68
|
+
// capabilities is fetched in constructor, wait a tick then access
|
|
69
|
+
await new Promise((r) => setTimeout(r, 2000))
|
|
70
|
+
const caps = provider.capabilities
|
|
71
|
+
expect(caps).toBeDefined()
|
|
72
|
+
expect(caps).toHaveProperty('advancedSearch')
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('GET /store/thor/categories (channel-scoped)', async () => {
|
|
76
|
+
const categories = await provider.getCategories({ limit: 5 });
|
|
77
|
+
expect(categories).toBeInstanceOf(Array);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('GET /store/thor/config (storefront config)', async () => {
|
|
81
|
+
const config = await provider.getStorefrontConfig();
|
|
82
|
+
// May return null if no config set for this channel — should not throw
|
|
83
|
+
expect(config === null || typeof config === 'object').toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('GET /store/thor/site-config (designer config)', async () => {
|
|
87
|
+
const config = await provider.getDesignerConfig();
|
|
88
|
+
expect(config === null || typeof config === 'object').toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('GET /store/search (advanced search)', async () => {
|
|
92
|
+
// Search depends on MeiliSearch being running on the backend — may 500 if not available
|
|
93
|
+
try {
|
|
94
|
+
const result = await provider.searchProductsAdvanced({ query: 'test', first: 3 })
|
|
95
|
+
expect(result).toHaveProperty('products')
|
|
96
|
+
expect(result.products).toBeInstanceOf(Array)
|
|
97
|
+
} catch (e: any) {
|
|
98
|
+
// MeiliSearch not running is acceptable in dev
|
|
99
|
+
console.log('Search endpoint unavailable (MeiliSearch down):', e.message?.slice(0, 80));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// --------------------------------------------------------------------------
|
|
105
|
+
// Group B: Standard Medusa Store Endpoints (via SDK)
|
|
106
|
+
// --------------------------------------------------------------------------
|
|
107
|
+
describe('Group B: Medusa Store Standard Endpoints', () => {
|
|
108
|
+
let provider: MedusaProvider;
|
|
109
|
+
|
|
110
|
+
beforeAll(() => {
|
|
111
|
+
provider = new MedusaProvider({
|
|
112
|
+
baseUrl,
|
|
113
|
+
publishableKey,
|
|
114
|
+
currencyCode: 'USD',
|
|
115
|
+
adminConfig: { apiKey: secretKey },
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('GET /store/products (list)', async () => {
|
|
120
|
+
const products = await provider.getProducts({ first: 3 });
|
|
121
|
+
expect(products).toBeInstanceOf(Array);
|
|
122
|
+
expect(products.length).toBeGreaterThan(0);
|
|
123
|
+
expect(products[0]).toHaveProperty('id');
|
|
124
|
+
expect(products[0]).toHaveProperty('title');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('GET /store/products (by handle)', async () => {
|
|
128
|
+
const products = await provider.getProducts({ first: 1 });
|
|
129
|
+
expect(products.length).toBeGreaterThan(0);
|
|
130
|
+
const handle = products[0].handle;
|
|
131
|
+
expect(handle).toBeTruthy();
|
|
132
|
+
|
|
133
|
+
const product = await provider.getProduct(handle!);
|
|
134
|
+
expect(product).toBeDefined();
|
|
135
|
+
expect(product!.handle).toBe(handle);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('GET /store/collections', async () => {
|
|
139
|
+
const collections = await provider.getCollections({});
|
|
140
|
+
expect(collections).toBeInstanceOf(Array);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('GET /store/product-categories', async () => {
|
|
144
|
+
const categories = await provider.getCategories({ limit: 10 });
|
|
145
|
+
expect(categories).toBeInstanceOf(Array);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('GET /store/regions', async () => {
|
|
149
|
+
const regions = await provider.getRegions();
|
|
150
|
+
expect(regions).toBeInstanceOf(Array);
|
|
151
|
+
expect(regions.length).toBeGreaterThan(0);
|
|
152
|
+
expect(regions[0]).toHaveProperty('id');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('GET /store/regions/:id (countries)', async () => {
|
|
156
|
+
const regions = await provider.getRegions();
|
|
157
|
+
expect(regions.length).toBeGreaterThan(0);
|
|
158
|
+
|
|
159
|
+
const countries = await provider.getRegionCountries(regions[0].id);
|
|
160
|
+
expect(countries).toBeInstanceOf(Array);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('Cart lifecycle', () => {
|
|
164
|
+
let cartId: string;
|
|
165
|
+
|
|
166
|
+
it('POST /store/carts (create)', async () => {
|
|
167
|
+
const cart = await provider.createCart({})
|
|
168
|
+
expect(cart).toHaveProperty('id')
|
|
169
|
+
expect(cart.id).toBeTruthy()
|
|
170
|
+
cartId = cart.id as string
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('GET /store/carts/:id (retrieve)', async () => {
|
|
174
|
+
expect(cartId).toBeTruthy()
|
|
175
|
+
const cart = await provider.getCart(cartId as string)
|
|
176
|
+
expect(cart).toHaveProperty('id')
|
|
177
|
+
expect(cart!.id).toBe(cartId)
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('POST /store/carts/:id/line-items (add item)', async () => {
|
|
181
|
+
expect(cartId).toBeTruthy()
|
|
182
|
+
const products = await provider.getProducts({ first: 1 })
|
|
183
|
+
expect(products.length).toBeGreaterThan(0)
|
|
184
|
+
const variant = products[0].variants?.[0]
|
|
185
|
+
expect(variant).toBeDefined()
|
|
186
|
+
|
|
187
|
+
const cart = await provider.addToCart(cartId as string, [{
|
|
188
|
+
merchandiseId: variant!.id,
|
|
189
|
+
quantity: 1,
|
|
190
|
+
}])
|
|
191
|
+
expect(cart.lines.length).toBeGreaterThan(0)
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('POST /store/carts/:id/line-items/:lineId (update quantity)', async () => {
|
|
195
|
+
expect(cartId).toBeTruthy()
|
|
196
|
+
const cart = await provider.getCart(cartId as string)
|
|
197
|
+
expect(cart!.lines.length).toBeGreaterThan(0)
|
|
198
|
+
const lineId = cart!.lines[0].id as string
|
|
199
|
+
|
|
200
|
+
const updated = await provider.updateCart(cartId as string, [{
|
|
201
|
+
id: lineId,
|
|
202
|
+
merchandiseId: cart!.lines[0].merchandise.id,
|
|
203
|
+
quantity: 2,
|
|
204
|
+
}])
|
|
205
|
+
const line = updated.lines.find((l) => l.id === lineId)
|
|
206
|
+
expect(line?.quantity).toBe(2)
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('DELETE /store/carts/:id/line-items/:lineId (remove item)', async () => {
|
|
210
|
+
expect(cartId).toBeTruthy()
|
|
211
|
+
const cart = await provider.getCart(cartId as string)
|
|
212
|
+
expect(cart!.lines.length).toBeGreaterThan(0)
|
|
213
|
+
const lineId = cart!.lines[0].id as string
|
|
214
|
+
|
|
215
|
+
const updated = await provider.removeFromCart(cartId as string, [lineId])
|
|
216
|
+
expect(updated.lines.find((l) => l.id === lineId)).toBeUndefined()
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('GET /store/payment-providers', async () => {
|
|
221
|
+
const regions = await provider.getRegions()
|
|
222
|
+
expect(regions.length).toBeGreaterThan(0)
|
|
223
|
+
|
|
224
|
+
const methods = await provider.getPaymentMethods({ regionId: regions[0].id })
|
|
225
|
+
expect(methods).toBeInstanceOf(Array)
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// --------------------------------------------------------------------------
|
|
230
|
+
// Group C: Customer Authentication (login, profile, addresses)
|
|
231
|
+
// --------------------------------------------------------------------------
|
|
232
|
+
describe('Group C: Customer Authentication', () => {
|
|
233
|
+
const testEmail = optionalEnv('MEDUSA_TEST_CUSTOMER_EMAIL')
|
|
234
|
+
const testPassword = optionalEnv('MEDUSA_TEST_CUSTOMER_PASSWORD')
|
|
235
|
+
|
|
236
|
+
it('login with test customer', async () => {
|
|
237
|
+
if (!testEmail || !testPassword) {
|
|
238
|
+
console.log('Skipping: MEDUSA_TEST_CUSTOMER_EMAIL/PASSWORD not set')
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const provider = new MedusaProvider({
|
|
243
|
+
baseUrl,
|
|
244
|
+
publishableKey,
|
|
245
|
+
currencyCode: 'USD',
|
|
246
|
+
auth: {
|
|
247
|
+
method: 'jwt',
|
|
248
|
+
storage: 'memory',
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const authProvider = provider.auth
|
|
253
|
+
expect(authProvider).toBeDefined()
|
|
254
|
+
|
|
255
|
+
const result = await authProvider!.login({ email: testEmail, password: testPassword })
|
|
256
|
+
expect(result.token || result.customer).toBeTruthy()
|
|
257
|
+
expect(result.customer).toBeDefined()
|
|
258
|
+
expect(result.customer!.email).toBe(testEmail)
|
|
259
|
+
|
|
260
|
+
// getCurrentCustomer
|
|
261
|
+
const customer = await authProvider!.getCurrentCustomer()
|
|
262
|
+
expect(customer).toBeDefined()
|
|
263
|
+
expect(customer!.email).toBe(testEmail)
|
|
264
|
+
|
|
265
|
+
// getAddresses
|
|
266
|
+
const addresses = await authProvider!.getAddresses()
|
|
267
|
+
expect(addresses).toBeInstanceOf(Array)
|
|
268
|
+
|
|
269
|
+
// Logout
|
|
270
|
+
await authProvider!.logout()
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// --------------------------------------------------------------------------
|
|
275
|
+
// Group D: Admin Thor Plugin Endpoints (direct HTTP with JWT)
|
|
276
|
+
//
|
|
277
|
+
// NOTE: MedusaAdminClient uses x-medusa-access-token which is NOT supported
|
|
278
|
+
// by Medusa v2. These tests use direct fetch with Bearer JWT to verify the
|
|
279
|
+
// endpoints exist and respond correctly. This documents a real adapter bug.
|
|
280
|
+
// --------------------------------------------------------------------------
|
|
281
|
+
describe('Group D: Admin Thor Plugin Endpoints (direct JWT)', () => {
|
|
282
|
+
let jwt: string
|
|
283
|
+
|
|
284
|
+
beforeAll(async () => {
|
|
285
|
+
// Obtain admin JWT
|
|
286
|
+
const res = await fetch(`${baseUrl}/auth/user/emailpass`, {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers: { 'Content-Type': 'application/json' },
|
|
289
|
+
body: JSON.stringify({ email: adminEmail, password: adminPassword }),
|
|
290
|
+
})
|
|
291
|
+
const data = await res.json() as { token?: string }
|
|
292
|
+
expect(data.token).toBeTruthy()
|
|
293
|
+
jwt = data.token as string
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
async function adminFetch(path: string): Promise<any> {
|
|
297
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
298
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
299
|
+
})
|
|
300
|
+
if (!res.ok) {
|
|
301
|
+
const text = await res.text()
|
|
302
|
+
throw new Error(`${res.status} ${path}: ${text}`)
|
|
303
|
+
}
|
|
304
|
+
return res.json()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
it('GET /admin/thor/sales-channels/:id/customers', async () => {
|
|
308
|
+
const result = await adminFetch(`/admin/thor/sales-channels/${salesChannelId}/customers?limit=5`)
|
|
309
|
+
expect(result).toHaveProperty('customers')
|
|
310
|
+
expect(result.customers).toBeInstanceOf(Array)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('GET /admin/thor/sales-channels/:id/api-keys', async () => {
|
|
314
|
+
const result = await adminFetch(`/admin/thor/sales-channels/${salesChannelId}/api-keys`)
|
|
315
|
+
expect(result).toHaveProperty('api_keys')
|
|
316
|
+
expect(result.api_keys).toBeInstanceOf(Array)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('GET /admin/thor/sales-channels/:id/categories', async () => {
|
|
320
|
+
const result = await adminFetch(`/admin/thor/sales-channels/${salesChannelId}/categories?limit=10`)
|
|
321
|
+
expect(result).toHaveProperty('categories')
|
|
322
|
+
expect(result.categories).toBeInstanceOf(Array)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('GET /admin/thor/storefront-config/:id (may 404)', async () => {
|
|
326
|
+
const res = await fetch(`${baseUrl}/admin/thor/storefront-config/${salesChannelId}`, {
|
|
327
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
328
|
+
})
|
|
329
|
+
// 200 or 404 are both acceptable
|
|
330
|
+
expect([200, 404]).toContain(res.status)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('GET /admin/thor/site-config/:id (may 404)', async () => {
|
|
334
|
+
const res = await fetch(`${baseUrl}/admin/thor/site-config/${salesChannelId}`, {
|
|
335
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
336
|
+
})
|
|
337
|
+
expect([200, 404]).toContain(res.status)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('GET /admin/thor/site-config/:id/history (may 404)', async () => {
|
|
341
|
+
const res = await fetch(`${baseUrl}/admin/thor/site-config/${salesChannelId}/history`, {
|
|
342
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
343
|
+
})
|
|
344
|
+
expect([200, 404]).toContain(res.status)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('BUG: MedusaAdminClient x-medusa-access-token returns 401', async () => {
|
|
348
|
+
// This documents the adapter bug: MedusaAdminClient sends x-medusa-access-token
|
|
349
|
+
// but Medusa v2 only accepts Authorization: Bearer for admin routes.
|
|
350
|
+
const res = await fetch(`${baseUrl}/admin/thor/sales-channels/${salesChannelId}/customers?limit=1`, {
|
|
351
|
+
headers: { 'x-medusa-access-token': jwt },
|
|
352
|
+
})
|
|
353
|
+
expect(res.status).toBe(401)
|
|
354
|
+
})
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// --------------------------------------------------------------------------
|
|
358
|
+
// Group E: Admin Standard Medusa Endpoints (via SDK with Basic auth)
|
|
359
|
+
// --------------------------------------------------------------------------
|
|
360
|
+
describe('Group E: Admin Standard Medusa Endpoints', () => {
|
|
361
|
+
let provider: MedusaProvider;
|
|
362
|
+
|
|
363
|
+
beforeAll(() => {
|
|
364
|
+
provider = new MedusaProvider({
|
|
365
|
+
baseUrl,
|
|
366
|
+
publishableKey,
|
|
367
|
+
currencyCode: 'USD',
|
|
368
|
+
adminConfig: { apiKey: secretKey },
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('GET /admin/products (list)', async () => {
|
|
373
|
+
const result = await provider.getAdminProducts({ limit: 3 })
|
|
374
|
+
expect(result).toHaveProperty('products')
|
|
375
|
+
expect(result.products).toBeInstanceOf(Array)
|
|
376
|
+
expect(result.products.length).toBeGreaterThan(0)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('GET /admin/products/:id (retrieve)', async () => {
|
|
380
|
+
const list = await provider.getAdminProducts({ limit: 1 })
|
|
381
|
+
expect(list.products.length).toBeGreaterThan(0)
|
|
382
|
+
const id = list.products[0].id
|
|
383
|
+
|
|
384
|
+
const product = await provider.getAdminProduct(id)
|
|
385
|
+
expect(product).toBeDefined()
|
|
386
|
+
expect(product!.id).toBe(id)
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('GET /admin/orders (list)', async () => {
|
|
390
|
+
const result = await provider.getAdminOrders({ limit: 3 })
|
|
391
|
+
expect(result).toHaveProperty('orders')
|
|
392
|
+
expect(result.orders).toBeInstanceOf(Array)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('GET /admin/customers (list)', async () => {
|
|
396
|
+
const result = await provider.getAdminCustomers({ limit: 3 })
|
|
397
|
+
expect(result).toHaveProperty('customers')
|
|
398
|
+
expect(result.customers).toBeInstanceOf(Array)
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
it('GET /admin/regions (list)', async () => {
|
|
402
|
+
const result = await provider.getAdminRegions()
|
|
403
|
+
expect(result).toBeInstanceOf(Array)
|
|
404
|
+
expect(result.length).toBeGreaterThan(0)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('GET /admin/stock-locations (list)', async () => {
|
|
408
|
+
const result = await provider.getStockLocations()
|
|
409
|
+
expect(result).toBeInstanceOf(Array)
|
|
410
|
+
})
|
|
411
|
+
});
|
|
412
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { http, HttpResponse } from 'msw'
|
|
3
|
+
import { server } from './client.test-helpers'
|
|
4
|
+
import { StelOrderClient } from './client'
|
|
5
|
+
|
|
6
|
+
describe('StelOrderClient', () => {
|
|
7
|
+
it('normalizes base url and sends APIKEY header', async () => {
|
|
8
|
+
server.use(
|
|
9
|
+
http.get('https://stel.example.com/app/products', ({ request }) => {
|
|
10
|
+
expect(request.headers.get('APIKEY')).toBe('secret')
|
|
11
|
+
expect(request.headers.get('accept')).toBe('application/json')
|
|
12
|
+
return HttpResponse.json([{ id: 1, name: 'P1' }])
|
|
13
|
+
}),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const client = new StelOrderClient({
|
|
17
|
+
baseUrl: 'https://stel.example.com/api/openapi.json',
|
|
18
|
+
apiKey: 'secret',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const products = await client.list<{ id: number; name: string }>('products')
|
|
22
|
+
expect(products).toEqual([{ id: 1, name: 'P1' }])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('supports create update and delete', async () => {
|
|
26
|
+
server.use(
|
|
27
|
+
http.post('https://stel.example.com/app/clients', async ({ request }) => {
|
|
28
|
+
expect(await request.json()).toEqual({ email: 'a@b.com' })
|
|
29
|
+
return HttpResponse.json({ id: 1, email: 'a@b.com' })
|
|
30
|
+
}),
|
|
31
|
+
http.put('https://stel.example.com/app/clients/1', async ({ request }) => {
|
|
32
|
+
expect(await request.json()).toEqual({ email: 'c@d.com' })
|
|
33
|
+
return HttpResponse.json({ id: 1, email: 'c@d.com' })
|
|
34
|
+
}),
|
|
35
|
+
http.delete('https://stel.example.com/app/clients/1', () => HttpResponse.text('', { status: 204 })),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const client = new StelOrderClient({ baseUrl: 'https://stel.example.com', apiKey: 'secret' })
|
|
39
|
+
await expect(client.create('clients', { email: 'a@b.com' })).resolves.toEqual({ id: 1, email: 'a@b.com' })
|
|
40
|
+
await expect(client.update('clients', 1, { email: 'c@d.com' })).resolves.toEqual({ id: 1, email: 'c@d.com' })
|
|
41
|
+
await expect(client.remove('clients', 1)).resolves.toBeUndefined()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('throws provider api error on non-ok responses', async () => {
|
|
45
|
+
server.use(
|
|
46
|
+
http.get('https://stel.example.com/app/products/1', () =>
|
|
47
|
+
HttpResponse.json({ message: 'Not found' }, { status: 404 }),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const client = new StelOrderClient({ baseUrl: 'https://stel.example.com', apiKey: 'secret' })
|
|
52
|
+
|
|
53
|
+
await expect(client.getOne('products', 1)).rejects.toMatchObject({
|
|
54
|
+
name: 'ProviderAPIError',
|
|
55
|
+
status: 404,
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
})
|