@labdigital/commercetools-mock 2.8.0 → 2.10.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/README.md CHANGED
@@ -42,9 +42,14 @@ const ctMock = new CommercetoolsMock({
42
42
  })
43
43
 
44
44
  describe('A module', () => {
45
+ const mswServer = setupServer()
46
+
45
47
  beforeAll(() => {
46
- const server = setupServer()
47
- ctMock.registerHandlers(server)
48
+ mswServer.listen({ onUnhandledRequest: "error" })
49
+ })
50
+
51
+ beforeEach(() => {
52
+ ctMock.registerHandlers(mswServer)
48
53
 
49
54
  ctMock.project().add('type', {
50
55
  ...getBaseResourceProperties()
@@ -54,11 +59,11 @@ describe('A module', () => {
54
59
  })
55
60
 
56
61
  afterAll(() => {
57
- server.clearHandlers()
58
- ctMock.stop()
62
+ mswServer.close()
59
63
  })
60
64
 
61
65
  afterEach(() => {
66
+ server.clearHandlers()
62
67
  ctMock.clear()
63
68
  })
64
69
 
package/dist/index.cjs CHANGED
@@ -720,7 +720,7 @@ var resolveValue = (obj, val) => {
720
720
  if (Array.isArray(obj)) {
721
721
  return Object.values(obj).filter((v) => val.value in v).map((v) => v[val.value]);
722
722
  }
723
- throw new PredicateError(`The field '${val.value}' does not exist.`);
723
+ return void 0;
724
724
  }
725
725
  return obj[val.value];
726
726
  };
@@ -800,22 +800,43 @@ var generateMatchFunc = (predicate) => {
800
800
  const expr = parser.parse();
801
801
  lexer.expect(")");
802
802
  return (obj, vars) => {
803
- const value = resolveValue(obj, left);
804
- if (value) {
805
- return expr(value, vars);
803
+ if (Array.isArray(obj)) {
804
+ return obj.some((item) => {
805
+ const value = resolveValue(item, left);
806
+ if (value) {
807
+ return expr(value, vars);
808
+ }
809
+ return false;
810
+ });
811
+ } else {
812
+ const value = resolveValue(obj, left);
813
+ if (value) {
814
+ return expr(value, vars);
815
+ }
816
+ return false;
806
817
  }
807
- return false;
808
818
  };
809
819
  }).bp(")", 0).led("=", 20, ({ left, bp }) => {
810
820
  const expr = parser.parse({ terminals: [bp - 1] });
811
821
  validateSymbol(expr);
812
822
  return (obj, vars) => {
813
- const resolvedValue = resolveValue(obj, left);
814
- const resolvedSymbol = resolveSymbol(expr, vars);
815
- if (Array.isArray(resolvedValue)) {
816
- return !!resolvedValue.some((elem) => elem === resolvedSymbol);
823
+ if (Array.isArray(obj)) {
824
+ return obj.some((item) => {
825
+ const value = resolveValue(item, left);
826
+ const other = resolveSymbol(expr, vars);
827
+ if (Array.isArray(value)) {
828
+ return !!value.some((elem) => elem === other);
829
+ }
830
+ return value === other;
831
+ });
832
+ } else {
833
+ const resolvedValue = resolveValue(obj, left);
834
+ const resolvedSymbol = resolveSymbol(expr, vars);
835
+ if (Array.isArray(resolvedValue)) {
836
+ return !!resolvedValue.some((elem) => elem === resolvedSymbol);
837
+ }
838
+ return resolvedValue === resolvedSymbol;
817
839
  }
818
- return resolvedValue === resolvedSymbol;
819
840
  };
820
841
  }).led("!=", 20, ({ left, bp }) => {
821
842
  const expr = parser.parse({ terminals: [bp - 1] });
@@ -883,10 +904,11 @@ var generateMatchFunc = (predicate) => {
883
904
  if (!Array.isArray(symbols)) {
884
905
  symbols = [expr];
885
906
  }
886
- const inValues = symbols.map(
907
+ const inValues = symbols.flatMap(
887
908
  (item) => resolveSymbol(item, vars)
888
909
  );
889
- return inValues.includes(resolveValue(obj, left));
910
+ const value = resolveValue(obj, left);
911
+ return inValues.includes(value);
890
912
  };
891
913
  }).led("MATCHES_IGNORE_CASE", 20, ({ left, bp }) => {
892
914
  const expr = parser.parse({ terminals: [bp - 1] });
@@ -1064,9 +1086,12 @@ var InMemoryStorage = class extends AbstractStorage {
1064
1086
  }
1065
1087
  let resources = this.all(projectKey, typeId);
1066
1088
  if (params.where) {
1089
+ const vars = Object.fromEntries(
1090
+ Object.entries(params).filter(([key]) => key.startsWith("var.")).map(([key, value]) => [key.slice(4), value])
1091
+ );
1067
1092
  try {
1068
1093
  const filterFunc = parseQueryExpression(params.where);
1069
- resources = resources.filter((resource) => filterFunc(resource, {}));
1094
+ resources = resources.filter((resource) => filterFunc(resource, vars));
1070
1095
  } catch (err) {
1071
1096
  throw new CommercetoolsError(
1072
1097
  {
@@ -1133,28 +1158,34 @@ var InMemoryStorage = class extends AbstractStorage {
1133
1158
  if (resource) {
1134
1159
  return resource;
1135
1160
  }
1136
- console.error(
1137
- `No resource found with typeId=${identifier.typeId}, id=${identifier.id}`
1138
- );
1139
- return null;
1161
+ throw new CommercetoolsError({
1162
+ code: "ReferencedResourceNotFound",
1163
+ message: `The referenced object of type '${identifier.typeId}' with id '${identifier.id}' was not found. It either doesn't exist, or it can't be accessed from this endpoint (e.g., if the endpoint filters by store or customer account).`,
1164
+ typeId: identifier.typeId,
1165
+ id: identifier.id
1166
+ });
1140
1167
  }
1141
1168
  if (identifier.key) {
1142
- const store = this.forProjectKey(projectKey)[identifier.typeId];
1143
- if (store) {
1144
- const resource = Array.from(store.values()).find(
1145
- // @ts-ignore
1146
- (r) => r.key === identifier.key
1147
- );
1148
- if (resource) {
1149
- return resource;
1150
- }
1151
- } else {
1152
- throw new Error(
1153
- `No storage found for resource type: ${identifier.typeId}`
1154
- );
1169
+ const resource = this.getByKey(
1170
+ projectKey,
1171
+ identifier.typeId,
1172
+ identifier.key
1173
+ );
1174
+ if (resource) {
1175
+ return resource;
1155
1176
  }
1177
+ throw new CommercetoolsError({
1178
+ code: "ReferencedResourceNotFound",
1179
+ message: `The referenced object of type '${identifier.typeId}' with key '${identifier.key}' was not found. It either doesn't exist, or it can't be accessed from this endpoint (e.g., if the endpoint filters by store or customer account).`,
1180
+ typeId: identifier.typeId,
1181
+ key: identifier.key
1182
+ });
1156
1183
  }
1157
- return null;
1184
+ throw new CommercetoolsError({
1185
+ code: "InvalidJsonInput",
1186
+ message: "Request body does not contain valid JSON.",
1187
+ detailedErrorMessage: "ResourceIdentifier requires an 'id' xor a 'key'"
1188
+ });
1158
1189
  }
1159
1190
  addProject = (projectKey) => {
1160
1191
  if (!this.projects[projectKey]) {
@@ -1308,11 +1339,12 @@ var hashPassword = (clearPassword) => Buffer.from(clearPassword).toString("base6
1308
1339
 
1309
1340
  // src/oauth/server.ts
1310
1341
  var OAuth2Server = class {
1311
- store;
1312
- customerRepository;
1313
1342
  constructor(options) {
1343
+ this.options = options;
1314
1344
  this.store = new OAuth2Store(options.validate);
1315
1345
  }
1346
+ store;
1347
+ customerRepository;
1316
1348
  setCustomerRepository(repository) {
1317
1349
  this.customerRepository = repository;
1318
1350
  }
@@ -1336,6 +1368,11 @@ var OAuth2Server = class {
1336
1368
  return router;
1337
1369
  }
1338
1370
  createMiddleware() {
1371
+ if (!this.options.validate) {
1372
+ return async (request, response, next) => {
1373
+ next();
1374
+ };
1375
+ }
1339
1376
  return async (request, response, next) => {
1340
1377
  const token = getBearerToken(request);
1341
1378
  if (!token) {
@@ -1873,10 +1910,7 @@ var AbstractResourceRepository = class extends AbstractRepository {
1873
1910
  }
1874
1911
  query(context, params = {}) {
1875
1912
  const result = this._storage.query(context.projectKey, this.getTypeId(), {
1876
- expand: params.expand,
1877
- where: params.where,
1878
- offset: params.offset,
1879
- limit: params.limit
1913
+ ...params
1880
1914
  });
1881
1915
  result.results = result.results.map(this.postProcessResource);
1882
1916
  return result;
@@ -2196,6 +2230,11 @@ var CartRepository = class extends AbstractResourceRepository {
2196
2230
  fractionDigits: 0
2197
2231
  },
2198
2232
  shippingMode: "Single",
2233
+ shippingAddress: createAddress(
2234
+ draft.shippingAddress,
2235
+ context.projectKey,
2236
+ this._storage
2237
+ ),
2199
2238
  shipping: [],
2200
2239
  origin: draft.origin ?? "Customer",
2201
2240
  refusedGifts: [],
@@ -2313,6 +2352,8 @@ var CartRepository = class extends AbstractResourceRepository {
2313
2352
  }
2314
2353
  resource.totalPrice.centAmount = calculateCartTotalPrice(resource);
2315
2354
  },
2355
+ recalculate: () => {
2356
+ },
2316
2357
  addItemShippingAddress: (context, resource, { action, address }) => {
2317
2358
  const newAddress = createAddress(
2318
2359
  address,
@@ -2397,9 +2438,6 @@ var CartRepository = class extends AbstractResourceRepository {
2397
2438
  context.projectKey,
2398
2439
  shippingMethod
2399
2440
  );
2400
- if (!method) {
2401
- throw new Error(`Type ${shippingMethod} not found`);
2402
- }
2403
2441
  resource.shippingInfo = {
2404
2442
  shippingMethod: {
2405
2443
  typeId: "shipping-method",
@@ -2423,6 +2461,33 @@ var CartRepository = class extends AbstractResourceRepository {
2423
2461
  }
2424
2462
  resource.custom.fields[name] = value;
2425
2463
  },
2464
+ setCustomShippingMethod: (context, resource, {
2465
+ shippingMethodName,
2466
+ shippingRate,
2467
+ taxCategory,
2468
+ externalTaxRate
2469
+ }) => {
2470
+ if (externalTaxRate) {
2471
+ throw new Error("External tax rate is not supported");
2472
+ }
2473
+ const tax = taxCategory ? this._storage.getByResourceIdentifier(
2474
+ context.projectKey,
2475
+ taxCategory
2476
+ ) : void 0;
2477
+ resource.shippingInfo = {
2478
+ shippingMethodName,
2479
+ price: createCentPrecisionMoney(shippingRate.price),
2480
+ shippingRate: {
2481
+ price: createTypedMoney(shippingRate.price),
2482
+ tiers: []
2483
+ },
2484
+ taxCategory: tax ? {
2485
+ typeId: "tax-category",
2486
+ id: tax?.id
2487
+ } : void 0,
2488
+ shippingMethodState: "MatchesCart"
2489
+ };
2490
+ },
2426
2491
  setCustomType: (context, resource, { type, fields }) => {
2427
2492
  if (!type) {
2428
2493
  resource.custom = void 0;
@@ -2974,6 +3039,16 @@ var CustomObjectRepository = class extends AbstractResourceRepository {
2974
3039
  return resource;
2975
3040
  }
2976
3041
  }
3042
+ queryWithContainer(context, container, params = {}) {
3043
+ const whereClause = params.where || [];
3044
+ whereClause.push(`container="${container}"`);
3045
+ const result = this._storage.query(context.projectKey, this.getTypeId(), {
3046
+ ...params,
3047
+ where: whereClause
3048
+ });
3049
+ result.results = result.results.map(this.postProcessResource);
3050
+ return result;
3051
+ }
2977
3052
  getWithContainerAndKey(context, container, key) {
2978
3053
  const items = this._storage.all(context.projectKey, this.getTypeId());
2979
3054
  return items.find(
@@ -5502,6 +5577,50 @@ var ReviewRepository = class extends AbstractResourceRepository {
5502
5577
 
5503
5578
  // src/repositories/shipping-method.ts
5504
5579
  var import_deep_equal3 = __toESM(require("deep-equal"), 1);
5580
+
5581
+ // src/shippingCalculator.ts
5582
+ var markMatchingShippingRate = (cart, shippingRate) => {
5583
+ const isMatching = shippingRate.price.currencyCode === cart.totalPrice.currencyCode;
5584
+ return {
5585
+ ...shippingRate,
5586
+ tiers: markMatchingShippingRatePriceTiers(cart, shippingRate.tiers),
5587
+ isMatching
5588
+ };
5589
+ };
5590
+ var markMatchingShippingRatePriceTiers = (cart, tiers) => {
5591
+ if (tiers.length === 0) {
5592
+ return [];
5593
+ }
5594
+ if (new Set(tiers.map((tier) => tier.type)).size > 1) {
5595
+ throw new Error("Can't handle multiple types of tiers");
5596
+ }
5597
+ const tierType = tiers[0].type;
5598
+ switch (tierType) {
5599
+ case "CartValue":
5600
+ return markMatchingCartValueTiers(cart, tiers);
5601
+ default:
5602
+ throw new Error(`Unsupported tier type: ${tierType}`);
5603
+ }
5604
+ };
5605
+ var markMatchingCartValueTiers = (cart, tiers) => {
5606
+ const sortedTiers = [...tiers].sort(
5607
+ (a, b) => b.minimumCentAmount - a.minimumCentAmount
5608
+ );
5609
+ const result = {};
5610
+ let hasMatchingTier = false;
5611
+ for (const tier of sortedTiers) {
5612
+ const isMatching = !hasMatchingTier && cart.totalPrice.currencyCode === tier.price.currencyCode && cart.totalPrice.centAmount >= tier.minimumCentAmount;
5613
+ if (isMatching)
5614
+ hasMatchingTier = true;
5615
+ result[tier.minimumCentAmount] = {
5616
+ ...tier,
5617
+ isMatching
5618
+ };
5619
+ }
5620
+ return tiers.map((tier) => result[tier.minimumCentAmount]);
5621
+ };
5622
+
5623
+ // src/repositories/shipping-method.ts
5505
5624
  var ShippingMethodRepository = class extends AbstractResourceRepository {
5506
5625
  getTypeId() {
5507
5626
  return "shipping-method";
@@ -5541,6 +5660,53 @@ var ShippingMethodRepository = class extends AbstractResourceRepository {
5541
5660
  freeAbove: rate.freeAbove && createTypedMoney(rate.freeAbove),
5542
5661
  tiers: rate.tiers || []
5543
5662
  });
5663
+ /*
5664
+ * Retrieves all the ShippingMethods that can ship to the shipping address of
5665
+ * the given Cart. Each ShippingMethod contains exactly one ShippingRate with
5666
+ * the flag isMatching set to true. This ShippingRate is used when the
5667
+ * ShippingMethod is added to the Cart.
5668
+ */
5669
+ matchingCart(context, cartId, params = {}) {
5670
+ const cart = this._storage.get(context.projectKey, "cart", cartId);
5671
+ if (!cart) {
5672
+ return void 0;
5673
+ }
5674
+ if (!cart.shippingAddress?.country) {
5675
+ throw new CommercetoolsError({
5676
+ code: "InvalidOperation",
5677
+ message: `The cart with ID '${cart.id}' does not have a shipping address set.`
5678
+ });
5679
+ }
5680
+ const zones = this._storage.query(context.projectKey, "zone", {
5681
+ where: [`locations(country="${cart.shippingAddress.country}"))`],
5682
+ limit: 100
5683
+ });
5684
+ const zoneIds = zones.results.map((zone) => zone.id);
5685
+ const shippingMethods = this.query(context, {
5686
+ where: [
5687
+ `zoneRates(zone(id in (:zoneIds)))`,
5688
+ `zoneRates(shippingRates(price(currencyCode="${cart.totalPrice.currencyCode}")))`
5689
+ ],
5690
+ "var.zoneIds": zoneIds,
5691
+ expand: params.expand
5692
+ });
5693
+ const results = shippingMethods.results.map((shippingMethod) => {
5694
+ const rates = shippingMethod.zoneRates.map((zoneRate) => ({
5695
+ zone: zoneRate.zone,
5696
+ // Iterate through the shippingRates and mark the matching ones
5697
+ // then we filter out the non-matching ones
5698
+ shippingRates: zoneRate.shippingRates.map((rate) => markMatchingShippingRate(cart, rate)).filter((rate) => rate.isMatching)
5699
+ })).filter((zoneRate) => zoneRate.shippingRates.length > 0);
5700
+ return {
5701
+ ...shippingMethod,
5702
+ zoneRates: rates
5703
+ };
5704
+ }).filter((shippingMethod) => shippingMethod.zoneRates.length > 0);
5705
+ return {
5706
+ ...shippingMethods,
5707
+ results
5708
+ };
5709
+ }
5544
5710
  actions = {
5545
5711
  addShippingRate: (_context, resource, { shippingRate, zone }) => {
5546
5712
  const rate = this._transformShippingRate(shippingRate);
@@ -6394,10 +6560,26 @@ var CustomObjectService = class extends AbstractService {
6394
6560
  return "custom-objects";
6395
6561
  }
6396
6562
  extraRoutes(router) {
6563
+ router.get("/:container", this.getWithContainer.bind(this));
6397
6564
  router.get("/:container/:key", this.getWithContainerAndKey.bind(this));
6398
6565
  router.post("/:container/:key", this.createWithContainerAndKey.bind(this));
6399
6566
  router.delete("/:container/:key", this.deleteWithContainerAndKey.bind(this));
6400
6567
  }
6568
+ getWithContainer(request, response) {
6569
+ const limit = this._parseParam(request.query.limit);
6570
+ const offset = this._parseParam(request.query.offset);
6571
+ const result = this.repository.queryWithContainer(
6572
+ getRepositoryContext(request),
6573
+ request.params.container,
6574
+ {
6575
+ expand: this._parseParam(request.query.expand),
6576
+ where: this._parseParam(request.query.where),
6577
+ limit: limit !== void 0 ? Number(limit) : void 0,
6578
+ offset: offset !== void 0 ? Number(offset) : void 0
6579
+ }
6580
+ );
6581
+ return response.status(200).send(result);
6582
+ }
6401
6583
  getWithContainerAndKey(request, response) {
6402
6584
  const result = this.repository.getWithContainerAndKey(
6403
6585
  getRepositoryContext(request),
@@ -6836,7 +7018,21 @@ var ShippingMethodService = class extends AbstractService {
6836
7018
  return "shipping-methods";
6837
7019
  }
6838
7020
  extraRoutes(parent) {
6839
- parent.get("/matching-cart", this.get.bind(this));
7021
+ parent.get("/matching-cart", this.matchingCart.bind(this));
7022
+ }
7023
+ matchingCart(request, response) {
7024
+ const cartId = queryParamsValue(request.query.cartId);
7025
+ if (!cartId) {
7026
+ return response.status(400).send();
7027
+ }
7028
+ const result = this.repository.matchingCart(
7029
+ getRepositoryContext(request),
7030
+ cartId,
7031
+ {
7032
+ expand: this._parseParam(request.query.expand)
7033
+ }
7034
+ );
7035
+ return response.status(200).send(result);
6840
7036
  }
6841
7037
  };
6842
7038