@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.cjs CHANGED
@@ -36,10 +36,11 @@ __export(src_exports, {
36
36
  module.exports = __toCommonJS(src_exports);
37
37
 
38
38
  // src/ctMock.ts
39
- var import_nock = __toESM(require("nock"), 1);
40
39
  var import_express6 = __toESM(require("express"), 1);
41
40
  var import_supertest = __toESM(require("supertest"), 1);
42
41
  var import_morgan = __toESM(require("morgan"), 1);
42
+ var import_node = require("msw/node");
43
+ var import_msw = require("msw");
43
44
 
44
45
  // src/storage/abstract.ts
45
46
  var AbstractStorage = class {
@@ -52,10 +53,12 @@ var import_assert = __toESM(require("assert"), 1);
52
53
  var CommercetoolsError = class extends Error {
53
54
  info;
54
55
  statusCode;
56
+ errors;
55
57
  constructor(info, statusCode = 400) {
56
58
  super(info.message);
57
59
  this.info = info;
58
60
  this.statusCode = statusCode || 500;
61
+ this.errors = info.errors ?? [];
59
62
  }
60
63
  };
61
64
 
@@ -1255,6 +1258,16 @@ var OAuth2Store = class {
1255
1258
  this.tokens.push(token);
1256
1259
  return token;
1257
1260
  }
1261
+ getCustomerToken(scope, customerId) {
1262
+ const token = {
1263
+ access_token: (0, import_crypto.randomBytes)(16).toString("base64"),
1264
+ token_type: "Bearer",
1265
+ expires_in: 172800,
1266
+ scope: scope ? `${scope} custome_id:${customerId}` : `customer_id: ${customerId}`
1267
+ };
1268
+ this.tokens.push(token);
1269
+ return token;
1270
+ }
1258
1271
  validateToken(token) {
1259
1272
  if (!this.validate)
1260
1273
  return true;
@@ -1276,16 +1289,36 @@ var getBearerToken = (request) => {
1276
1289
  return void 0;
1277
1290
  };
1278
1291
 
1292
+ // src/lib/password.ts
1293
+ var hashPassword = (clearPassword) => Buffer.from(clearPassword).toString("base64");
1294
+
1279
1295
  // src/oauth/server.ts
1280
1296
  var OAuth2Server = class {
1281
1297
  store;
1298
+ customerRepository;
1282
1299
  constructor(options) {
1283
1300
  this.store = new OAuth2Store(options.validate);
1284
1301
  }
1302
+ setCustomerRepository(repository) {
1303
+ this.customerRepository = repository;
1304
+ }
1285
1305
  createRouter() {
1286
1306
  const router = import_express.default.Router();
1287
1307
  router.use(import_body_parser.default.urlencoded({ extended: true }));
1308
+ router.use(this.validateClientCredentials.bind(this));
1288
1309
  router.post("/token", this.tokenHandler.bind(this));
1310
+ router.post(
1311
+ "/:projectKey/customers/token",
1312
+ this.customerTokenHandler.bind(this)
1313
+ );
1314
+ router.post(
1315
+ "/:projectKey/in-store/key=:storeKey/customers/token",
1316
+ this.inStoreCustomerTokenHandler.bind(this)
1317
+ );
1318
+ router.post(
1319
+ "/:projectKey/anonymous/token",
1320
+ this.anonymousTokenHandler.bind(this)
1321
+ );
1289
1322
  return router;
1290
1323
  }
1291
1324
  createMiddleware() {
@@ -1316,7 +1349,7 @@ var OAuth2Server = class {
1316
1349
  next();
1317
1350
  };
1318
1351
  }
1319
- async tokenHandler(request, response, next) {
1352
+ async validateClientCredentials(request, response, next) {
1320
1353
  const authHeader = request.header("Authorization");
1321
1354
  if (!authHeader) {
1322
1355
  return next(
@@ -1341,6 +1374,13 @@ var OAuth2Server = class {
1341
1374
  )
1342
1375
  );
1343
1376
  }
1377
+ request.credentials = {
1378
+ clientId: credentials.name,
1379
+ clientSecret: credentials.pass
1380
+ };
1381
+ next();
1382
+ }
1383
+ async tokenHandler(request, response, next) {
1344
1384
  const grantType = request.query.grant_type || request.body.grant_type;
1345
1385
  if (!grantType) {
1346
1386
  return next(
@@ -1355,8 +1395,15 @@ var OAuth2Server = class {
1355
1395
  }
1356
1396
  if (grantType === "client_credentials") {
1357
1397
  const token = this.store.getClientToken(
1358
- credentials.name,
1359
- credentials.pass,
1398
+ request.credentials.clientId,
1399
+ request.credentials.clientSecret,
1400
+ request.query.scope?.toString()
1401
+ );
1402
+ return response.status(200).send(token);
1403
+ } else if (grantType === "refresh_token") {
1404
+ const token = this.store.getClientToken(
1405
+ request.credentials.clientId,
1406
+ request.credentials.clientSecret,
1360
1407
  request.query.scope?.toString()
1361
1408
  );
1362
1409
  return response.status(200).send(token);
@@ -1372,6 +1419,69 @@ var OAuth2Server = class {
1372
1419
  );
1373
1420
  }
1374
1421
  }
1422
+ async customerTokenHandler(request, response, next) {
1423
+ const grantType = request.query.grant_type || request.body.grant_type;
1424
+ if (!grantType) {
1425
+ return next(
1426
+ new CommercetoolsError(
1427
+ {
1428
+ code: "invalid_request",
1429
+ message: "Missing required parameter: grant_type."
1430
+ },
1431
+ 400
1432
+ )
1433
+ );
1434
+ }
1435
+ if (grantType === "password") {
1436
+ const username = request.query.username || request.body.username;
1437
+ const password = hashPassword(
1438
+ request.query.password || request.body.password
1439
+ );
1440
+ const scope = request.query.scope?.toString() || request.body.scope?.toString();
1441
+ const result = this.customerRepository.query(
1442
+ { projectKey: request.params.projectKey },
1443
+ {
1444
+ where: [`email = "${username}"`, `password = "${password}"`]
1445
+ }
1446
+ );
1447
+ if (result.count === 0) {
1448
+ return next(
1449
+ new CommercetoolsError(
1450
+ {
1451
+ code: "invalid_customer_account_credentials",
1452
+ message: "Customer account with the given credentials not found."
1453
+ },
1454
+ 400
1455
+ )
1456
+ );
1457
+ }
1458
+ const customer = result.results[0];
1459
+ const token = this.store.getCustomerToken(scope, customer.id);
1460
+ return response.status(200).send(token);
1461
+ }
1462
+ }
1463
+ async inStoreCustomerTokenHandler(request, response, next) {
1464
+ return next(
1465
+ new CommercetoolsError(
1466
+ {
1467
+ code: "invalid_client",
1468
+ message: "Not implemented yet in commercetools-mock"
1469
+ },
1470
+ 401
1471
+ )
1472
+ );
1473
+ }
1474
+ async anonymousTokenHandler(request, response, next) {
1475
+ return next(
1476
+ new CommercetoolsError(
1477
+ {
1478
+ code: "invalid_client",
1479
+ message: "Not implemented yet in commercetools-mock"
1480
+ },
1481
+ 401
1482
+ )
1483
+ );
1484
+ }
1375
1485
  };
1376
1486
 
1377
1487
  // src/projectAPI.ts
@@ -1415,19 +1525,19 @@ var ProjectAPI = class {
1415
1525
 
1416
1526
  // src/lib/proxy.ts
1417
1527
  var copyHeaders = (headers) => {
1418
- const validHeaders = ["accept", "host", "authorization"];
1528
+ const validHeaders = ["accept", "host", "authorization", "content-type"];
1419
1529
  const result = {};
1420
- Object.entries(headers).forEach(([key, value]) => {
1530
+ for (const [key, value] of headers.entries()) {
1421
1531
  if (validHeaders.includes(key.toLowerCase())) {
1422
1532
  result[key] = value;
1423
1533
  }
1424
- });
1534
+ }
1425
1535
  return result;
1426
1536
  };
1427
1537
 
1428
1538
  // src/constants.ts
1429
- var DEFAULT_API_HOSTNAME = /^https:\/\/api\..*?\.commercetools.com:443$/;
1430
- var DEFAULT_AUTH_HOSTNAME = /^https:\/\/auth\..*?\.commercetools.com:443$/;
1539
+ var DEFAULT_API_HOSTNAME = "https://api.*.commercetools.com";
1540
+ var DEFAULT_AUTH_HOSTNAME = "https://auth.*.commercetools.com";
1431
1541
 
1432
1542
  // src/repositories/helpers.ts
1433
1543
  var import_uuid2 = require("uuid");
@@ -2848,11 +2958,29 @@ var CustomerRepository = class extends AbstractResourceRepository {
2848
2958
  return "customer";
2849
2959
  }
2850
2960
  create(context, draft) {
2961
+ const results = this._storage.query(context.projectKey, this.getTypeId(), {
2962
+ where: [`email="${draft.email.toLocaleLowerCase()}"`]
2963
+ });
2964
+ if (results.count > 0) {
2965
+ throw new CommercetoolsError({
2966
+ code: "CustomerAlreadyExists",
2967
+ statusCode: 400,
2968
+ message: "There is already an existing customer with the provided email.",
2969
+ errors: [
2970
+ {
2971
+ code: "DuplicateField",
2972
+ message: `Customer with email '${draft.email}' already exists.`,
2973
+ duplicateValue: draft.email,
2974
+ field: "email"
2975
+ }
2976
+ ]
2977
+ });
2978
+ }
2851
2979
  const resource = {
2852
2980
  ...getBaseResourceProperties(),
2853
2981
  authenticationMode: draft.authenticationMode || "Password",
2854
- email: draft.email,
2855
- password: draft.password ? Buffer.from(draft.password).toString("base64") : void 0,
2982
+ email: draft.email.toLowerCase(),
2983
+ password: draft.password ? hashPassword(draft.password) : void 0,
2856
2984
  isEmailVerified: draft.isEmailVerified || false,
2857
2985
  addresses: []
2858
2986
  };
@@ -2890,7 +3018,7 @@ var CustomerRepository = class extends AbstractResourceRepository {
2890
3018
  return;
2891
3019
  }
2892
3020
  if (authMode === "Password") {
2893
- resource.password = password ? Buffer.from(password).toString("base64") : void 0;
3021
+ resource.password = password ? hashPassword(password) : void 0;
2894
3022
  return;
2895
3023
  }
2896
3024
  throw new CommercetoolsError(
@@ -6020,7 +6148,7 @@ var MyCustomerService = class extends AbstractService {
6020
6148
  }
6021
6149
  signIn(request, response) {
6022
6150
  const { email, password } = request.body;
6023
- const encodedPassword = Buffer.from(password).toString("base64");
6151
+ const encodedPassword = hashPassword(password);
6024
6152
  const result = this.repository.query(getRepositoryContext(request), {
6025
6153
  where: [`email = "${email}"`, `password = "${encodedPassword}"`]
6026
6154
  });
@@ -6402,12 +6530,13 @@ var DEFAULT_OPTIONS = {
6402
6530
  authHost: DEFAULT_AUTH_HOSTNAME,
6403
6531
  silent: false
6404
6532
  };
6533
+ var _globalListeners = [];
6405
6534
  var CommercetoolsMock = class {
6406
6535
  app;
6407
6536
  options;
6408
6537
  _storage;
6409
6538
  _oauth2;
6410
- _nockScopes = { auth: void 0, api: void 0 };
6539
+ _mswServer = void 0;
6411
6540
  _services;
6412
6541
  _repositories;
6413
6542
  _projectService;
@@ -6424,16 +6553,15 @@ var CommercetoolsMock = class {
6424
6553
  this.app = this.createApp({ silent: this.options.silent });
6425
6554
  }
6426
6555
  start() {
6427
- this.mockAuthHost();
6428
- this.mockApiHost();
6556
+ this.clear();
6557
+ this.startServer();
6429
6558
  }
6430
6559
  stop() {
6431
- this._nockScopes.auth?.persist(false);
6432
- this._nockScopes.auth = void 0;
6433
- this._nockScopes.api?.persist(false);
6434
- this._nockScopes.api = void 0;
6560
+ this._mswServer?.close();
6561
+ this._mswServer = void 0;
6435
6562
  }
6436
6563
  clear() {
6564
+ this._mswServer?.resetHandlers();
6437
6565
  this._storage.clear();
6438
6566
  }
6439
6567
  project(projectKey) {
@@ -6457,6 +6585,7 @@ var CommercetoolsMock = class {
6457
6585
  }
6458
6586
  createApp(options) {
6459
6587
  this._repositories = createRepositories(this._storage);
6588
+ this._oauth2.setCustomerRepository(this._repositories.customer);
6460
6589
  const app = (0, import_express6.default)();
6461
6590
  const projectRouter = import_express6.default.Router({ mergeParams: true });
6462
6591
  projectRouter.use(import_express6.default.json());
@@ -6482,6 +6611,13 @@ var CommercetoolsMock = class {
6482
6611
  );
6483
6612
  app.use((err, req, resp, next) => {
6484
6613
  if (err instanceof CommercetoolsError) {
6614
+ if (err.errors?.length > 0) {
6615
+ return resp.status(err.statusCode).send({
6616
+ statusCode: err.statusCode,
6617
+ message: err.message,
6618
+ errors: err.errors
6619
+ });
6620
+ }
6485
6621
  return resp.status(err.statusCode).send({
6486
6622
  statusCode: err.statusCode,
6487
6623
  message: err.message,
@@ -6496,25 +6632,65 @@ var CommercetoolsMock = class {
6496
6632
  });
6497
6633
  return app;
6498
6634
  }
6499
- mockApiHost() {
6500
- const app = this.app;
6501
- this._nockScopes.api = (0, import_nock.default)(this.options.apiHost).persist().get(/.*/).reply(async function(uri) {
6502
- const response = await (0, import_supertest.default)(app).get(uri).set(copyHeaders(this.req.headers));
6503
- return [response.status, response.body];
6504
- }).post(/.*/).reply(async function(uri, body) {
6505
- const response = await (0, import_supertest.default)(app).post(uri).set(copyHeaders(this.req.headers)).send(body);
6506
- return [response.status, response.body];
6507
- }).delete(/.*/).reply(async function(uri, body) {
6508
- const response = await (0, import_supertest.default)(app).delete(uri).set(copyHeaders(this.req.headers)).send(body);
6509
- return [response.status, response.body];
6510
- });
6511
- }
6512
- mockAuthHost() {
6635
+ startServer() {
6636
+ if (_globalListeners.length > 0) {
6637
+ if (this._mswServer !== void 0) {
6638
+ throw new Error("Server already started");
6639
+ } else {
6640
+ console.warn("Server wasn't stopped properly, clearing");
6641
+ _globalListeners.forEach((listener) => listener.close());
6642
+ }
6643
+ }
6513
6644
  const app = this.app;
6514
- this._nockScopes.auth = (0, import_nock.default)(this.options.authHost).persist().post(/^\/oauth\/.*/).reply(async function(uri, body) {
6515
- const response = await (0, import_supertest.default)(app).post(uri + "?" + body).set(copyHeaders(this.req.headers)).send();
6516
- return [response.status, response.body];
6645
+ this._mswServer = (0, import_node.setupServer)(
6646
+ import_msw.http.post(`${this.options.authHost}/oauth/*`, async ({ request }) => {
6647
+ const text = await request.text();
6648
+ const url = new URL(request.url);
6649
+ const res = await (0, import_supertest.default)(app).post(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(text);
6650
+ return new import_msw.HttpResponse(res.text, {
6651
+ status: res.status,
6652
+ headers: res.headers
6653
+ });
6654
+ }),
6655
+ import_msw.http.get(`${this.options.apiHost}/*`, async ({ request }) => {
6656
+ const body = await request.text();
6657
+ const url = new URL(request.url);
6658
+ const res = await (0, import_supertest.default)(app).get(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(body);
6659
+ return new import_msw.HttpResponse(res.text, {
6660
+ status: res.status,
6661
+ headers: res.headers
6662
+ });
6663
+ }),
6664
+ import_msw.http.post(`${this.options.apiHost}/*`, async ({ request }) => {
6665
+ const body = await request.text();
6666
+ const url = new URL(request.url);
6667
+ const res = await (0, import_supertest.default)(app).post(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(body);
6668
+ return new import_msw.HttpResponse(res.text, {
6669
+ status: res.status,
6670
+ headers: res.headers
6671
+ });
6672
+ }),
6673
+ import_msw.http.delete(`${this.options.apiHost}/*`, async ({ request }) => {
6674
+ const body = await request.text();
6675
+ const url = new URL(request.url);
6676
+ const res = await (0, import_supertest.default)(app).delete(url.pathname + "?" + url.searchParams.toString()).set(copyHeaders(request.headers)).send(body);
6677
+ return new import_msw.HttpResponse(res.text, {
6678
+ status: res.status,
6679
+ headers: res.headers
6680
+ });
6681
+ })
6682
+ );
6683
+ this._mswServer.listen({
6684
+ // We need to allow requests done by supertest
6685
+ onUnhandledRequest: (request, print) => {
6686
+ const url = new URL(request.url);
6687
+ if (url.hostname === "127.0.0.1") {
6688
+ return;
6689
+ }
6690
+ print.error();
6691
+ }
6517
6692
  });
6693
+ _globalListeners.push(this._mswServer);
6518
6694
  }
6519
6695
  };
6520
6696
  // Annotate the CommonJS export names for ESM import in node: