@labdigital/commercetools-mock 1.10.0 → 2.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.cjs +171 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +171 -79
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +2 -4
- package/src/ctMock.ts +81 -50
- package/src/index.test.ts +54 -20
- package/src/lib/proxy.ts +4 -4
- package/src/repositories/cart.ts +48 -0
- package/src/repositories/product-selection.ts +14 -5
- package/src/services/cart.test.ts +87 -0
- package/src/services/index.ts +9 -4
- package/src/services/product-selection.test.ts +36 -0
- package/src/services/product-selection.ts +16 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@labdigital/commercetools-mock",
|
|
3
3
|
"author": "Michael van Tellingen",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=18",
|
|
23
23
|
"pnpm": ">=8.6.5"
|
|
24
24
|
},
|
|
25
25
|
"packageManager": "pnpm@8.6.5",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"express": "^4.18.2",
|
|
40
40
|
"lodash.isequal": "^4.5.0",
|
|
41
41
|
"morgan": "^1.10.0",
|
|
42
|
-
"
|
|
42
|
+
"msw": "^2.0.0",
|
|
43
43
|
"supertest": "^6.3.3",
|
|
44
44
|
"uuid": "^9.0.0"
|
|
45
45
|
},
|
package/src/constants.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
export const DEFAULT_API_HOSTNAME =
|
|
2
|
-
|
|
3
|
-
export const DEFAULT_AUTH_HOSTNAME =
|
|
4
|
-
/^https:\/\/auth\..*?\.commercetools.com:443$/
|
|
1
|
+
export const DEFAULT_API_HOSTNAME = 'https://api.*.commercetools.com'
|
|
2
|
+
export const DEFAULT_AUTH_HOSTNAME = 'https://auth.*.commercetools.com'
|
package/src/ctMock.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import nock from 'nock'
|
|
2
1
|
import express, { NextFunction, Request, Response } from 'express'
|
|
3
2
|
import supertest from 'supertest'
|
|
4
3
|
import morgan from 'morgan'
|
|
4
|
+
import { setupServer, SetupServer } from 'msw/node'
|
|
5
|
+
import { http, HttpResponse } from 'msw'
|
|
5
6
|
import { AbstractStorage, InMemoryStorage } from './storage/index.js'
|
|
6
7
|
import { Services } from './types.js'
|
|
7
8
|
import { CommercetoolsError } from './exceptions.js'
|
|
@@ -36,16 +37,15 @@ const DEFAULT_OPTIONS: CommercetoolsMockOptions = {
|
|
|
36
37
|
silent: false,
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
const _globalListeners: SetupServer[] = []
|
|
41
|
+
|
|
39
42
|
export class CommercetoolsMock {
|
|
40
43
|
public app: express.Express
|
|
41
44
|
public options: CommercetoolsMockOptions
|
|
42
45
|
|
|
43
46
|
private _storage: AbstractStorage
|
|
44
47
|
private _oauth2: OAuth2Server
|
|
45
|
-
private
|
|
46
|
-
auth: nock.Scope | undefined
|
|
47
|
-
api: nock.Scope | undefined
|
|
48
|
-
} = { auth: undefined, api: undefined }
|
|
48
|
+
private _mswServer: SetupServer | undefined = undefined
|
|
49
49
|
private _services: Services | null
|
|
50
50
|
private _repositories: RepositoryMap | null
|
|
51
51
|
private _projectService?: ProjectService
|
|
@@ -67,19 +67,17 @@ export class CommercetoolsMock {
|
|
|
67
67
|
|
|
68
68
|
start() {
|
|
69
69
|
// Order is important here when the hostnames match
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
70
|
+
this.clear()
|
|
71
|
+
this.startServer()
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
stop() {
|
|
75
|
-
this.
|
|
76
|
-
this.
|
|
77
|
-
|
|
78
|
-
this._nockScopes.api?.persist(false)
|
|
79
|
-
this._nockScopes.api = undefined
|
|
75
|
+
this._mswServer?.close()
|
|
76
|
+
this._mswServer = undefined
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
clear() {
|
|
80
|
+
this._mswServer?.resetHandlers()
|
|
83
81
|
this._storage.clear()
|
|
84
82
|
}
|
|
85
83
|
|
|
@@ -157,48 +155,81 @@ export class CommercetoolsMock {
|
|
|
157
155
|
return app
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
private
|
|
161
|
-
|
|
158
|
+
private startServer() {
|
|
159
|
+
// Check if there are any other servers running
|
|
160
|
+
if (_globalListeners.length > 0) {
|
|
161
|
+
if (this._mswServer !== undefined) {
|
|
162
|
+
throw new Error('Server already started')
|
|
163
|
+
} else {
|
|
164
|
+
console.warn("Server wasn't stopped properly, clearing")
|
|
165
|
+
_globalListeners.forEach((listener) => listener.close())
|
|
166
|
+
}
|
|
167
|
+
}
|
|
162
168
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.
|
|
176
|
-
|
|
169
|
+
const app = this.app
|
|
170
|
+
this._mswServer = setupServer(
|
|
171
|
+
http.post(`${this.options.authHost}/oauth/*`, async ({ request }) => {
|
|
172
|
+
const text = await request.text()
|
|
173
|
+
const url = new URL(request.url)
|
|
174
|
+
const res = await supertest(app)
|
|
175
|
+
.post(url.pathname + '?' + url.searchParams.toString())
|
|
176
|
+
.set(copyHeaders(request.headers))
|
|
177
|
+
.send(text)
|
|
178
|
+
|
|
179
|
+
return new HttpResponse(res.text, {
|
|
180
|
+
status: res.status,
|
|
181
|
+
headers: res.headers,
|
|
182
|
+
})
|
|
183
|
+
}),
|
|
184
|
+
http.get(`${this.options.apiHost}/*`, async ({ request }) => {
|
|
185
|
+
const body = await request.text()
|
|
186
|
+
const url = new URL(request.url)
|
|
187
|
+
const res = await supertest(app)
|
|
188
|
+
.get(url.pathname + '?' + url.searchParams.toString())
|
|
189
|
+
.set(copyHeaders(request.headers))
|
|
177
190
|
.send(body)
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
191
|
+
return new HttpResponse(res.text, {
|
|
192
|
+
status: res.status,
|
|
193
|
+
headers: res.headers,
|
|
194
|
+
})
|
|
195
|
+
}),
|
|
196
|
+
http.post(`${this.options.apiHost}/*`, async ({ request }) => {
|
|
197
|
+
const body = await request.text()
|
|
198
|
+
const url = new URL(request.url)
|
|
199
|
+
const res = await supertest(app)
|
|
200
|
+
.post(url.pathname + '?' + url.searchParams.toString())
|
|
201
|
+
.set(copyHeaders(request.headers))
|
|
202
|
+
.send(body)
|
|
203
|
+
return new HttpResponse(res.text, {
|
|
204
|
+
status: res.status,
|
|
205
|
+
headers: res.headers,
|
|
206
|
+
})
|
|
207
|
+
}),
|
|
208
|
+
http.delete(`${this.options.apiHost}/*`, async ({ request }) => {
|
|
209
|
+
const body = await request.text()
|
|
210
|
+
const url = new URL(request.url)
|
|
211
|
+
const res = await supertest(app)
|
|
212
|
+
.delete(url.pathname + '?' + url.searchParams.toString())
|
|
213
|
+
.set(copyHeaders(request.headers))
|
|
185
214
|
.send(body)
|
|
186
|
-
return [response.status, response.body]
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private mockAuthHost() {
|
|
191
|
-
const app = this.app
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const response = await supertest(app)
|
|
198
|
-
.post(uri + '?' + body)
|
|
199
|
-
.set(copyHeaders(this.req.headers))
|
|
200
|
-
.send()
|
|
201
|
-
return [response.status, response.body]
|
|
216
|
+
return new HttpResponse(res.text, {
|
|
217
|
+
status: res.status,
|
|
218
|
+
headers: res.headers,
|
|
219
|
+
})
|
|
202
220
|
})
|
|
221
|
+
)
|
|
222
|
+
this._mswServer.listen({
|
|
223
|
+
// We need to allow requests done by supertest
|
|
224
|
+
onUnhandledRequest: (request, print) => {
|
|
225
|
+
const url = new URL(request.url)
|
|
226
|
+
if (url.hostname === '127.0.0.1') {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
print.error()
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
_globalListeners.push(this._mswServer)
|
|
203
234
|
}
|
|
204
235
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -1,27 +1,63 @@
|
|
|
1
1
|
import { type InvalidTokenError } from '@commercetools/platform-sdk'
|
|
2
2
|
import { CommercetoolsMock } from './index.js'
|
|
3
|
-
import {
|
|
4
|
-
import nock from 'nock'
|
|
3
|
+
import { expect, test } from 'vitest'
|
|
5
4
|
import got from 'got'
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
test('node:fetch client', async () => {
|
|
7
|
+
const ctMock = new CommercetoolsMock({
|
|
8
|
+
enableAuthentication: true,
|
|
9
|
+
validateCredentials: true,
|
|
10
|
+
apiHost: 'https://localhost',
|
|
11
|
+
authHost: 'https://localhost:8080',
|
|
12
|
+
})
|
|
13
|
+
ctMock.start()
|
|
14
|
+
|
|
15
|
+
const authHeader = 'Basic ' + Buffer.from('foo:bar').toString('base64')
|
|
16
|
+
let response = await fetch('https://localhost:8080/oauth/token', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
20
|
+
Authorization: authHeader,
|
|
21
|
+
},
|
|
22
|
+
body: new URLSearchParams({
|
|
23
|
+
grant_type: 'client_credentials',
|
|
24
|
+
scope: 'manage_project:commercetools-node-mock',
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const authBody = await response.json()
|
|
29
|
+
expect(response.status).toBe(200)
|
|
30
|
+
|
|
31
|
+
const token = authBody.access_token
|
|
32
|
+
response = await fetch('https://localhost/my-project/orders', {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${token}`,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const body = await response.json()
|
|
39
|
+
expect(response.status).toBe(200)
|
|
40
|
+
expect(body).toStrictEqual({
|
|
41
|
+
count: 0,
|
|
42
|
+
total: 0,
|
|
43
|
+
offset: 0,
|
|
44
|
+
limit: 20,
|
|
45
|
+
results: [],
|
|
46
|
+
})
|
|
47
|
+
ctMock.stop()
|
|
14
48
|
})
|
|
15
49
|
|
|
16
|
-
test('
|
|
50
|
+
test('got client', async () => {
|
|
17
51
|
const ctMock = new CommercetoolsMock({
|
|
18
52
|
enableAuthentication: true,
|
|
19
53
|
validateCredentials: true,
|
|
54
|
+
apiHost: 'https://localhost',
|
|
55
|
+
authHost: 'https://localhost:8080',
|
|
20
56
|
})
|
|
21
57
|
ctMock.start()
|
|
22
58
|
|
|
23
59
|
let response = await got.post<{ access_token: string }>(
|
|
24
|
-
'https://
|
|
60
|
+
'https://localhost:8080/oauth/token',
|
|
25
61
|
{
|
|
26
62
|
searchParams: {
|
|
27
63
|
grant_type: 'client_credentials',
|
|
@@ -36,15 +72,12 @@ test('Default mock endpoints', async () => {
|
|
|
36
72
|
|
|
37
73
|
const token = response.body.access_token
|
|
38
74
|
expect(response.body.access_token).toBeDefined()
|
|
39
|
-
response = await got.get(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
responseType: 'json',
|
|
46
|
-
}
|
|
47
|
-
)
|
|
75
|
+
response = await got.get('https://localhost/my-project/orders', {
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${token}`,
|
|
78
|
+
},
|
|
79
|
+
responseType: 'json',
|
|
80
|
+
})
|
|
48
81
|
expect(response.statusCode).toBe(200)
|
|
49
82
|
expect(response.body).toStrictEqual({
|
|
50
83
|
count: 0,
|
|
@@ -192,6 +225,7 @@ test('apiHost mock proxy: querystring', async () => {
|
|
|
192
225
|
expand: 'custom.type',
|
|
193
226
|
},
|
|
194
227
|
})
|
|
228
|
+
|
|
195
229
|
expect(response.statusCode).toBe(200)
|
|
196
230
|
expect(response.body).toStrictEqual({
|
|
197
231
|
count: 0,
|
package/src/lib/proxy.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export const copyHeaders = (headers:
|
|
2
|
-
const validHeaders = ['accept', 'host', 'authorization']
|
|
1
|
+
export const copyHeaders = (headers: Headers) => {
|
|
2
|
+
const validHeaders = ['accept', 'host', 'authorization', 'content-type']
|
|
3
3
|
const result: Record<string, string> = {}
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
for (const [key, value] of headers.entries()) {
|
|
6
6
|
if (validHeaders.includes(key.toLowerCase())) {
|
|
7
7
|
result[key] = value
|
|
8
8
|
}
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
10
|
|
|
11
11
|
return result
|
|
12
12
|
}
|
package/src/repositories/cart.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
Cart,
|
|
5
5
|
CartAddLineItemAction,
|
|
6
6
|
CartChangeLineItemQuantityAction,
|
|
7
|
+
CartAddItemShippingAddressAction,
|
|
8
|
+
CartSetLineItemShippingDetailsAction,
|
|
7
9
|
CartDraft,
|
|
8
10
|
CartRemoveLineItemAction,
|
|
9
11
|
CartSetBillingAddressAction,
|
|
@@ -18,6 +20,7 @@ import type {
|
|
|
18
20
|
GeneralError,
|
|
19
21
|
LineItem,
|
|
20
22
|
LineItemDraft,
|
|
23
|
+
ItemShippingDetails,
|
|
21
24
|
Price,
|
|
22
25
|
Product,
|
|
23
26
|
ProductPagedQueryResponse,
|
|
@@ -216,6 +219,20 @@ export class CartRepository extends AbstractResourceRepository<'cart'> {
|
|
|
216
219
|
// Update cart total price
|
|
217
220
|
resource.totalPrice.centAmount = calculateCartTotalPrice(resource)
|
|
218
221
|
},
|
|
222
|
+
addItemShippingAddress: (
|
|
223
|
+
context: RepositoryContext,
|
|
224
|
+
resource: Writable<Cart>,
|
|
225
|
+
{ action, address }: CartAddItemShippingAddressAction
|
|
226
|
+
) => {
|
|
227
|
+
const newAddress = createAddress(
|
|
228
|
+
address,
|
|
229
|
+
context.projectKey,
|
|
230
|
+
this._storage
|
|
231
|
+
)
|
|
232
|
+
if (newAddress) {
|
|
233
|
+
resource.itemShippingAddresses.push(newAddress)
|
|
234
|
+
}
|
|
235
|
+
},
|
|
219
236
|
changeLineItemQuantity: (
|
|
220
237
|
context: RepositoryContext,
|
|
221
238
|
resource: Writable<Cart>,
|
|
@@ -392,6 +409,37 @@ export class CartRepository extends AbstractResourceRepository<'cart'> {
|
|
|
392
409
|
) => {
|
|
393
410
|
resource.locale = locale
|
|
394
411
|
},
|
|
412
|
+
setLineItemShippingDetails: (
|
|
413
|
+
context: RepositoryContext,
|
|
414
|
+
resource: Writable<Cart>,
|
|
415
|
+
{
|
|
416
|
+
action,
|
|
417
|
+
shippingDetails,
|
|
418
|
+
lineItemId,
|
|
419
|
+
lineItemKey,
|
|
420
|
+
}: CartSetLineItemShippingDetailsAction
|
|
421
|
+
) => {
|
|
422
|
+
const lineItem = resource.lineItems.find(
|
|
423
|
+
(x) =>
|
|
424
|
+
(lineItemId && x.id === lineItemId) ||
|
|
425
|
+
(lineItemKey && x.key === lineItemKey)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if (!lineItem) {
|
|
429
|
+
// Check if line item is found
|
|
430
|
+
throw new CommercetoolsError<GeneralError>({
|
|
431
|
+
code: 'General',
|
|
432
|
+
message: lineItemKey
|
|
433
|
+
? `A line item with key '${lineItemKey}' not found.`
|
|
434
|
+
: `A line item with ID '${lineItemId}' not found.`,
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
lineItem.shippingDetails = {
|
|
439
|
+
...shippingDetails,
|
|
440
|
+
valid: true,
|
|
441
|
+
} as ItemShippingDetails
|
|
442
|
+
},
|
|
395
443
|
setShippingAddress: (
|
|
396
444
|
context: RepositoryContext,
|
|
397
445
|
resource: Writable<Cart>,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ProductSelection,
|
|
3
|
+
ProductSelectionChangeNameAction,
|
|
3
4
|
ProductSelectionDraft,
|
|
4
|
-
|
|
5
|
-
ReviewUpdateAction,
|
|
5
|
+
ProductSelectionUpdateAction,
|
|
6
6
|
} from '@commercetools/platform-sdk'
|
|
7
7
|
import { getBaseResourceProperties } from '../helpers.js'
|
|
8
8
|
import type { Writable } from '../types.js'
|
|
@@ -20,6 +20,7 @@ export class ProductSelectionRepository extends AbstractResourceRepository<'prod
|
|
|
20
20
|
const resource: ProductSelection = {
|
|
21
21
|
...getBaseResourceProperties(),
|
|
22
22
|
productCount: 0,
|
|
23
|
+
key: draft.key,
|
|
23
24
|
name: draft.name,
|
|
24
25
|
type: 'individual',
|
|
25
26
|
mode: 'Individual',
|
|
@@ -30,12 +31,20 @@ export class ProductSelectionRepository extends AbstractResourceRepository<'prod
|
|
|
30
31
|
|
|
31
32
|
actions: Partial<
|
|
32
33
|
Record<
|
|
33
|
-
|
|
34
|
+
ProductSelectionUpdateAction['action'],
|
|
34
35
|
(
|
|
35
36
|
context: RepositoryContext,
|
|
36
|
-
resource: Writable<
|
|
37
|
+
resource: Writable<ProductSelection>,
|
|
37
38
|
action: any
|
|
38
39
|
) => void
|
|
39
40
|
>
|
|
40
|
-
> = {
|
|
41
|
+
> = {
|
|
42
|
+
changeName: (
|
|
43
|
+
context: RepositoryContext,
|
|
44
|
+
resource: Writable<ProductSelection>,
|
|
45
|
+
{ name }: ProductSelectionChangeNameAction
|
|
46
|
+
) => {
|
|
47
|
+
resource.name = name
|
|
48
|
+
},
|
|
49
|
+
}
|
|
41
50
|
}
|
|
@@ -272,6 +272,37 @@ describe('Cart Update Actions', () => {
|
|
|
272
272
|
expect(response.body.message).toBe("A product with ID '123' not found.")
|
|
273
273
|
})
|
|
274
274
|
|
|
275
|
+
test('addItemShippingAddress', async () => {
|
|
276
|
+
const product = await supertest(ctMock.app)
|
|
277
|
+
.post(`/dummy/products`)
|
|
278
|
+
.send(productDraft)
|
|
279
|
+
.then((x) => x.body)
|
|
280
|
+
|
|
281
|
+
assert(cart, 'cart not created')
|
|
282
|
+
|
|
283
|
+
const response = await supertest(ctMock.app)
|
|
284
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
285
|
+
.send({
|
|
286
|
+
version: 1,
|
|
287
|
+
actions: [
|
|
288
|
+
{
|
|
289
|
+
action: 'addItemShippingAddress',
|
|
290
|
+
address: {
|
|
291
|
+
firstName: 'John',
|
|
292
|
+
lastName: 'Doe',
|
|
293
|
+
company: 'My Company',
|
|
294
|
+
country: 'NL',
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
expect(response.body.itemShippingAddresses).toHaveLength(1)
|
|
301
|
+
expect(response.status).toBe(200)
|
|
302
|
+
expect(response.body.version).toBe(2)
|
|
303
|
+
expect(response.body.lineItems).toHaveLength(0)
|
|
304
|
+
})
|
|
305
|
+
|
|
275
306
|
test('removeLineItem', async () => {
|
|
276
307
|
const product = await supertest(ctMock.app)
|
|
277
308
|
.post(`/dummy/products`)
|
|
@@ -418,4 +449,60 @@ describe('Cart Update Actions', () => {
|
|
|
418
449
|
expect(response.body.version).toBe(2)
|
|
419
450
|
expect(response.body.shippingAddress).toEqual(address)
|
|
420
451
|
})
|
|
452
|
+
|
|
453
|
+
test('setLineItemShippingDetails', async () => {
|
|
454
|
+
const product = await supertest(ctMock.app)
|
|
455
|
+
.post(`/dummy/products`)
|
|
456
|
+
.send(productDraft)
|
|
457
|
+
.then((x) => x.body)
|
|
458
|
+
|
|
459
|
+
assert(cart, 'cart not created')
|
|
460
|
+
assert(product, 'product not created')
|
|
461
|
+
|
|
462
|
+
const updatedCart = await supertest(ctMock.app)
|
|
463
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
464
|
+
.send({
|
|
465
|
+
version: 1,
|
|
466
|
+
actions: [
|
|
467
|
+
{
|
|
468
|
+
action: 'addLineItem',
|
|
469
|
+
productId: product.id,
|
|
470
|
+
variantId: product.masterData.current.variants[0].id,
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
})
|
|
474
|
+
const lineItem = updatedCart.body.lineItems[0]
|
|
475
|
+
assert(lineItem, 'lineItem not created')
|
|
476
|
+
|
|
477
|
+
expect(updatedCart.body.version).toBe(2)
|
|
478
|
+
expect(updatedCart.body.lineItems).toHaveLength(1)
|
|
479
|
+
|
|
480
|
+
const response = await supertest(ctMock.app)
|
|
481
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
482
|
+
.send({
|
|
483
|
+
version: updatedCart.body.version,
|
|
484
|
+
actions: [
|
|
485
|
+
{
|
|
486
|
+
action: 'setLineItemShippingDetails',
|
|
487
|
+
lineItemId: lineItem.id,
|
|
488
|
+
shippingDetails: {
|
|
489
|
+
targets: [
|
|
490
|
+
{
|
|
491
|
+
addressKey: 'address-key',
|
|
492
|
+
quantity: 1,
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
expect(response.status).toBe(200)
|
|
501
|
+
expect(response.body.version).toBe(3)
|
|
502
|
+
expect(response.body.lineItems).toHaveLength(1)
|
|
503
|
+
|
|
504
|
+
const updatedLineItem = response.body.lineItems[0]
|
|
505
|
+
expect(updatedLineItem.shippingDetails).toBeDefined()
|
|
506
|
+
expect(updatedLineItem.shippingDetails.targets).toHaveLength(1)
|
|
507
|
+
})
|
|
421
508
|
})
|
package/src/services/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { AssociateRoleServices } from './associate-roles.js'
|
|
2
|
+
import { AttributeGroupService } from './attribute-group.js'
|
|
2
3
|
import { BusinessUnitServices } from './business-units.js'
|
|
3
|
-
import { CartService } from './cart.js'
|
|
4
4
|
import { CartDiscountService } from './cart-discount.js'
|
|
5
|
+
import { CartService } from './cart.js'
|
|
5
6
|
import { CategoryServices } from './category.js'
|
|
6
7
|
import { ChannelService } from './channel.js'
|
|
7
8
|
import { CustomObjectService } from './custom-object.js'
|
|
8
|
-
import { CustomerService } from './customer.js'
|
|
9
9
|
import { CustomerGroupService } from './customer-group.js'
|
|
10
|
+
import { CustomerService } from './customer.js'
|
|
10
11
|
import { DiscountCodeService } from './discount-code.js'
|
|
11
12
|
import { ExtensionServices } from './extension.js'
|
|
12
13
|
import { InventoryEntryService } from './inventory-entry.js'
|
|
@@ -16,10 +17,11 @@ import { MyOrderService } from './my-order.js'
|
|
|
16
17
|
import { MyPaymentService } from './my-payment.js'
|
|
17
18
|
import { OrderService } from './order.js'
|
|
18
19
|
import { PaymentService } from './payment.js'
|
|
19
|
-
import { ProductService } from './product.js'
|
|
20
20
|
import { ProductDiscountService } from './product-discount.js'
|
|
21
21
|
import { ProductProjectionService } from './product-projection.js'
|
|
22
|
+
import { ProductSelectionService } from './product-selection.js'
|
|
22
23
|
import { ProductTypeService } from './product-type.js'
|
|
24
|
+
import { ProductService } from './product.js'
|
|
23
25
|
import { ShippingMethodService } from './shipping-method.js'
|
|
24
26
|
import { ShoppingListService } from './shopping-list.js'
|
|
25
27
|
import { StandAlonePriceService } from './standalone-price.js'
|
|
@@ -29,7 +31,6 @@ import { SubscriptionService } from './subscription.js'
|
|
|
29
31
|
import { TaxCategoryService } from './tax-category.js'
|
|
30
32
|
import { TypeService } from './type.js'
|
|
31
33
|
import { ZoneService } from './zone.js'
|
|
32
|
-
import { AttributeGroupService } from './attribute-group.js'
|
|
33
34
|
|
|
34
35
|
export const createServices = (router: any, repos: any) => ({
|
|
35
36
|
'associate-role': new AssociateRoleServices(router, repos['associate-role']),
|
|
@@ -74,6 +75,10 @@ export const createServices = (router: any, repos: any) => ({
|
|
|
74
75
|
router,
|
|
75
76
|
repos['product-projection']
|
|
76
77
|
),
|
|
78
|
+
'product-selection': new ProductSelectionService(
|
|
79
|
+
router,
|
|
80
|
+
repos['product-selection']
|
|
81
|
+
),
|
|
77
82
|
'shopping-list': new ShoppingListService(router, repos['shopping-list']),
|
|
78
83
|
state: new StateService(router, repos['state']),
|
|
79
84
|
store: new StoreService(router, repos['store']),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ProductSelectionDraft } from '@commercetools/platform-sdk'
|
|
2
|
+
import supertest from 'supertest'
|
|
3
|
+
import { describe, expect, test } from 'vitest'
|
|
4
|
+
import { CommercetoolsMock } from '../index.js'
|
|
5
|
+
|
|
6
|
+
const ctMock = new CommercetoolsMock()
|
|
7
|
+
|
|
8
|
+
describe('product-selection', () => {
|
|
9
|
+
test('Create product selection', async () => {
|
|
10
|
+
const draft: ProductSelectionDraft = {
|
|
11
|
+
name: {
|
|
12
|
+
en: 'foo',
|
|
13
|
+
},
|
|
14
|
+
key: 'foo',
|
|
15
|
+
}
|
|
16
|
+
const response = await supertest(ctMock.app)
|
|
17
|
+
.post('/dummy/product-selections')
|
|
18
|
+
.send(draft)
|
|
19
|
+
|
|
20
|
+
expect(response.status).toBe(201)
|
|
21
|
+
|
|
22
|
+
expect(response.body).toEqual({
|
|
23
|
+
createdAt: expect.anything(),
|
|
24
|
+
id: expect.anything(),
|
|
25
|
+
lastModifiedAt: expect.anything(),
|
|
26
|
+
name: {
|
|
27
|
+
en: 'foo',
|
|
28
|
+
},
|
|
29
|
+
key: 'foo',
|
|
30
|
+
version: 1,
|
|
31
|
+
productCount: 0,
|
|
32
|
+
type: 'individual',
|
|
33
|
+
mode: 'Individual',
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Router } from 'express'
|
|
2
|
+
import AbstractService from './abstract.js'
|
|
3
|
+
import { ProductSelectionRepository } from '../repositories/product-selection.js'
|
|
4
|
+
|
|
5
|
+
export class ProductSelectionService extends AbstractService {
|
|
6
|
+
public repository: ProductSelectionRepository
|
|
7
|
+
|
|
8
|
+
constructor(parent: Router, repository: ProductSelectionRepository) {
|
|
9
|
+
super(parent)
|
|
10
|
+
this.repository = repository
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getBasePath() {
|
|
14
|
+
return 'product-selections'
|
|
15
|
+
}
|
|
16
|
+
}
|