@labdigital/commercetools-mock 1.11.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
3
  "author": "Michael van Tellingen",
4
- "version": "1.11.0",
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": ">=16",
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
- "nock": "^13.3.2",
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
- /^https:\/\/api\..*?\.commercetools.com:443$/
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 _nockScopes: {
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.mockAuthHost()
71
- this.mockApiHost()
70
+ this.clear()
71
+ this.startServer()
72
72
  }
73
73
 
74
74
  stop() {
75
- this._nockScopes.auth?.persist(false)
76
- this._nockScopes.auth = undefined
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 mockApiHost() {
161
- const app = this.app
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
- this._nockScopes.api = nock(this.options.apiHost)
164
- .persist()
165
- .get(/.*/)
166
- .reply(async function (uri) {
167
- const response = await supertest(app)
168
- .get(uri)
169
- .set(copyHeaders(this.req.headers))
170
- return [response.status, response.body]
171
- })
172
- .post(/.*/)
173
- .reply(async function (uri, body) {
174
- const response = await supertest(app)
175
- .post(uri)
176
- .set(copyHeaders(this.req.headers))
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 [response.status, response.body]
179
- })
180
- .delete(/.*/)
181
- .reply(async function (uri, body) {
182
- const response = await supertest(app)
183
- .delete(uri)
184
- .set(copyHeaders(this.req.headers))
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
- this._nockScopes.auth = nock(this.options.authHost)
194
- .persist()
195
- .post(/^\/oauth\/.*/)
196
- .reply(async function (uri, body) {
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 { afterEach, beforeEach, expect, test } from 'vitest'
4
- import nock from 'nock'
3
+ import { expect, test } from 'vitest'
5
4
  import got from 'got'
6
5
 
7
- beforeEach(() => {
8
- nock.disableNetConnect()
9
- nock.enableNetConnect('127.0.0.1') // supertest
10
- })
11
- afterEach(() => {
12
- nock.enableNetConnect()
13
- nock.cleanAll()
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('Default mock endpoints', async () => {
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://auth.europe-west1.gcp.commercetools.com/oauth/token',
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
- 'https://api.europe-west1.gcp.commercetools.com/my-project/orders',
41
- {
42
- headers: {
43
- Authorization: `Bearer ${token}`,
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: Record<string, string>) => {
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
- Object.entries(headers).forEach(([key, value]) => {
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
  }