@labdigital/commercetools-mock 2.40.0 → 2.41.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.
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.1",
4
4
  "license": "MIT",
5
5
  "author": "Michael van Tellingen",
6
6
  "type": "module",
@@ -3,11 +3,50 @@ 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("adding stores to customer", async () => {
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
+
49
+ test("adding multiple stores to customer", async () => {
11
50
  const store1: Store = {
12
51
  id: "d0016081-e9af-48a7-8133-1f04f340a335",
13
52
  key: "store-1",
@@ -72,4 +111,57 @@ describe("Order repository", () => {
72
111
  },
73
112
  ]);
74
113
  });
114
+
115
+ test("adding single store to customer", async () => {
116
+ const store1: Store = {
117
+ id: "58082253-fe4e-4714-941f-86ab596d42ed",
118
+ key: "store-1",
119
+ name: {
120
+ en: "Store 1",
121
+ },
122
+ version: 1,
123
+ createdAt: "2021-09-02T12:23:30.036Z",
124
+ lastModifiedAt: "2021-09-02T12:23:30.546Z",
125
+ languages: [],
126
+ distributionChannels: [],
127
+ countries: [],
128
+ supplyChannels: [],
129
+ productSelections: [],
130
+ };
131
+
132
+ storage.add("dummy", "store", store1);
133
+
134
+ const result = repository.create(
135
+ { projectKey: "dummy" },
136
+ {
137
+ email: "my-customer2@email.com",
138
+ stores: [
139
+ {
140
+ typeId: "store",
141
+ key: store1.key,
142
+ },
143
+ ],
144
+ },
145
+ );
146
+
147
+ expect(result?.stores).toEqual([
148
+ {
149
+ typeId: "store",
150
+ key: store1.key,
151
+ },
152
+ ]);
153
+ });
154
+
155
+ test("adding customer without linked stores", async () => {
156
+ const result = repository.create(
157
+ { projectKey: "dummy" },
158
+ {
159
+ email: "my-customer-without-stores@email.com",
160
+ stores: [],
161
+ },
162
+ );
163
+
164
+ expect(result.email).toEqual("my-customer-without-stores@email.com");
165
+ expect(result?.stores).toHaveLength(0);
166
+ });
75
167
  });
@@ -9,7 +9,9 @@ import type {
9
9
  InvalidInputError,
10
10
  MyCustomerResetPassword,
11
11
  ResourceNotFoundError,
12
+ Store,
12
13
  StoreKeyReference,
14
+ StoreResourceIdentifier,
13
15
  } from "@commercetools/platform-sdk";
14
16
  import { CommercetoolsError } from "~src/exceptions";
15
17
  import { generateRandomString, getBaseResourceProperties } from "~src/helpers";
@@ -20,7 +22,7 @@ import {
20
22
  validatePasswordResetToken,
21
23
  } from "~src/lib/password";
22
24
  import type { AbstractStorage } from "~src/storage/abstract";
23
- import type { Writable } from "~src/types";
25
+ import type { ResourceMap, ShallowWritable, Writable } from "~src/types";
24
26
  import {
25
27
  AbstractResourceRepository,
26
28
  type RepositoryContext,
@@ -37,7 +39,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
37
39
  create(context: RepositoryContext, draft: CustomerDraft): Customer {
38
40
  // Check uniqueness
39
41
  const results = this._storage.query(context.projectKey, this.getTypeId(), {
40
- where: [`email="${draft.email.toLocaleLowerCase()}"`],
42
+ where: [`lowercaseEmail="${draft.email.toLowerCase()}"`],
41
43
  });
42
44
  if (results.count > 0) {
43
45
  throw new CommercetoolsError<any>({
@@ -105,29 +107,11 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
105
107
 
106
108
  let storesForCustomer: StoreKeyReference[] = [];
107
109
 
108
- if (draft.stores) {
109
- const storeIds = draft.stores
110
- .map((storeReference) => storeReference.id)
111
- .filter(Boolean);
112
-
113
- const stores = this._storage.query(context.projectKey, "store", {
114
- where: storeIds.map((id) => `id="${id}"`),
115
- }).results;
116
-
117
- if (storeIds.length !== stores.length) {
118
- throw new CommercetoolsError<ResourceNotFoundError>({
119
- code: "ResourceNotFound",
120
- message: `Store with ID '${storeIds.find((id) => !stores.some((store) => store.id === id))}' was not found.`,
121
- });
122
- }
123
-
124
- storesForCustomer = draft.stores.map((storeReference) => ({
125
- typeId: "store",
126
- key:
127
- storeReference.key ??
128
- (stores.find((store) => store.id === storeReference.id)
129
- ?.key as string),
130
- }));
110
+ if (draft.stores && draft.stores.length > 0) {
111
+ storesForCustomer = this.storeReferenceToStoreKeyReference(
112
+ draft.stores,
113
+ context.projectKey,
114
+ );
131
115
  }
132
116
 
133
117
  const resource: Customer = {
@@ -141,6 +125,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
141
125
  dateOfBirth: draft.dateOfBirth,
142
126
  companyName: draft.companyName,
143
127
  email: draft.email.toLowerCase(),
128
+ lowercaseEmail: draft.email.toLowerCase(),
144
129
  password: draft.password ? hashPassword(draft.password) : undefined,
145
130
  isEmailVerified: draft.isEmailVerified || false,
146
131
  addresses: addresses,
@@ -156,10 +141,25 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
156
141
  this._storage,
157
142
  ),
158
143
  stores: storesForCustomer,
159
- };
144
+ } satisfies unknown as Customer;
145
+
160
146
  return this.saveNew(context, resource);
161
147
  }
162
148
 
149
+ saveUpdate(
150
+ context: RepositoryContext,
151
+ version: number,
152
+ resource: ShallowWritable<ResourceMap["customer"]>,
153
+ ): ShallowWritable<ResourceMap["customer"]> {
154
+ // Also update lowercaseEmail attribute
155
+ const updatedResource: Customer = {
156
+ ...resource,
157
+ lowercaseEmail: resource.email.toLowerCase(),
158
+ } satisfies unknown as Customer;
159
+
160
+ return super.saveUpdate(context, version, updatedResource);
161
+ }
162
+
163
163
  passwordResetToken(
164
164
  context: RepositoryContext,
165
165
  request: CustomerCreatePasswordResetToken,
@@ -251,4 +251,35 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
251
251
  value: token,
252
252
  };
253
253
  }
254
+
255
+ private storeReferenceToStoreKeyReference(
256
+ draftStores: StoreResourceIdentifier[],
257
+ projectKey: string,
258
+ ): StoreKeyReference[] {
259
+ const storeIds = draftStores
260
+ .map((storeReference) => storeReference.id)
261
+ .filter(Boolean);
262
+
263
+ let stores: Store[] = [];
264
+
265
+ if (storeIds.length > 0) {
266
+ stores = this._storage.query(projectKey, "store", {
267
+ where: storeIds.map((id) => `id="${id}"`),
268
+ }).results;
269
+
270
+ if (storeIds.length !== stores.length) {
271
+ throw new CommercetoolsError<ResourceNotFoundError>({
272
+ code: "ResourceNotFound",
273
+ message: `Store with ID '${storeIds.find((id) => !stores.some((store) => store.id === id))}' was not found.`,
274
+ });
275
+ }
276
+ }
277
+
278
+ return draftStores.map((storeReference) => ({
279
+ typeId: "store",
280
+ key:
281
+ storeReference.key ??
282
+ (stores.find((store) => store.id === storeReference.id)?.key as string),
283
+ }));
284
+ }
254
285
  }
@@ -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