@labdigital/commercetools-mock 2.40.0 → 2.41.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
- "version": "2.40.0",
3
+ "version": "2.41.0",
4
4
  "license": "MIT",
5
5
  "author": "Michael van Tellingen",
6
6
  "type": "module",
@@ -3,10 +3,49 @@ import { describe, expect, test } from "vitest";
3
3
  import { InMemoryStorage } from "~src/storage";
4
4
  import { CustomerRepository } from "./index";
5
5
 
6
- describe("Order repository", () => {
6
+ describe("Customer repository", () => {
7
7
  const storage = new InMemoryStorage();
8
8
  const repository = new CustomerRepository(storage);
9
9
 
10
+ test("query by lowercaseEmail", async () => {
11
+ const customer = repository.create(
12
+ { projectKey: "dummy" },
13
+ { email: "my-customer-UPPERCASE@email.com" },
14
+ );
15
+
16
+ const result = repository.query(
17
+ { projectKey: "dummy" },
18
+ { where: [`lowercaseEmail = "my-customer-uppercase@email.com"`] },
19
+ );
20
+
21
+ expect(result.results).toHaveLength(1);
22
+ expect(result.results[0].id).toEqual(customer.id);
23
+ });
24
+
25
+ test("updating lowercaseEmail", async () => {
26
+ const customer = repository.create(
27
+ { projectKey: "dummy" },
28
+ { email: "my-customer-UPPERCASE-v1@email.com" },
29
+ );
30
+
31
+ repository.saveUpdate({ projectKey: "dummy" }, customer.version, {
32
+ ...customer,
33
+ email: "my-customer-UPPERCASE-v2@email.com",
34
+ version: customer.version + 1,
35
+ });
36
+
37
+ const result = repository.query(
38
+ { projectKey: "dummy" },
39
+ { where: [`lowercaseEmail = "my-customer-uppercase-v2@email.com"`] },
40
+ );
41
+
42
+ expect(result.results).toHaveLength(1);
43
+ expect(result.results[0].id).toEqual(customer.id);
44
+ expect(result.results[0].email).toEqual(
45
+ "my-customer-UPPERCASE-v2@email.com",
46
+ );
47
+ });
48
+
10
49
  test("adding stores to customer", async () => {
11
50
  const store1: Store = {
12
51
  id: "d0016081-e9af-48a7-8133-1f04f340a335",
@@ -72,4 +111,17 @@ describe("Order repository", () => {
72
111
  },
73
112
  ]);
74
113
  });
114
+
115
+ test("adding customer without linked stores", async () => {
116
+ const result = repository.create(
117
+ { projectKey: "dummy" },
118
+ {
119
+ email: "my-customer-without-stores@email.com",
120
+ stores: [],
121
+ },
122
+ );
123
+
124
+ expect(result.email).toEqual("my-customer-without-stores@email.com");
125
+ expect(result?.stores).toHaveLength(0);
126
+ });
75
127
  });
@@ -20,7 +20,7 @@ import {
20
20
  validatePasswordResetToken,
21
21
  } from "~src/lib/password";
22
22
  import type { AbstractStorage } from "~src/storage/abstract";
23
- import type { Writable } from "~src/types";
23
+ import type { ResourceMap, ShallowWritable, Writable } from "~src/types";
24
24
  import {
25
25
  AbstractResourceRepository,
26
26
  type RepositoryContext,
@@ -37,7 +37,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
37
37
  create(context: RepositoryContext, draft: CustomerDraft): Customer {
38
38
  // Check uniqueness
39
39
  const results = this._storage.query(context.projectKey, this.getTypeId(), {
40
- where: [`email="${draft.email.toLocaleLowerCase()}"`],
40
+ where: [`lowercaseEmail="${draft.email.toLowerCase()}"`],
41
41
  });
42
42
  if (results.count > 0) {
43
43
  throw new CommercetoolsError<any>({
@@ -105,7 +105,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
105
105
 
106
106
  let storesForCustomer: StoreKeyReference[] = [];
107
107
 
108
- if (draft.stores) {
108
+ if (draft.stores && draft.stores.length > 0) {
109
109
  const storeIds = draft.stores
110
110
  .map((storeReference) => storeReference.id)
111
111
  .filter(Boolean);
@@ -141,6 +141,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
141
141
  dateOfBirth: draft.dateOfBirth,
142
142
  companyName: draft.companyName,
143
143
  email: draft.email.toLowerCase(),
144
+ lowercaseEmail: draft.email.toLowerCase(),
144
145
  password: draft.password ? hashPassword(draft.password) : undefined,
145
146
  isEmailVerified: draft.isEmailVerified || false,
146
147
  addresses: addresses,
@@ -156,10 +157,25 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
156
157
  this._storage,
157
158
  ),
158
159
  stores: storesForCustomer,
159
- };
160
+ } satisfies unknown as Customer;
161
+
160
162
  return this.saveNew(context, resource);
161
163
  }
162
164
 
165
+ saveUpdate(
166
+ context: RepositoryContext,
167
+ version: number,
168
+ resource: ShallowWritable<ResourceMap["customer"]>,
169
+ ): ShallowWritable<ResourceMap["customer"]> {
170
+ // Also update lowercaseEmail attribute
171
+ const updatedResource: Customer = {
172
+ ...resource,
173
+ lowercaseEmail: resource.email.toLowerCase(),
174
+ } satisfies unknown as Customer;
175
+
176
+ return super.saveUpdate(context, version, updatedResource);
177
+ }
178
+
163
179
  passwordResetToken(
164
180
  context: RepositoryContext,
165
181
  request: CustomerCreatePasswordResetToken,
@@ -15,6 +15,7 @@ import type {
15
15
  OrderSetDeliveryCustomFieldAction,
16
16
  OrderSetLocaleAction,
17
17
  OrderSetOrderNumberAction,
18
+ OrderSetParcelCustomFieldAction,
18
19
  OrderSetPurchaseOrderNumberAction,
19
20
  OrderSetShippingAddressAction,
20
21
  OrderSetStoreAction,
@@ -26,7 +27,6 @@ import type {
26
27
  Store,
27
28
  SyncInfo,
28
29
  } from "@commercetools/platform-sdk";
29
- import assert from "assert";
30
30
  import { getBaseResourceProperties } from "~src/helpers";
31
31
  import type { Writable } from "~src/types";
32
32
  import type { RepositoryContext, UpdateHandlerInterface } from "../abstract";
@@ -194,18 +194,14 @@ export class OrderUpdateHandler
194
194
  resource: Writable<Order>,
195
195
  { deliveryId, name, value }: OrderSetDeliveryCustomFieldAction,
196
196
  ) {
197
- assert(resource.shippingInfo, "shippingInfo is not defined");
197
+ if (!resource.shippingInfo) {
198
+ throw new Error("Resource has no shipping info");
199
+ }
198
200
 
199
- if (Array.isArray(resource.shippingInfo.deliveries)) {
200
- resource.shippingInfo.deliveries.map((delivery) => {
201
- if (delivery.id !== deliveryId) throw "No matching delivery id found";
202
- if (delivery.custom) {
203
- const update = delivery.custom.fields;
204
- update[name] = value;
205
- Object.assign(delivery.custom.fields, update);
206
- }
207
- return delivery;
208
- });
201
+ for (const delivery of resource.shippingInfo.deliveries || []) {
202
+ if (delivery.id === deliveryId && delivery.custom?.fields) {
203
+ delivery.custom.fields[name] = value;
204
+ }
209
205
  }
210
206
  }
211
207
 
@@ -225,6 +221,24 @@ export class OrderUpdateHandler
225
221
  resource.orderNumber = orderNumber;
226
222
  }
227
223
 
224
+ setParcelCustomField(
225
+ context: RepositoryContext,
226
+ resource: Writable<Order>,
227
+ { parcelId, name, value }: OrderSetParcelCustomFieldAction,
228
+ ) {
229
+ if (!resource.shippingInfo) {
230
+ throw new Error("Resource has no shipping info");
231
+ }
232
+
233
+ for (const delivery of resource.shippingInfo.deliveries || []) {
234
+ for (const parcel of delivery.parcels || []) {
235
+ if (parcel.id === parcelId && parcel.custom?.fields) {
236
+ parcel.custom.fields[name] = value;
237
+ }
238
+ }
239
+ }
240
+ }
241
+
228
242
  setPurchaseOrderNumber(
229
243
  context: RepositoryContext,
230
244
  resource: Writable<Order>,
@@ -31,6 +31,7 @@ describe("Me", () => {
31
31
  customer: {
32
32
  ...draft,
33
33
  password: "cDRzc3cwcmQ=",
34
+ lowercaseEmail: draft.email.toLowerCase(),
34
35
  authenticationMode: "Password",
35
36
  version: 1,
36
37
  isEmailVerified: false,
@@ -593,6 +593,156 @@ describe("Order Update Actions", () => {
593
593
  ).toBe("dhl");
594
594
  });
595
595
 
596
+ test("setParcelCustomField", async () => {
597
+ const order: Order = {
598
+ ...getBaseResourceProperties(),
599
+ customLineItems: [],
600
+ lastMessageSequenceNumber: 0,
601
+ lineItems: [],
602
+ orderNumber: "1390",
603
+ orderState: "Open",
604
+ origin: "Customer",
605
+ paymentInfo: {
606
+ payments: [
607
+ {
608
+ typeId: "payment",
609
+ id: generateRandomString(10),
610
+ },
611
+ ],
612
+ },
613
+ refusedGifts: [],
614
+ shippingInfo: {
615
+ shippingMethodName: "Home delivery (package)",
616
+ price: {
617
+ type: "centPrecision",
618
+ currencyCode: "EUR",
619
+ centAmount: 999,
620
+ fractionDigits: 2,
621
+ },
622
+ shippingRate: {
623
+ price: {
624
+ type: "centPrecision",
625
+ currencyCode: "EUR",
626
+ centAmount: 999,
627
+ fractionDigits: 2,
628
+ },
629
+ tiers: [
630
+ {
631
+ type: "CartScore",
632
+ score: 24,
633
+ price: {
634
+ type: "centPrecision",
635
+ currencyCode: "EUR",
636
+ centAmount: 1998,
637
+ fractionDigits: 2,
638
+ },
639
+ },
640
+ {
641
+ type: "CartScore",
642
+ score: 47,
643
+ price: {
644
+ type: "centPrecision",
645
+ currencyCode: "EUR",
646
+ centAmount: 2997,
647
+ fractionDigits: 2,
648
+ },
649
+ },
650
+ {
651
+ type: "CartScore",
652
+ score: 70,
653
+ price: {
654
+ type: "centPrecision",
655
+ currencyCode: "EUR",
656
+ centAmount: 3996,
657
+ fractionDigits: 2,
658
+ },
659
+ },
660
+ {
661
+ type: "CartScore",
662
+ score: 93,
663
+ price: {
664
+ type: "centPrecision",
665
+ currencyCode: "EUR",
666
+ centAmount: 4995,
667
+ fractionDigits: 2,
668
+ },
669
+ },
670
+ ],
671
+ },
672
+ deliveries: [
673
+ {
674
+ id: "6a458cad-dd46-4f5f-8b73-debOede6a17d",
675
+ key: "CT-Z243002",
676
+ createdAt: "2024-07-29T13:37:48.047Z",
677
+ items: [
678
+ {
679
+ id: "5d209544-2892-45c9-bef0-dde4e250188e",
680
+ quantity: 1,
681
+ },
682
+ ],
683
+ parcels: [
684
+ {
685
+ id: "7a458cad-dd46-4f5f-8b73-debOede6a17d",
686
+ createdAt: "2024-07-29T13:37:48.047Z",
687
+ items: [
688
+ {
689
+ id: "5d209544-2892-45c9-bef0-dde4e250188e",
690
+ quantity: 1,
691
+ },
692
+ ],
693
+ custom: {
694
+ type: {
695
+ typeId: "type",
696
+ id: "c493b7bb-d415-450c-b421-e128a8b26569",
697
+ },
698
+ fields: {
699
+ status: "created",
700
+ },
701
+ },
702
+ },
703
+ ],
704
+ },
705
+ ],
706
+ shippingMethodState: "MatchesCart",
707
+ },
708
+ shipping: [],
709
+ shippingMode: "Single",
710
+ syncInfo: [],
711
+ totalPrice: {
712
+ type: "centPrecision",
713
+ fractionDigits: 2,
714
+ centAmount: 2000,
715
+ currencyCode: "EUR",
716
+ },
717
+ };
718
+ ctMock.project("dummy").add("order", order);
719
+
720
+ const response = await supertest(ctMock.app).get(
721
+ `/dummy/orders/order-number=${order.orderNumber}`,
722
+ );
723
+
724
+ // check if status is set
725
+ const _updateResponse = await supertest(ctMock.app)
726
+ .post(`/dummy/orders/${response.body.id}`)
727
+ .send({
728
+ version: 0,
729
+ actions: [
730
+ {
731
+ action: "setParcelCustomField",
732
+ parcelId: "7a458cad-dd46-4f5f-8b73-debOede6a17d",
733
+ name: "status",
734
+ value: "delayed",
735
+ },
736
+ ],
737
+ });
738
+ expect(_updateResponse.status).toBe(200);
739
+ expect(_updateResponse.body.version).toBe(1);
740
+ expect(
741
+ _updateResponse.body.shippingInfo.deliveries[0].parcels[0].custom.fields
742
+ .status,
743
+ ).toBe("delayed");
744
+ });
745
+
596
746
  test("updateSyncInfo", async () => {
597
747
  assert(order, "order not created");
598
748