@labdigital/commercetools-mock 2.58.0 → 2.59.1

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.
@@ -2,21 +2,35 @@ import assert from "node:assert";
2
2
  import type {
3
3
  Cart,
4
4
  CartReference,
5
+ CentPrecisionMoney,
5
6
  CustomLineItem,
6
7
  CustomLineItemImportDraft,
7
8
  GeneralError,
9
+ InvalidOperationError,
8
10
  LineItem,
9
11
  LineItemImportDraft,
12
+ MissingTaxRateForCountryError,
10
13
  Order,
11
14
  OrderFromCartDraft,
12
15
  OrderImportDraft,
13
16
  Product,
14
17
  ProductPagedQueryResponse,
15
18
  ProductVariant,
19
+ ShippingInfo,
20
+ ShippingMethodDoesNotMatchCartError,
21
+ ShippingMethodReference,
22
+ TaxPortion,
23
+ TaxedItemPrice,
16
24
  } from "@commercetools/platform-sdk";
25
+ import { Decimal } from "decimal.js/decimal";
17
26
  import type { Config } from "~src/config";
18
27
  import { CommercetoolsError } from "~src/exceptions";
19
28
  import { generateRandomString, getBaseResourceProperties } from "~src/helpers";
29
+ import {
30
+ createShippingInfoFromMethod,
31
+ getShippingMethodsMatchingCart,
32
+ } from "~src/shipping";
33
+ import type { Writable } from "~src/types";
20
34
  import type { RepositoryContext } from "../abstract";
21
35
  import { AbstractResourceRepository, type QueryParams } from "../abstract";
22
36
  import {
@@ -26,6 +40,7 @@ import {
26
40
  createPrice,
27
41
  createTypedMoney,
28
42
  resolveStoreReference,
43
+ roundDecimal,
29
44
  } from "../helpers";
30
45
  import { OrderUpdateHandler } from "./actions";
31
46
 
@@ -84,6 +99,7 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
84
99
  refusedGifts: [],
85
100
  shipping: cart.shipping,
86
101
  shippingAddress: cart.shippingAddress,
102
+ shippingInfo: cart.shippingInfo,
87
103
  shippingMode: cart.shippingMode,
88
104
  syncInfo: [],
89
105
  taxCalculationMode: cart.taxCalculationMode,
@@ -100,7 +116,7 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
100
116
  import(context: RepositoryContext, draft: OrderImportDraft): Order {
101
117
  // TODO: Check if order with given orderNumber already exists
102
118
  assert(this, "OrderRepository not valid");
103
- const resource: Order = {
119
+ const resource: Writable<Order> = {
104
120
  ...getBaseResourceProperties(),
105
121
 
106
122
  billingAddress: createAddress(
@@ -132,7 +148,7 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
132
148
  refusedGifts: [],
133
149
  shippingMode: "Single",
134
150
  shipping: [],
135
-
151
+ shippingInfo: undefined,
136
152
  store: resolveStoreReference(
137
153
  draft.store,
138
154
  context.projectKey,
@@ -151,6 +167,33 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
151
167
 
152
168
  totalPrice: createCentPrecisionMoney(draft.totalPrice),
153
169
  };
170
+
171
+ // Set shipping info after resource is created
172
+ if (draft.shippingInfo?.shippingMethod) {
173
+ const { ...shippingMethodRef } = draft.shippingInfo.shippingMethod;
174
+
175
+ // get id when reference is by key only
176
+ if (shippingMethodRef.key && !shippingMethodRef.id) {
177
+ const shippingMethod =
178
+ this._storage.getByResourceIdentifier<"shipping-method">(
179
+ context.projectKey,
180
+ shippingMethodRef,
181
+ );
182
+ if (!shippingMethod) {
183
+ throw new CommercetoolsError<GeneralError>({
184
+ code: "General",
185
+ message: `A shipping method with key '${shippingMethodRef.key}' does not exist.`,
186
+ });
187
+ }
188
+ shippingMethodRef.id = shippingMethod.id;
189
+ }
190
+
191
+ resource.shippingInfo = this.createShippingInfo(context, resource, {
192
+ typeId: "shipping-method",
193
+ id: shippingMethodRef.id as string,
194
+ });
195
+ }
196
+
154
197
  return this.saveNew(context, resource);
155
198
  }
156
199
 
@@ -219,6 +262,7 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
219
262
  id: variant.id,
220
263
  sku: variant.sku,
221
264
  price: createPrice(draft.price),
265
+ attributes: variant.attributes,
222
266
  },
223
267
  };
224
268
 
@@ -271,4 +315,56 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
271
315
 
272
316
  return;
273
317
  }
318
+
319
+ createShippingInfo(
320
+ context: RepositoryContext,
321
+ resource: Writable<Order>,
322
+ shippingMethodRef: ShippingMethodReference,
323
+ ): ShippingInfo {
324
+ const cartLikeForMatching: Writable<Cart> = {
325
+ ...resource,
326
+ cartState: "Active" as const,
327
+ inventoryMode: "None" as const,
328
+ itemShippingAddresses: [],
329
+ priceRoundingMode: resource.taxRoundingMode || "HalfEven",
330
+ taxMode: resource.taxMode || "Platform",
331
+ taxCalculationMode: resource.taxCalculationMode || "LineItemLevel",
332
+ taxRoundingMode: resource.taxRoundingMode || "HalfEven",
333
+ discountCodes: resource.discountCodes || [],
334
+ directDiscounts: resource.directDiscounts || [],
335
+ shippingInfo: undefined,
336
+ };
337
+
338
+ const shippingMethods = getShippingMethodsMatchingCart(
339
+ context,
340
+ this._storage,
341
+ cartLikeForMatching,
342
+ {
343
+ expand: ["zoneRates[*].zone"],
344
+ },
345
+ );
346
+
347
+ const method = shippingMethods.results.find(
348
+ (candidate) => candidate.id === shippingMethodRef.id,
349
+ );
350
+
351
+ if (!method) {
352
+ throw new CommercetoolsError<ShippingMethodDoesNotMatchCartError>({
353
+ code: "ShippingMethodDoesNotMatchCart",
354
+ message: `The shipping method with ID '${shippingMethodRef.id}' is not allowed for the order with ID '${resource.id}'.`,
355
+ });
356
+ }
357
+
358
+ const baseShippingInfo = createShippingInfoFromMethod(
359
+ context,
360
+ this._storage,
361
+ resource,
362
+ method,
363
+ );
364
+
365
+ return {
366
+ ...baseShippingInfo,
367
+ deliveries: [],
368
+ };
369
+ }
274
370
  }
@@ -711,6 +711,176 @@ describe("Cart Update Actions", () => {
711
711
  ]);
712
712
  });
713
713
 
714
+ test("setLineItemCustomField", async () => {
715
+ const product = await supertest(ctMock.app)
716
+ .post("/dummy/products")
717
+ .send(productDraft)
718
+ .then((x) => x.body);
719
+
720
+ assert(product, "product not created");
721
+
722
+ const type = await supertest(ctMock.app)
723
+ .post("/dummy/types")
724
+ .send({
725
+ key: "my-type",
726
+ name: {
727
+ en: "My Type",
728
+ },
729
+ description: {
730
+ en: "My Type Description",
731
+ },
732
+ fieldDefinitions: [
733
+ {
734
+ name: "foo",
735
+ label: {
736
+ en: "foo",
737
+ },
738
+ required: false,
739
+ type: {
740
+ name: "String",
741
+ },
742
+ inputHint: "SingleLine",
743
+ },
744
+ ],
745
+ })
746
+ .then((x) => x.body);
747
+
748
+ assert(type, "type not created");
749
+
750
+ const myCart = await supertest(ctMock.app)
751
+ .post("/dummy/carts")
752
+ .send({
753
+ currency: "EUR",
754
+ lineItems: [
755
+ {
756
+ sku: product.masterData.current.masterVariant.sku,
757
+ quantity: 1,
758
+ custom: {
759
+ type: {
760
+ typeId: "type",
761
+ key: "my-type",
762
+ },
763
+ fields: {},
764
+ },
765
+ },
766
+ ],
767
+ })
768
+ .then((x) => x.body);
769
+
770
+ const lineItem = myCart.lineItems[0];
771
+ assert(lineItem, "lineItem not created");
772
+
773
+ const response = await supertest(ctMock.app)
774
+ .post(`/dummy/carts/${myCart.id}`)
775
+ .send({
776
+ version: myCart.version,
777
+ actions: [
778
+ {
779
+ action: "setLineItemCustomField",
780
+ lineItemId: lineItem.id,
781
+ name: "foo",
782
+ value: "bar",
783
+ },
784
+ ],
785
+ });
786
+
787
+ expect(response.status).toBe(200);
788
+ expect(response.body.version).toBe(2);
789
+ expect(response.body.lineItems).toMatchObject([
790
+ {
791
+ id: lineItem.id,
792
+ custom: {
793
+ fields: {
794
+ foo: "bar",
795
+ },
796
+ },
797
+ },
798
+ ]);
799
+ });
800
+
801
+ test("setLineItemCustomType", async () => {
802
+ const product = await supertest(ctMock.app)
803
+ .post("/dummy/products")
804
+ .send(productDraft)
805
+ .then((x) => x.body);
806
+
807
+ assert(product, "product not created");
808
+
809
+ const type = await supertest(ctMock.app)
810
+ .post("/dummy/types")
811
+ .send({
812
+ key: "my-type",
813
+ name: {
814
+ en: "My Type",
815
+ },
816
+ description: {
817
+ en: "My Type Description",
818
+ },
819
+ fieldDefinitions: [
820
+ {
821
+ name: "foo",
822
+ label: {
823
+ en: "foo",
824
+ },
825
+ required: false,
826
+ type: {
827
+ name: "String",
828
+ },
829
+ inputHint: "SingleLine",
830
+ },
831
+ ],
832
+ })
833
+ .then((x) => x.body);
834
+
835
+ assert(type, "type not created");
836
+
837
+ const myCart = await supertest(ctMock.app)
838
+ .post("/dummy/carts")
839
+ .send({
840
+ currency: "EUR",
841
+ lineItems: [
842
+ {
843
+ sku: product.masterData.current.masterVariant.sku,
844
+ quantity: 1,
845
+ },
846
+ ],
847
+ })
848
+ .then((x) => x.body);
849
+
850
+ const lineItem = myCart.lineItems[0];
851
+ assert(lineItem, "lineItem not created");
852
+
853
+ const response = await supertest(ctMock.app)
854
+ .post(`/dummy/carts/${myCart.id}`)
855
+ .send({
856
+ version: myCart.version,
857
+ actions: [
858
+ {
859
+ action: "setLineItemCustomType",
860
+ lineItemId: lineItem.id,
861
+ type: {
862
+ typeId: "type",
863
+ key: "my-type",
864
+ },
865
+ },
866
+ ],
867
+ });
868
+
869
+ expect(response.status).toBe(200);
870
+ expect(response.body.version).toBe(2);
871
+ expect(response.body.lineItems).toMatchObject([
872
+ {
873
+ id: lineItem.id,
874
+ custom: {
875
+ type: {
876
+ typeId: "type",
877
+ id: type.id,
878
+ },
879
+ },
880
+ },
881
+ ]);
882
+ });
883
+
714
884
  test("setCustomerEmail", async () => {
715
885
  assert(cart, "cart not created");
716
886
 
@@ -1,5 +1,10 @@
1
1
  import assert from "node:assert";
2
- import type { Order, Payment, State } from "@commercetools/platform-sdk";
2
+ import type {
3
+ CentPrecisionMoney,
4
+ Order,
5
+ Payment,
6
+ State,
7
+ } from "@commercetools/platform-sdk";
3
8
  import supertest from "supertest";
4
9
  import { afterEach, beforeEach, describe, expect, test } from "vitest";
5
10
  import { generateRandomString } from "~src/helpers";
@@ -611,6 +616,236 @@ describe("Order Update Actions", () => {
611
616
  ).toBe("dhl");
612
617
  });
613
618
 
619
+ test("setLineItemCustomField", async () => {
620
+ const order: Order = {
621
+ ...getBaseResourceProperties(),
622
+ version: 1,
623
+ customLineItems: [],
624
+ directDiscounts: [],
625
+ discountCodes: [],
626
+ lineItems: [
627
+ {
628
+ id: "d70b14c8-72cf-4cab-82ba-6339cebe1e79",
629
+ productId: "06028a97-d622-47ac-a194-a3d90baa2b3c",
630
+ productSlug: { "nl-NL": "test-product" },
631
+ productType: { typeId: "product-type", id: "some-uuid" },
632
+ name: { "nl-NL": "test product" },
633
+ custom: {
634
+ type: {
635
+ typeId: "type",
636
+ id: "a493b7bb-d415-450c-b421-e128a8b26569",
637
+ },
638
+ fields: {},
639
+ },
640
+ variant: {
641
+ id: 1,
642
+ sku: "1337",
643
+ attributes: [{ name: "test", value: "test" }],
644
+ prices: [],
645
+ assets: [],
646
+ images: [],
647
+ },
648
+ price: {
649
+ id: "2f59a6c9-6a86-48d3-87f9-fabb3b12fd93",
650
+ value: {
651
+ type: "centPrecision",
652
+ centAmount: 14900,
653
+ currencyCode: "EUR",
654
+ fractionDigits: 2,
655
+ },
656
+ },
657
+ totalPrice: {
658
+ type: "centPrecision",
659
+ currencyCode: "EUR",
660
+ fractionDigits: 2,
661
+ centAmount: 14900,
662
+ },
663
+ taxedPricePortions: [],
664
+ perMethodTaxRate: [],
665
+ quantity: 1,
666
+ discountedPricePerQuantity: [],
667
+ lineItemMode: "Standard",
668
+ priceMode: "Platform",
669
+ state: [],
670
+ },
671
+ ],
672
+ orderState: "Open",
673
+ origin: "Customer",
674
+ refusedGifts: [],
675
+ shipping: [],
676
+ shippingMode: "Single",
677
+ syncInfo: [],
678
+ taxCalculationMode: "LineItemLevel",
679
+ taxMode: "Platform",
680
+ taxRoundingMode: "HalfEven",
681
+ totalPrice: {
682
+ type: "centPrecision",
683
+ centAmount: 14900,
684
+ currencyCode: "EUR",
685
+ fractionDigits: 0,
686
+ },
687
+ };
688
+
689
+ ctMock.project("dummy").add("order", order);
690
+
691
+ const lineItem = order.lineItems[0];
692
+ assert(lineItem, "lineItem not created");
693
+
694
+ const response = await supertest(ctMock.app)
695
+ .post(`/dummy/orders/${order.id}`)
696
+ .send({
697
+ version: order.version,
698
+ actions: [
699
+ {
700
+ action: "setLineItemCustomField",
701
+ lineItemId: lineItem.id,
702
+ name: "foo",
703
+ value: "bar",
704
+ },
705
+ ],
706
+ });
707
+
708
+ expect(response.status).toBe(200);
709
+ expect(response.body.version).toBe(2);
710
+ expect(response.body.lineItems).toMatchObject([
711
+ {
712
+ id: lineItem.id,
713
+ custom: {
714
+ fields: {
715
+ foo: "bar",
716
+ },
717
+ },
718
+ },
719
+ ]);
720
+ });
721
+
722
+ test("setLineItemCustomType", async () => {
723
+ const order: Order = {
724
+ ...getBaseResourceProperties(),
725
+ version: 1,
726
+ customLineItems: [],
727
+ directDiscounts: [],
728
+ discountCodes: [],
729
+ lineItems: [
730
+ {
731
+ id: "d70b14c8-72cf-4cab-82ba-6339cebe1e79",
732
+ productId: "06028a97-d622-47ac-a194-a3d90baa2b3c",
733
+ productSlug: { "nl-NL": "test-product" },
734
+ productType: { typeId: "product-type", id: "some-uuid" },
735
+ name: { "nl-NL": "test product" },
736
+ variant: {
737
+ id: 1,
738
+ sku: "1337",
739
+ attributes: [{ name: "test", value: "test" }],
740
+ prices: [],
741
+ assets: [],
742
+ images: [],
743
+ },
744
+ price: {
745
+ id: "2f59a6c9-6a86-48d3-87f9-fabb3b12fd93",
746
+ value: {
747
+ type: "centPrecision",
748
+ centAmount: 14900,
749
+ currencyCode: "EUR",
750
+ fractionDigits: 2,
751
+ },
752
+ },
753
+ totalPrice: {
754
+ type: "centPrecision",
755
+ currencyCode: "EUR",
756
+ fractionDigits: 2,
757
+ centAmount: 14900,
758
+ },
759
+ taxedPricePortions: [],
760
+ perMethodTaxRate: [],
761
+ quantity: 1,
762
+ discountedPricePerQuantity: [],
763
+ lineItemMode: "Standard",
764
+ priceMode: "Platform",
765
+ state: [],
766
+ },
767
+ ],
768
+ orderState: "Open",
769
+ origin: "Customer",
770
+ refusedGifts: [],
771
+ shipping: [],
772
+ shippingMode: "Single",
773
+ syncInfo: [],
774
+ taxCalculationMode: "LineItemLevel",
775
+ taxMode: "Platform",
776
+ taxRoundingMode: "HalfEven",
777
+ totalPrice: {
778
+ type: "centPrecision",
779
+ centAmount: 14900,
780
+ currencyCode: "EUR",
781
+ fractionDigits: 0,
782
+ },
783
+ };
784
+
785
+ ctMock.project("dummy").add("order", order);
786
+
787
+ const type = await supertest(ctMock.app)
788
+ .post("/dummy/types")
789
+ .send({
790
+ key: "my-type",
791
+ name: {
792
+ en: "My Type",
793
+ },
794
+ description: {
795
+ en: "My Type Description",
796
+ },
797
+ fieldDefinitions: [
798
+ {
799
+ name: "foo",
800
+ label: {
801
+ en: "foo",
802
+ },
803
+ required: false,
804
+ type: {
805
+ name: "String",
806
+ },
807
+ inputHint: "SingleLine",
808
+ },
809
+ ],
810
+ })
811
+ .then((x) => x.body);
812
+
813
+ assert(type, "type not created");
814
+
815
+ const lineItem = order.lineItems[0];
816
+ assert(lineItem, "lineItem not created");
817
+
818
+ const response = await supertest(ctMock.app)
819
+ .post(`/dummy/orders/${order.id}`)
820
+ .send({
821
+ version: order.version,
822
+ actions: [
823
+ {
824
+ action: "setLineItemCustomType",
825
+ lineItemId: lineItem.id,
826
+ type: {
827
+ typeId: "type",
828
+ id: type.id,
829
+ },
830
+ },
831
+ ],
832
+ });
833
+
834
+ expect(response.status).toBe(200);
835
+ expect(response.body.version).toBe(2);
836
+ expect(response.body.lineItems).toMatchObject([
837
+ {
838
+ id: lineItem.id,
839
+ custom: {
840
+ type: {
841
+ typeId: "type",
842
+ id: type.id,
843
+ },
844
+ },
845
+ },
846
+ ]);
847
+ });
848
+
614
849
  test("setParcelCustomField", async () => {
615
850
  const order: Order = {
616
851
  ...getBaseResourceProperties(),