@labdigital/commercetools-mock 1.11.0 → 2.1.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.cts CHANGED
@@ -630,7 +630,7 @@ declare class CommercetoolsMock {
630
630
  options: CommercetoolsMockOptions;
631
631
  private _storage;
632
632
  private _oauth2;
633
- private _nockScopes;
633
+ private _mswServer;
634
634
  private _services;
635
635
  private _repositories;
636
636
  private _projectService?;
@@ -641,8 +641,7 @@ declare class CommercetoolsMock {
641
641
  project(projectKey?: string): ProjectAPI;
642
642
  runServer(port?: number, options?: AppOptions): void;
643
643
  private createApp;
644
- private mockApiHost;
645
- private mockAuthHost;
644
+ private startServer;
646
645
  }
647
646
 
648
647
  declare const getBaseResourceProperties: () => {
package/dist/index.d.ts CHANGED
@@ -630,7 +630,7 @@ declare class CommercetoolsMock {
630
630
  options: CommercetoolsMockOptions;
631
631
  private _storage;
632
632
  private _oauth2;
633
- private _nockScopes;
633
+ private _mswServer;
634
634
  private _services;
635
635
  private _repositories;
636
636
  private _projectService?;
@@ -641,8 +641,7 @@ declare class CommercetoolsMock {
641
641
  project(projectKey?: string): ProjectAPI;
642
642
  runServer(port?: number, options?: AppOptions): void;
643
643
  private createApp;
644
- private mockApiHost;
645
- private mockAuthHost;
644
+ private startServer;
646
645
  }
647
646
 
648
647
  declare const getBaseResourceProperties: () => {
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // src/ctMock.ts
2
- import nock from "nock";
3
2
  import express2 from "express";
4
3
  import supertest from "supertest";
5
4
  import morgan from "morgan";
5
+ import { setupServer } from "msw/node";
6
+ import { http, HttpResponse } from "msw";
6
7
 
7
8
  // src/storage/abstract.ts
8
9
  var AbstractStorage = class {
@@ -15,10 +16,12 @@ import assert from "assert";
15
16
  var CommercetoolsError = class extends Error {
16
17
  info;
17
18
  statusCode;
19
+ errors;
18
20
  constructor(info, statusCode = 400) {
19
21
  super(info.message);
20
22
  this.info = info;
21
23
  this.statusCode = statusCode || 500;
24
+ this.errors = info.errors ?? [];
22
25
  }
23
26
  };
24
27
 
@@ -1218,6 +1221,16 @@ var OAuth2Store = class {
1218
1221
  this.tokens.push(token);
1219
1222
  return token;
1220
1223
  }
1224
+ getCustomerToken(scope, customerId) {
1225
+ const token = {
1226
+ access_token: randomBytes(16).toString("base64"),
1227
+ token_type: "Bearer",
1228
+ expires_in: 172800,
1229
+ scope: scope ? `${scope} custome_id:${customerId}` : `customer_id: ${customerId}`
1230
+ };
1231
+ this.tokens.push(token);
1232
+ return token;
1233
+ }
1221
1234
  validateToken(token) {
1222
1235
  if (!this.validate)
1223
1236
  return true;
@@ -1239,16 +1252,36 @@ var getBearerToken = (request) => {
1239
1252
  return void 0;
1240
1253
  };
1241
1254
 
1255
+ // src/lib/password.ts
1256
+ var hashPassword = (clearPassword) => Buffer.from(clearPassword).toString("base64");
1257
+
1242
1258
  // src/oauth/server.ts
1243
1259
  var OAuth2Server = class {
1244
1260
  store;
1261
+ customerRepository;
1245
1262
  constructor(options) {
1246
1263
  this.store = new OAuth2Store(options.validate);
1247
1264
  }
1265
+ setCustomerRepository(repository) {
1266
+ this.customerRepository = repository;
1267
+ }
1248
1268
  createRouter() {
1249
1269
  const router = express.Router();
1250
1270
  router.use(bodyParser.urlencoded({ extended: true }));
1271
+ router.use(this.validateClientCredentials.bind(this));
1251
1272
  router.post("/token", this.tokenHandler.bind(this));
1273
+ router.post(
1274
+ "/:projectKey/customers/token",
1275
+ this.customerTokenHandler.bind(this)
1276
+ );
1277
+ router.post(
1278
+ "/:projectKey/in-store/key=:storeKey/customers/token",
1279
+ this.inStoreCustomerTokenHandler.bind(this)
1280
+ );
1281
+ router.post(
1282
+ "/:projectKey/anonymous/token",
1283
+ this.anonymousTokenHandler.bind(this)
1284
+ );
1252
1285
  return router;
1253
1286
  }
1254
1287
  createMiddleware() {
@@ -1279,7 +1312,7 @@ var OAuth2Server = class {
1279
1312
  next();
1280
1313
  };
1281
1314
  }
1282
- async tokenHandler(request, response, next) {
1315
+ async validateClientCredentials(request, response, next) {
1283
1316
  const authHeader = request.header("Authorization");
1284
1317
  if (!authHeader) {
1285
1318
  return next(
@@ -1304,6 +1337,13 @@ var OAuth2Server = class {
1304
1337
  )
1305
1338
  );
1306
1339
  }
1340
+ request.credentials = {
1341
+ clientId: credentials.name,
1342
+ clientSecret: credentials.pass
1343
+ };
1344
+ next();
1345
+ }
1346
+ async tokenHandler(request, response, next) {
1307
1347
  const grantType = request.query.grant_type || request.body.grant_type;
1308
1348
  if (!grantType) {
1309
1349
  return next(
@@ -1318,8 +1358,15 @@ var OAuth2Server = class {
1318
1358
  }
1319
1359
  if (grantType === "client_credentials") {
1320
1360
  const token = this.store.getClientToken(
1321
- credentials.name,
1322
- credentials.pass,
1361
+ request.credentials.clientId,
1362
+ request.credentials.clientSecret,
1363
+ request.query.scope?.toString()
1364
+ );
1365
+ return response.status(200).send(token);
1366
+ } else if (grantType === "refresh_token") {
1367
+ const token = this.store.getClientToken(
1368
+ request.credentials.clientId,
1369
+ request.credentials.clientSecret,
1323
1370
  request.query.scope?.toString()
1324
1371
  );
1325
1372
  return response.status(200).send(token);
@@ -1335,6 +1382,69 @@ var OAuth2Server = class {
1335
1382
  );
1336
1383
  }
1337
1384
  }
1385
+ async customerTokenHandler(request, response, next) {
1386
+ const grantType = request.query.grant_type || request.body.grant_type;
1387
+ if (!grantType) {
1388
+ return next(
1389
+ new CommercetoolsError(
1390
+ {
1391
+ code: "invalid_request",
1392
+ message: "Missing required parameter: grant_type."
1393
+ },
1394
+ 400
1395
+ )
1396
+ );
1397
+ }
1398
+ if (grantType === "password") {
1399
+ const username = request.query.username || request.body.username;
1400
+ const password = hashPassword(
1401
+ request.query.password || request.body.password
1402
+ );
1403
+ const scope = request.query.scope?.toString() || request.body.scope?.toString();
1404
+ const result = this.customerRepository.query(
1405
+ { projectKey: request.params.projectKey },
1406
+ {
1407
+ where: [`email = "${username}"`, `password = "${password}"`]
1408
+ }
1409
+ );
1410
+ if (result.count === 0) {
1411
+ return next(
1412
+ new CommercetoolsError(
1413
+ {
1414
+ code: "invalid_customer_account_credentials",
1415
+ message: "Customer account with the given credentials not found."
1416
+ },
1417
+ 400
1418
+ )
1419
+ );
1420
+ }
1421
+ const customer = result.results[0];
1422
+ const token = this.store.getCustomerToken(scope, customer.id);
1423
+ return response.status(200).send(token);
1424
+ }
1425
+ }
1426
+ async inStoreCustomerTokenHandler(request, response, next) {
1427
+ return next(
1428
+ new CommercetoolsError(
1429
+ {
1430
+ code: "invalid_client",
1431
+ message: "Not implemented yet in commercetools-mock"
1432
+ },
1433
+ 401
1434
+ )
1435
+ );
1436
+ }
1437
+ async anonymousTokenHandler(request, response, next) {
1438
+ return next(
1439
+ new CommercetoolsError(
1440
+ {
1441
+ code: "invalid_client",
1442
+ message: "Not implemented yet in commercetools-mock"
1443
+ },
1444
+ 401
1445
+ )
1446
+ );
1447
+ }
1338
1448
  };
1339
1449
 
1340
1450
  // src/projectAPI.ts
@@ -1378,19 +1488,19 @@ var ProjectAPI = class {
1378
1488
 
1379
1489
  // src/lib/proxy.ts
1380
1490
  var copyHeaders = (headers) => {
1381
- const validHeaders = ["accept", "host", "authorization"];
1491
+ const validHeaders = ["accept", "host", "authorization", "content-type"];
1382
1492
  const result = {};
1383
- Object.entries(headers).forEach(([key, value]) => {
1493
+ for (const [key, value] of headers.entries()) {
1384
1494
  if (validHeaders.includes(key.toLowerCase())) {
1385
1495
  result[key] = value;
1386
1496
  }
1387
- });
1497
+ }
1388
1498
  return result;
1389
1499
  };
1390
1500
 
1391
1501
  // src/constants.ts
1392
- var DEFAULT_API_HOSTNAME = /^https:\/\/api\..*?\.commercetools.com:443$/;
1393
- var DEFAULT_AUTH_HOSTNAME = /^https:\/\/auth\..*?\.commercetools.com:443$/;
1502
+ var DEFAULT_API_HOSTNAME = "https://api.*.commercetools.com";
1503
+ var DEFAULT_AUTH_HOSTNAME = "https://auth.*.commercetools.com";
1394
1504
 
1395
1505
  // src/repositories/helpers.ts
1396
1506
  import { v4 as uuidv42 } from "uuid";
@@ -2811,11 +2921,29 @@ var CustomerRepository = class extends AbstractResourceRepository {
2811
2921
  return "customer";
2812
2922
  }
2813
2923
  create(context, draft) {
2924
+ const results = this._storage.query(context.projectKey, this.getTypeId(), {
2925
+ where: [`email="${draft.email.toLocaleLowerCase()}"`]
2926
+ });
2927
+ if (results.count > 0) {
2928
+ throw new CommercetoolsError({
2929
+ code: "CustomerAlreadyExists",
2930
+ statusCode: 400,
2931
+ message: "There is already an existing customer with the provided email.",
2932
+ errors: [
2933
+ {
2934
+ code: "DuplicateField",
2935
+ message: `Customer with email '${draft.email}' already exists.`,
2936
+ duplicateValue: draft.email,
2937
+ field: "email"
2938
+ }
2939
+ ]
2940
+ });
2941
+ }
2814
2942
  const resource = {
2815
2943
  ...getBaseResourceProperties(),
2816
2944
  authenticationMode: draft.authenticationMode || "Password",
2817
- email: draft.email,
2818
- password: draft.password ? Buffer.from(draft.password).toString("base64") : void 0,
2945
+ email: draft.email.toLowerCase(),
2946
+ password: draft.password ? hashPassword(draft.password) : void 0,
2819
2947
  isEmailVerified: draft.isEmailVerified || false,
2820
2948
  addresses: []
2821
2949
  };
@@ -2853,7 +2981,7 @@ var CustomerRepository = class extends AbstractResourceRepository {
2853
2981
  return;
2854
2982
  }
2855
2983
  if (authMode === "Password") {
2856
- resource.password = password ? Buffer.from(password).toString("base64") : void 0;
2984
+ resource.password = password ? hashPassword(password) : void 0;
2857
2985
  return;
2858
2986
  }
2859
2987
  throw new CommercetoolsError(
@@ -5983,7 +6111,7 @@ var MyCustomerService = class extends AbstractService {
5983
6111
  }
5984
6112
  signIn(request, response) {
5985
6113
  const { email, password } = request.body;
5986
- const encodedPassword = Buffer.from(password).toString("base64");
6114
+ const encodedPassword = hashPassword(password);
5987
6115
  const result = this.repository.query(getRepositoryContext(request), {
5988
6116
  where: [`email = "${email}"`, `password = "${encodedPassword}"`]
5989
6117
  });
@@ -6365,12 +6493,13 @@ var DEFAULT_OPTIONS = {
6365
6493
  authHost: DEFAULT_AUTH_HOSTNAME,
6366
6494
  silent: false
6367
6495
  };
6496
+ var _globalListeners = [];
6368
6497
  var CommercetoolsMock = class {
6369
6498
  app;
6370
6499
  options;
6371
6500
  _storage;
6372
6501
  _oauth2;
6373
- _nockScopes = { auth: void 0, api: void 0 };
6502
+ _mswServer = void 0;
6374
6503
  _services;
6375
6504
  _repositories;
6376
6505
  _projectService;
@@ -6387,16 +6516,15 @@ var CommercetoolsMock = class {
6387
6516
  this.app = this.createApp({ silent: this.options.silent });
6388
6517
  }
6389
6518
  start() {
6390
- this.mockAuthHost();
6391
- this.mockApiHost();
6519
+ this.clear();
6520
+ this.startServer();
6392
6521
  }
6393
6522
  stop() {
6394
- this._nockScopes.auth?.persist(false);
6395
- this._nockScopes.auth = void 0;
6396
- this._nockScopes.api?.persist(false);
6397
- this._nockScopes.api = void 0;
6523
+ this._mswServer?.close();
6524
+ this._mswServer = void 0;
6398
6525
  }
6399
6526
  clear() {
6527
+ this._mswServer?.resetHandlers();
6400
6528
  this._storage.clear();
6401
6529
  }
6402
6530
  project(projectKey) {
@@ -6420,6 +6548,7 @@ var CommercetoolsMock = class {
6420
6548
  }
6421
6549
  createApp(options) {
6422
6550
  this._repositories = createRepositories(this._storage);
6551
+ this._oauth2.setCustomerRepository(this._repositories.customer);
6423
6552
  const app = express2();
6424
6553
  const projectRouter = express2.Router({ mergeParams: true });
6425
6554
  projectRouter.use(express2.json());
@@ -6445,6 +6574,13 @@ var CommercetoolsMock = class {
6445
6574
  );
6446
6575
  app.use((err, req, resp, next) => {
6447
6576
  if (err instanceof CommercetoolsError) {
6577
+ if (err.errors?.length > 0) {
6578
+ return resp.status(err.statusCode).send({
6579
+ statusCode: err.statusCode,
6580
+ message: err.message,
6581
+ errors: err.errors
6582
+ });
6583
+ }
6448
6584
  return resp.status(err.statusCode).send({
6449
6585
  statusCode: err.statusCode,
6450
6586
  message: err.message,
@@ -6459,25 +6595,65 @@ var CommercetoolsMock = class {
6459
6595
  });
6460
6596
  return app;
6461
6597
  }
6462
- mockApiHost() {
6463
- const app = this.app;
6464
- this._nockScopes.api = nock(this.options.apiHost).persist().get(/.*/).reply(async function(uri) {
6465
- const response = await supertest(app).get(uri).set(copyHeaders(this.req.headers));
6466
- return [response.status, response.body];
6467
- }).post(/.*/).reply(async function(uri, body) {
6468
- const response = await supertest(app).post(uri).set(copyHeaders(this.req.headers)).send(body);
6469
- return [response.status, response.body];
6470
- }).delete(/.*/).reply(async function(uri, body) {
6471
- const response = await supertest(app).delete(uri).set(copyHeaders(this.req.headers)).send(body);
6472
- return [response.status, response.body];
6473
- });
6474
- }
6475
- mockAuthHost() {
6598
+ startServer() {
6599
+ if (_globalListeners.length > 0) {
6600
+ if (this._mswServer !== void 0) {
6601
+ throw new Error("Server already started");
6602
+ } else {
6603
+ console.warn("Server wasn't stopped properly, clearing");
6604
+ _globalListeners.forEach((listener) => listener.close());
6605
+ }
6606
+ }
6476
6607
  const app = this.app;
6477
- this._nockScopes.auth = nock(this.options.authHost).persist().post(/^\/oauth\/.*/).reply(async function(uri, body) {
6478
- const response = await supertest(app).post(uri + "?" + body).set(copyHeaders(this.req.headers)).send();
6479
- return [response.status, response.body];
6608
+ this._mswServer = setupServer(
6609
+ http.post(`${this.options.authHost}/oauth/*`, async ({ request }) => {
6610
+ const text = await request.text();
6611
+ const url = new URL(request.url);
6612
+ const res = await supertest(app).post(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(text);
6613
+ return new HttpResponse(res.text, {
6614
+ status: res.status,
6615
+ headers: res.headers
6616
+ });
6617
+ }),
6618
+ http.get(`${this.options.apiHost}/*`, async ({ request }) => {
6619
+ const body = await request.text();
6620
+ const url = new URL(request.url);
6621
+ const res = await supertest(app).get(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(body);
6622
+ return new HttpResponse(res.text, {
6623
+ status: res.status,
6624
+ headers: res.headers
6625
+ });
6626
+ }),
6627
+ http.post(`${this.options.apiHost}/*`, async ({ request }) => {
6628
+ const body = await request.text();
6629
+ const url = new URL(request.url);
6630
+ const res = await supertest(app).post(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(body);
6631
+ return new HttpResponse(res.text, {
6632
+ status: res.status,
6633
+ headers: res.headers
6634
+ });
6635
+ }),
6636
+ http.delete(`${this.options.apiHost}/*`, async ({ request }) => {
6637
+ const body = await request.text();
6638
+ const url = new URL(request.url);
6639
+ const res = await supertest(app).delete(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(body);
6640
+ return new HttpResponse(res.text, {
6641
+ status: res.status,
6642
+ headers: res.headers
6643
+ });
6644
+ })
6645
+ );
6646
+ this._mswServer.listen({
6647
+ // We need to allow requests done by supertest
6648
+ onUnhandledRequest: (request, print) => {
6649
+ const url = new URL(request.url);
6650
+ if (url.hostname === "127.0.0.1") {
6651
+ return;
6652
+ }
6653
+ print.error();
6654
+ }
6480
6655
  });
6656
+ _globalListeners.push(this._mswServer);
6481
6657
  }
6482
6658
  };
6483
6659
  export {