@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/dist/index.cjs +57 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +57 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/repositories/customer/index.test.ts +94 -2
- package/src/repositories/customer/index.ts +57 -26
- package/src/repositories/order/actions.ts +26 -12
- package/src/services/my-customer.test.ts +1 -0
- package/src/services/order.test.ts +150 -0
package/package.json
CHANGED
|
@@ -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("
|
|
6
|
+
describe("Customer repository", () => {
|
|
7
7
|
const storage = new InMemoryStorage();
|
|
8
8
|
const repository = new CustomerRepository(storage);
|
|
9
9
|
|
|
10
|
-
test("
|
|
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: [`
|
|
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
|
-
|
|
110
|
-
.
|
|
111
|
-
.
|
|
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
|
-
|
|
197
|
+
if (!resource.shippingInfo) {
|
|
198
|
+
throw new Error("Resource has no shipping info");
|
|
199
|
+
}
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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>,
|
|
@@ -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
|
|