@labdigital/commercetools-mock 2.39.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/dist/index.cjs +51 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +51 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/repositories/customer/index.test.ts +127 -0
- package/src/repositories/customer/index.ts +48 -4
- 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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Store } from "@commercetools/platform-sdk";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { InMemoryStorage } from "~src/storage";
|
|
4
|
+
import { CustomerRepository } from "./index";
|
|
5
|
+
|
|
6
|
+
describe("Customer repository", () => {
|
|
7
|
+
const storage = new InMemoryStorage();
|
|
8
|
+
const repository = new CustomerRepository(storage);
|
|
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
|
+
|
|
49
|
+
test("adding stores to customer", async () => {
|
|
50
|
+
const store1: Store = {
|
|
51
|
+
id: "d0016081-e9af-48a7-8133-1f04f340a335",
|
|
52
|
+
key: "store-1",
|
|
53
|
+
name: {
|
|
54
|
+
en: "Store 1",
|
|
55
|
+
},
|
|
56
|
+
version: 1,
|
|
57
|
+
createdAt: "2021-09-02T12:23:30.036Z",
|
|
58
|
+
lastModifiedAt: "2021-09-02T12:23:30.546Z",
|
|
59
|
+
languages: [],
|
|
60
|
+
distributionChannels: [],
|
|
61
|
+
countries: [],
|
|
62
|
+
supplyChannels: [],
|
|
63
|
+
productSelections: [],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const store2: Store = {
|
|
67
|
+
id: "6dac7d6d-2a48-4705-aa8b-17b0124a499a",
|
|
68
|
+
key: "store-2",
|
|
69
|
+
name: {
|
|
70
|
+
en: "Store 2",
|
|
71
|
+
},
|
|
72
|
+
version: 1,
|
|
73
|
+
createdAt: "2021-09-02T12:23:30.036Z",
|
|
74
|
+
lastModifiedAt: "2021-09-02T12:23:30.546Z",
|
|
75
|
+
languages: [],
|
|
76
|
+
distributionChannels: [],
|
|
77
|
+
countries: [],
|
|
78
|
+
supplyChannels: [],
|
|
79
|
+
productSelections: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
storage.add("dummy", "store", store1);
|
|
83
|
+
storage.add("dummy", "store", store2);
|
|
84
|
+
|
|
85
|
+
const result = repository.create(
|
|
86
|
+
{ projectKey: "dummy" },
|
|
87
|
+
{
|
|
88
|
+
email: "my-customer@email.com",
|
|
89
|
+
stores: [
|
|
90
|
+
{
|
|
91
|
+
typeId: "store",
|
|
92
|
+
id: store1.id,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
typeId: "store",
|
|
96
|
+
key: store2.key,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(result?.stores).toHaveLength(2);
|
|
103
|
+
expect(result?.stores).toEqual([
|
|
104
|
+
{
|
|
105
|
+
typeId: "store",
|
|
106
|
+
key: store1.key,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
typeId: "store",
|
|
110
|
+
key: store2.key,
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
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
|
+
});
|
|
127
|
+
});
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
InvalidInputError,
|
|
10
10
|
MyCustomerResetPassword,
|
|
11
11
|
ResourceNotFoundError,
|
|
12
|
+
StoreKeyReference,
|
|
12
13
|
} from "@commercetools/platform-sdk";
|
|
13
14
|
import { CommercetoolsError } from "~src/exceptions";
|
|
14
15
|
import { generateRandomString, getBaseResourceProperties } from "~src/helpers";
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
validatePasswordResetToken,
|
|
20
21
|
} from "~src/lib/password";
|
|
21
22
|
import type { AbstractStorage } from "~src/storage/abstract";
|
|
22
|
-
import type { Writable } from "~src/types";
|
|
23
|
+
import type { ResourceMap, ShallowWritable, Writable } from "~src/types";
|
|
23
24
|
import {
|
|
24
25
|
AbstractResourceRepository,
|
|
25
26
|
type RepositoryContext,
|
|
@@ -36,7 +37,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
|
|
|
36
37
|
create(context: RepositoryContext, draft: CustomerDraft): Customer {
|
|
37
38
|
// Check uniqueness
|
|
38
39
|
const results = this._storage.query(context.projectKey, this.getTypeId(), {
|
|
39
|
-
where: [`
|
|
40
|
+
where: [`lowercaseEmail="${draft.email.toLowerCase()}"`],
|
|
40
41
|
});
|
|
41
42
|
if (results.count > 0) {
|
|
42
43
|
throw new CommercetoolsError<any>({
|
|
@@ -102,6 +103,33 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
|
|
|
102
103
|
lookupAdressId(addresses, addressId),
|
|
103
104
|
) ?? [];
|
|
104
105
|
|
|
106
|
+
let storesForCustomer: StoreKeyReference[] = [];
|
|
107
|
+
|
|
108
|
+
if (draft.stores && draft.stores.length > 0) {
|
|
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
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
const resource: Customer = {
|
|
106
134
|
...getBaseResourceProperties(),
|
|
107
135
|
key: draft.key,
|
|
@@ -113,6 +141,7 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
|
|
|
113
141
|
dateOfBirth: draft.dateOfBirth,
|
|
114
142
|
companyName: draft.companyName,
|
|
115
143
|
email: draft.email.toLowerCase(),
|
|
144
|
+
lowercaseEmail: draft.email.toLowerCase(),
|
|
116
145
|
password: draft.password ? hashPassword(draft.password) : undefined,
|
|
117
146
|
isEmailVerified: draft.isEmailVerified || false,
|
|
118
147
|
addresses: addresses,
|
|
@@ -127,11 +156,26 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
|
|
|
127
156
|
context.projectKey,
|
|
128
157
|
this._storage,
|
|
129
158
|
),
|
|
130
|
-
stores:
|
|
131
|
-
};
|
|
159
|
+
stores: storesForCustomer,
|
|
160
|
+
} satisfies unknown as Customer;
|
|
161
|
+
|
|
132
162
|
return this.saveNew(context, resource);
|
|
133
163
|
}
|
|
134
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
|
+
|
|
135
179
|
passwordResetToken(
|
|
136
180
|
context: RepositoryContext,
|
|
137
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
|
-
|
|
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
|
|