@thorprovider/medusa-extended 1.0.0
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/dist/index.d.mts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +315 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +288 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
- package/src/admin.test.ts +404 -0
- package/src/admin.ts +441 -0
- package/src/index.ts +7 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for MedusaAdminClient
|
|
3
|
+
*
|
|
4
|
+
* Uses msw to intercept fetch calls and verify that each method:
|
|
5
|
+
* - Hits the correct URL and HTTP method
|
|
6
|
+
* - Sends `x-medusa-access-token`
|
|
7
|
+
* - Forwards query-string options correctly
|
|
8
|
+
* - Returns the parsed response body
|
|
9
|
+
* - Throws ProviderAPIError on non-2xx responses
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from 'vitest'
|
|
13
|
+
import { http, HttpResponse } from 'msw'
|
|
14
|
+
import { server } from '../stelorder/client.test-helpers'
|
|
15
|
+
import { MedusaAdminClient } from './admin'
|
|
16
|
+
|
|
17
|
+
const BASE = 'https://medusa.example.com'
|
|
18
|
+
const API_KEY = 'test-admin-key'
|
|
19
|
+
|
|
20
|
+
function client() {
|
|
21
|
+
return new MedusaAdminClient({ baseUrl: BASE, apiKey: API_KEY })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function requireAdminKey(request: Request) {
|
|
29
|
+
expect(request.headers.get('x-medusa-access-token')).toBe(API_KEY)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Sales Channel — Customers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
describe('MedusaAdminClient.getChannelCustomers', () => {
|
|
37
|
+
it('GET /admin/thor/sales-channels/:id/customers with defaults', async () => {
|
|
38
|
+
server.use(
|
|
39
|
+
http.get(`${BASE}/admin/thor/sales-channels/sc_01/customers`, ({ request }) => {
|
|
40
|
+
requireAdminKey(request)
|
|
41
|
+
const url = new URL(request.url)
|
|
42
|
+
expect(url.searchParams.get('limit')).toBe('20')
|
|
43
|
+
expect(url.searchParams.get('offset')).toBe('0')
|
|
44
|
+
return HttpResponse.json({ customers: [{ id: 'cus_1', email: 'a@b.com', sales_channel_ids: ['sc_01'] }], count: 1 })
|
|
45
|
+
}),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const result = await client().getChannelCustomers('sc_01')
|
|
49
|
+
expect(result.customers).toHaveLength(1)
|
|
50
|
+
expect(result.count).toBe(1)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('forwards custom limit/offset', async () => {
|
|
54
|
+
server.use(
|
|
55
|
+
http.get(`${BASE}/admin/thor/sales-channels/sc_01/customers`, ({ request }) => {
|
|
56
|
+
const url = new URL(request.url)
|
|
57
|
+
expect(url.searchParams.get('limit')).toBe('5')
|
|
58
|
+
expect(url.searchParams.get('offset')).toBe('10')
|
|
59
|
+
return HttpResponse.json({ customers: [], count: 0 })
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
await client().getChannelCustomers('sc_01', { limit: 5, offset: 10 })
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Sales Channel — API Keys
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
describe('MedusaAdminClient.getChannelApiKeys', () => {
|
|
72
|
+
it('GET /admin/thor/sales-channels/:id/api-keys', async () => {
|
|
73
|
+
server.use(
|
|
74
|
+
http.get(`${BASE}/admin/thor/sales-channels/sc_01/api-keys`, ({ request }) => {
|
|
75
|
+
requireAdminKey(request)
|
|
76
|
+
return HttpResponse.json({ api_keys: [{ id: 'apk_1', token: 'pk_live_xxx', sales_channel_id: 'sc_01' }] })
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const result = await client().getChannelApiKeys('sc_01')
|
|
81
|
+
expect(result.api_keys).toHaveLength(1)
|
|
82
|
+
expect(result.api_keys[0].id).toBe('apk_1')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Sales Channel — Categories
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
describe('MedusaAdminClient.getChannelCategories', () => {
|
|
91
|
+
it('GET /admin/thor/sales-channels/:id/categories with include_descendants', async () => {
|
|
92
|
+
server.use(
|
|
93
|
+
http.get(`${BASE}/admin/thor/sales-channels/sc_01/categories`, ({ request }) => {
|
|
94
|
+
const url = new URL(request.url)
|
|
95
|
+
expect(url.searchParams.get('include_descendants')).toBe('true')
|
|
96
|
+
return HttpResponse.json({ categories: [], count: 0 })
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const result = await client().getChannelCategories('sc_01', { include_descendants: true })
|
|
101
|
+
expect(result.categories).toHaveLength(0)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Category — Sales Channels
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
describe('MedusaAdminClient.getCategorySalesChannels', () => {
|
|
110
|
+
it('GET /admin/thor/categories/:id/sales-channels', async () => {
|
|
111
|
+
server.use(
|
|
112
|
+
http.get(`${BASE}/admin/thor/categories/cat_01/sales-channels`, ({ request }) => {
|
|
113
|
+
requireAdminKey(request)
|
|
114
|
+
return HttpResponse.json({ sales_channels: [{ id: 'sc_01', name: 'Main' }] })
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const result = await client().getCategorySalesChannels('cat_01')
|
|
119
|
+
expect(result.sales_channels).toHaveLength(1)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Category — Assign / Unassign
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
describe('MedusaAdminClient.assignCategoryToChannels', () => {
|
|
128
|
+
it('POST /admin/thor/categories/:id/sales-channels', async () => {
|
|
129
|
+
server.use(
|
|
130
|
+
http.post(`${BASE}/admin/thor/categories/cat_01/sales-channels`, async ({ request }) => {
|
|
131
|
+
requireAdminKey(request)
|
|
132
|
+
const body = await request.clone().json() as any
|
|
133
|
+
expect(body.sales_channel_ids).toEqual(['sc_01'])
|
|
134
|
+
return HttpResponse.json({ message: 'ok', linked: 1 })
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const result = await client().assignCategoryToChannels('cat_01', { sales_channel_ids: ['sc_01'] })
|
|
139
|
+
expect(result.linked).toBe(1)
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('MedusaAdminClient.unassignCategoryFromChannels', () => {
|
|
144
|
+
it('DELETE /admin/thor/categories/:id/sales-channels with body', async () => {
|
|
145
|
+
server.use(
|
|
146
|
+
http.delete(`${BASE}/admin/thor/categories/cat_01/sales-channels`, async ({ request }) => {
|
|
147
|
+
requireAdminKey(request)
|
|
148
|
+
const body = await request.clone().json() as any
|
|
149
|
+
expect(body.sales_channel_ids).toEqual(['sc_01'])
|
|
150
|
+
return HttpResponse.json({ message: 'ok', dismissed: 1 })
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
const result = await client().unassignCategoryFromChannels('cat_01', { sales_channel_ids: ['sc_01'] })
|
|
155
|
+
expect(result.dismissed).toBe(1)
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Collection — Sales Channels
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
describe('MedusaAdminClient.getCollectionSalesChannels', () => {
|
|
164
|
+
it('GET /admin/thor/collections/:id/sales-channels', async () => {
|
|
165
|
+
server.use(
|
|
166
|
+
http.get(`${BASE}/admin/thor/collections/col_01/sales-channels`, ({ request }) => {
|
|
167
|
+
requireAdminKey(request)
|
|
168
|
+
return HttpResponse.json({ sales_channels: [{ id: 'sc_01', name: 'Main' }] })
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const result = await client().getCollectionSalesChannels('col_01')
|
|
173
|
+
expect(result.sales_channels).toHaveLength(1)
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('MedusaAdminClient.assignCollectionToChannels', () => {
|
|
178
|
+
it('POST /admin/thor/collections/:id/sales-channels', async () => {
|
|
179
|
+
server.use(
|
|
180
|
+
http.post(`${BASE}/admin/thor/collections/col_01/sales-channels`, async ({ request }) => {
|
|
181
|
+
const body = await request.clone().json() as any
|
|
182
|
+
expect(body.sales_channel_ids).toEqual(['sc_01'])
|
|
183
|
+
return HttpResponse.json({ message: 'ok', linked: 1 })
|
|
184
|
+
}),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
const result = await client().assignCollectionToChannels('col_01', { sales_channel_ids: ['sc_01'] })
|
|
188
|
+
expect(result.linked).toBe(1)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('MedusaAdminClient.unassignCollectionFromChannels', () => {
|
|
193
|
+
it('DELETE /admin/thor/collections/:id/sales-channels', async () => {
|
|
194
|
+
server.use(
|
|
195
|
+
http.delete(`${BASE}/admin/thor/collections/col_01/sales-channels`, async ({ request }) => {
|
|
196
|
+
const body = await request.clone().json() as any
|
|
197
|
+
expect(body.sales_channel_ids).toEqual(['sc_01'])
|
|
198
|
+
return HttpResponse.json({ message: 'ok', dismissed: 1 })
|
|
199
|
+
}),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
const result = await client().unassignCollectionFromChannels('col_01', { sales_channel_ids: ['sc_01'] })
|
|
203
|
+
expect(result.dismissed).toBe(1)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Storefront Config
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
describe('MedusaAdminClient.getStorefrontConfig', () => {
|
|
212
|
+
it('GET /admin/thor/storefront-config/:id', async () => {
|
|
213
|
+
server.use(
|
|
214
|
+
http.get(`${BASE}/admin/thor/storefront-config/sc_01`, ({ request }) => {
|
|
215
|
+
requireAdminKey(request)
|
|
216
|
+
return HttpResponse.json({
|
|
217
|
+
storefront_config: {
|
|
218
|
+
id: 'scfg_01',
|
|
219
|
+
sales_channel_id: 'sc_01',
|
|
220
|
+
theme_accent_color: '#000',
|
|
221
|
+
logo_url: '',
|
|
222
|
+
currency_code: 'USD',
|
|
223
|
+
seo_defaults: { title: 'Store', description: '' },
|
|
224
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
225
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const result = await client().getStorefrontConfig('sc_01')
|
|
232
|
+
expect(result.storefront_config.id).toBe('scfg_01')
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe('MedusaAdminClient.updateStorefrontConfig', () => {
|
|
237
|
+
it('PUT /admin/thor/storefront-config/:id', async () => {
|
|
238
|
+
server.use(
|
|
239
|
+
http.put(`${BASE}/admin/thor/storefront-config/sc_01`, async ({ request }) => {
|
|
240
|
+
const body = await request.clone().json() as any
|
|
241
|
+
expect(body.theme_accent_color).toBe('#ff0000')
|
|
242
|
+
return HttpResponse.json({
|
|
243
|
+
storefront_config: {
|
|
244
|
+
id: 'scfg_01',
|
|
245
|
+
sales_channel_id: 'sc_01',
|
|
246
|
+
theme_accent_color: '#ff0000',
|
|
247
|
+
logo_url: '',
|
|
248
|
+
currency_code: 'USD',
|
|
249
|
+
seo_defaults: { title: 'Store', description: '' },
|
|
250
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
251
|
+
updated_at: '2026-01-02T00:00:00Z',
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
}),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const result = await client().updateStorefrontConfig('sc_01', {
|
|
258
|
+
theme_accent_color: '#ff0000',
|
|
259
|
+
logo_url: '',
|
|
260
|
+
currency_code: 'USD',
|
|
261
|
+
seo_defaults: { title: 'Store', description: '' },
|
|
262
|
+
})
|
|
263
|
+
expect(result.storefront_config.theme_accent_color).toBe('#ff0000')
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Site Config
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
describe('MedusaAdminClient.getSiteConfig', () => {
|
|
272
|
+
it('GET /admin/thor/site-config/:sales_channel_id', async () => {
|
|
273
|
+
server.use(
|
|
274
|
+
http.get(`${BASE}/admin/thor/site-config/sc_01`, ({ request }) => {
|
|
275
|
+
requireAdminKey(request)
|
|
276
|
+
return HttpResponse.json({
|
|
277
|
+
site_config: {
|
|
278
|
+
id: 'scfg_01',
|
|
279
|
+
sales_channel_id: 'sc_01',
|
|
280
|
+
version: 'v1',
|
|
281
|
+
config: { theme: 'dark' },
|
|
282
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
283
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
284
|
+
history: [],
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
}),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const result = await client().getSiteConfig('sc_01')
|
|
291
|
+
expect(result.site_config.id).toBe('scfg_01')
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe('MedusaAdminClient.updateSiteConfig', () => {
|
|
296
|
+
it('PUT /admin/thor/site-config/:sales_channel_id', async () => {
|
|
297
|
+
server.use(
|
|
298
|
+
http.put(`${BASE}/admin/thor/site-config/sc_01`, async ({ request }) => {
|
|
299
|
+
const body = await request.json() as any
|
|
300
|
+
expect(body.config).toEqual({ theme: 'light' })
|
|
301
|
+
return HttpResponse.json({
|
|
302
|
+
site_config: {
|
|
303
|
+
id: 'scfg_02',
|
|
304
|
+
sales_channel_id: 'sc_01',
|
|
305
|
+
version: 'v2',
|
|
306
|
+
config: { theme: 'light' },
|
|
307
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
308
|
+
updated_at: '2026-01-02T00:00:00Z',
|
|
309
|
+
history: [],
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
}),
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
const result = await client().updateSiteConfig('sc_01', { config: { theme: 'light' } })
|
|
316
|
+
expect(result.site_config.version).toBe('v2')
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
describe('MedusaAdminClient.getSiteConfigHistory', () => {
|
|
321
|
+
it('GET /admin/thor/site-config/:id/history with defaults', async () => {
|
|
322
|
+
server.use(
|
|
323
|
+
http.get(`${BASE}/admin/thor/site-config/sc_01/history`, ({ request }) => {
|
|
324
|
+
requireAdminKey(request)
|
|
325
|
+
const url = new URL(request.url)
|
|
326
|
+
expect(url.searchParams.get('limit')).toBe('20')
|
|
327
|
+
expect(url.searchParams.get('offset')).toBe('0')
|
|
328
|
+
return HttpResponse.json({ history: [], count: 0 })
|
|
329
|
+
}),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
const result = await client().getSiteConfigHistory('sc_01')
|
|
333
|
+
expect(result.count).toBe(0)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('forwards custom limit/offset', async () => {
|
|
337
|
+
server.use(
|
|
338
|
+
http.get(`${BASE}/admin/thor/site-config/sc_01/history`, ({ request }) => {
|
|
339
|
+
const url = new URL(request.url)
|
|
340
|
+
expect(url.searchParams.get('limit')).toBe('5')
|
|
341
|
+
expect(url.searchParams.get('offset')).toBe('10')
|
|
342
|
+
return HttpResponse.json({ history: [], count: 0 })
|
|
343
|
+
}),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
await client().getSiteConfigHistory('sc_01', { limit: 5, offset: 10 })
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
describe('MedusaAdminClient.restoreSiteConfig', () => {
|
|
351
|
+
it('POST /admin/thor/site-config/:id/restore/:version', async () => {
|
|
352
|
+
server.use(
|
|
353
|
+
http.post(`${BASE}/admin/thor/site-config/sc_01/restore/v1`, ({ request }) => {
|
|
354
|
+
requireAdminKey(request)
|
|
355
|
+
return HttpResponse.json({
|
|
356
|
+
site_config: {
|
|
357
|
+
id: 'scfg_03',
|
|
358
|
+
sales_channel_id: 'sc_01',
|
|
359
|
+
version: 'v3',
|
|
360
|
+
config: { theme: 'dark' },
|
|
361
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
362
|
+
updated_at: '2026-01-03T00:00:00Z',
|
|
363
|
+
history: [],
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
}),
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
const result = await client().restoreSiteConfig('sc_01', 'v1')
|
|
370
|
+
expect(result.site_config.version).toBe('v3')
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Error handling
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
describe('MedusaAdminClient error handling', () => {
|
|
379
|
+
it('throws ProviderAPIError on 404', async () => {
|
|
380
|
+
server.use(
|
|
381
|
+
http.get(`${BASE}/admin/thor/sales-channels/sc_missing/customers`, () =>
|
|
382
|
+
HttpResponse.json({ message: 'Sales channel not found' }, { status: 404 }),
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
await expect(client().getChannelCustomers('sc_missing')).rejects.toMatchObject({
|
|
387
|
+
name: 'ProviderAPIError',
|
|
388
|
+
provider: 'ADMIN_API_404',
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('throws ProviderAPIError on 401', async () => {
|
|
393
|
+
server.use(
|
|
394
|
+
http.get(`${BASE}/admin/thor/sales-channels/sc_01/api-keys`, () =>
|
|
395
|
+
HttpResponse.json({ message: 'Unauthorized' }, { status: 401 }),
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
await expect(client().getChannelApiKeys('sc_01')).rejects.toMatchObject({
|
|
400
|
+
name: 'ProviderAPIError',
|
|
401
|
+
provider: 'ADMIN_API_401',
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
|
+
})
|