@labdigital/commercetools-mock 2.45.1 → 2.46.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 +46 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +46 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/predicateParser.test.ts +33 -2
- package/src/lib/predicateParser.ts +6 -0
- package/src/product-search.ts +48 -8
- package/src/repositories/cart/index.ts +17 -0
- package/src/services/cart.test.ts +25 -0
- package/src/services/customer.test.ts +12 -51
- package/src/services/product.test.ts +154 -70
- package/src/testing/customer.ts +40 -0
package/package.json
CHANGED
|
@@ -13,8 +13,12 @@ describe("Predicate filter", () => {
|
|
|
13
13
|
nested: {
|
|
14
14
|
numberProperty: 1234,
|
|
15
15
|
objectProperty: {
|
|
16
|
-
stringProperty: "foobar",
|
|
17
|
-
booleanProperty: true,
|
|
16
|
+
"stringProperty": "foobar",
|
|
17
|
+
"booleanProperty": true,
|
|
18
|
+
"45c652f2-76e8-48fd-ab64-d11ad99d6631": {
|
|
19
|
+
stringProperty: "foobar",
|
|
20
|
+
uuidProperty: "3a57cc78-db08-4cd3-b778-d59b3326c435",
|
|
21
|
+
},
|
|
18
22
|
},
|
|
19
23
|
array: [
|
|
20
24
|
{
|
|
@@ -331,6 +335,33 @@ describe("Predicate filter", () => {
|
|
|
331
335
|
);
|
|
332
336
|
expect(() => match(`stringProperty`)).toThrow(PredicateError);
|
|
333
337
|
});
|
|
338
|
+
|
|
339
|
+
test("uuid as field name", async () => {
|
|
340
|
+
expect(
|
|
341
|
+
match(
|
|
342
|
+
`nested(objectProperty(45c652f2-76e8-48fd-ab64-d11ad99d6631(stringProperty = "foobar")))`,
|
|
343
|
+
),
|
|
344
|
+
).toBeTruthy();
|
|
345
|
+
|
|
346
|
+
expect(
|
|
347
|
+
match(
|
|
348
|
+
`nested(objectProperty(3a57cc78-db08-4cd3-b778-d59b3326c435(stringProperty = "foobar")))`,
|
|
349
|
+
),
|
|
350
|
+
).toBeFalsy();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("uuid as value", async () => {
|
|
354
|
+
expect(
|
|
355
|
+
match(
|
|
356
|
+
`nested(objectProperty(45c652f2-76e8-48fd-ab64-d11ad99d6631(uuidProperty = "3a57cc78-db08-4cd3-b778-d59b3326c435")))`,
|
|
357
|
+
),
|
|
358
|
+
).toBeTruthy();
|
|
359
|
+
expect(
|
|
360
|
+
match(
|
|
361
|
+
`nested(objectProperty(45c652f2-76e8-48fd-ab64-d11ad99d6631(uuidProperty = "45c652f2-76e8-48fd-ab64-d11ad99d6631")))`,
|
|
362
|
+
),
|
|
363
|
+
).toBeFalsy();
|
|
364
|
+
});
|
|
334
365
|
});
|
|
335
366
|
|
|
336
367
|
describe("Report parse errors", () => {
|
|
@@ -124,6 +124,12 @@ const getLexer = (value: string) =>
|
|
|
124
124
|
.token("IS", /is(?![-_a-z0-9]+)/i)
|
|
125
125
|
.token("DEFINED", /defined(?![-_a-z0-9]+)/i)
|
|
126
126
|
|
|
127
|
+
// Special case for UUID identifiers,
|
|
128
|
+
// since they otherwise would get matched as INT, when starting with a digit
|
|
129
|
+
.token(
|
|
130
|
+
"IDENTIFIER",
|
|
131
|
+
/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/,
|
|
132
|
+
)
|
|
127
133
|
.token("FLOAT", /\d+\.\d+/)
|
|
128
134
|
.token("INT", /\d+/)
|
|
129
135
|
.token("VARIABLE", /:([-_A-Za-z0-9]+)/)
|
package/src/product-search.ts
CHANGED
|
@@ -13,6 +13,12 @@ import { validateSearchQuery } from "./lib/searchQueryTypeChecker";
|
|
|
13
13
|
import { applyPriceSelector } from "./priceSelector";
|
|
14
14
|
import type { AbstractStorage } from "./storage";
|
|
15
15
|
|
|
16
|
+
interface ProductSearchVariantAvailability {
|
|
17
|
+
isOnStock: boolean;
|
|
18
|
+
availableQuantity: number;
|
|
19
|
+
isOnStockForChannel: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export class ProductSearch {
|
|
17
23
|
protected _storage: AbstractStorage;
|
|
18
24
|
|
|
@@ -24,10 +30,32 @@ export class ProductSearch {
|
|
|
24
30
|
projectKey: string,
|
|
25
31
|
params: ProductSearchRequest,
|
|
26
32
|
): ProductPagedSearchResponse {
|
|
27
|
-
|
|
33
|
+
const availabilityBySku = this._storage
|
|
34
|
+
.all(projectKey, "inventory-entry")
|
|
35
|
+
.reduce((acc, entry) => {
|
|
36
|
+
const existingEntry = acc.get(entry.sku);
|
|
37
|
+
|
|
38
|
+
acc.set(entry.sku, {
|
|
39
|
+
isOnStock: existingEntry?.isOnStock || entry.quantityOnStock > 0,
|
|
40
|
+
availableQuantity:
|
|
41
|
+
existingEntry?.availableQuantity ?? 0 + entry.quantityOnStock,
|
|
42
|
+
// NOTE: This doesn't handle inventory entries for multiple channels,
|
|
43
|
+
// so it doesn't exactly replicate the behavior of the commercetools api.
|
|
44
|
+
isOnStockForChannel:
|
|
45
|
+
existingEntry?.isOnStockForChannel ?? entry.supplyChannel?.id,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return acc;
|
|
49
|
+
}, new Map<string, ProductSearchVariantAvailability>());
|
|
50
|
+
|
|
51
|
+
let productResources = this._storage
|
|
28
52
|
.all(projectKey, "product")
|
|
29
53
|
.map((r) =>
|
|
30
|
-
this.
|
|
54
|
+
this.transformProduct(
|
|
55
|
+
r,
|
|
56
|
+
params.productProjectionParameters?.staged ?? false,
|
|
57
|
+
availabilityBySku,
|
|
58
|
+
),
|
|
31
59
|
)
|
|
32
60
|
.filter((p) => {
|
|
33
61
|
if (!(params.productProjectionParameters?.staged ?? false)) {
|
|
@@ -46,7 +74,7 @@ export class ProductSearch {
|
|
|
46
74
|
const matchFunc = parseSearchQuery(params.query);
|
|
47
75
|
|
|
48
76
|
// Filters can modify the output. So clone the resources first.
|
|
49
|
-
|
|
77
|
+
productResources = productResources.filter((resource) =>
|
|
50
78
|
matchFunc(resource, markMatchingVariant),
|
|
51
79
|
);
|
|
52
80
|
} catch (err) {
|
|
@@ -63,7 +91,7 @@ export class ProductSearch {
|
|
|
63
91
|
|
|
64
92
|
// Apply the priceSelector
|
|
65
93
|
if (params.productProjectionParameters) {
|
|
66
|
-
applyPriceSelector(
|
|
94
|
+
applyPriceSelector(productResources, {
|
|
67
95
|
country: params.productProjectionParameters.priceCountry,
|
|
68
96
|
channel: params.productProjectionParameters.priceChannel,
|
|
69
97
|
customerGroup: params.productProjectionParameters.priceCustomerGroup,
|
|
@@ -76,7 +104,10 @@ export class ProductSearch {
|
|
|
76
104
|
|
|
77
105
|
const offset = params.offset || 0;
|
|
78
106
|
const limit = params.limit || 20;
|
|
79
|
-
const productProjectionsResult =
|
|
107
|
+
const productProjectionsResult = productResources.slice(
|
|
108
|
+
offset,
|
|
109
|
+
offset + limit,
|
|
110
|
+
);
|
|
80
111
|
|
|
81
112
|
/**
|
|
82
113
|
* Do not supply productProjection if productProjectionParameters are not given
|
|
@@ -100,7 +131,7 @@ export class ProductSearch {
|
|
|
100
131
|
);
|
|
101
132
|
|
|
102
133
|
return {
|
|
103
|
-
total:
|
|
134
|
+
total: productResources.length,
|
|
104
135
|
offset: offset,
|
|
105
136
|
limit: limit,
|
|
106
137
|
results: results,
|
|
@@ -108,7 +139,11 @@ export class ProductSearch {
|
|
|
108
139
|
};
|
|
109
140
|
}
|
|
110
141
|
|
|
111
|
-
|
|
142
|
+
transformProduct(
|
|
143
|
+
product: Product,
|
|
144
|
+
staged: boolean,
|
|
145
|
+
availabilityBySku: Map<string, ProductSearchVariantAvailability>,
|
|
146
|
+
): ProductProjection {
|
|
112
147
|
const obj = !staged
|
|
113
148
|
? product.masterData.current
|
|
114
149
|
: product.masterData.staged;
|
|
@@ -125,7 +160,12 @@ export class ProductSearch {
|
|
|
125
160
|
slug: obj.slug,
|
|
126
161
|
categories: obj.categories,
|
|
127
162
|
masterVariant: obj.masterVariant,
|
|
128
|
-
variants: obj.variants
|
|
163
|
+
variants: obj.variants.map((variant) => ({
|
|
164
|
+
...variant,
|
|
165
|
+
availability: variant.sku
|
|
166
|
+
? availabilityBySku.get(variant.sku)
|
|
167
|
+
: { isOnStock: false, availableQuantity: 0, isOnStockForChannel: [] },
|
|
168
|
+
})),
|
|
129
169
|
productType: product.productType,
|
|
130
170
|
hasStagedChanges: product.masterData.hasStagedChanges,
|
|
131
171
|
published: product.masterData.published,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InvalidOperationError } from "@commercetools/platform-sdk";
|
|
1
2
|
import {
|
|
2
3
|
type Cart,
|
|
3
4
|
type CartDraft,
|
|
@@ -27,6 +28,21 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
create(context: RepositoryContext, draft: CartDraft): Cart {
|
|
31
|
+
if (draft.anonymousId && draft.customerId) {
|
|
32
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
33
|
+
code: "InvalidOperation",
|
|
34
|
+
message: "Can set only one of customer OR anonymousId",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate that the customer exists
|
|
39
|
+
if (draft.customerId) {
|
|
40
|
+
this._storage.getByResourceIdentifier(context.projectKey, {
|
|
41
|
+
typeId: "customer",
|
|
42
|
+
id: draft.customerId,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
const lineItems =
|
|
31
47
|
draft.lineItems?.map((draftLineItem) =>
|
|
32
48
|
this.draftLineItemtoLineItem(
|
|
@@ -45,6 +61,7 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
|
|
|
45
61
|
: undefined,
|
|
46
62
|
cartState: "Active",
|
|
47
63
|
country: draft.country,
|
|
64
|
+
customerId: draft.customerId,
|
|
48
65
|
customerEmail: draft.customerEmail,
|
|
49
66
|
customLineItems: [],
|
|
50
67
|
directDiscounts: [],
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
import assert from "assert";
|
|
14
14
|
import supertest from "supertest";
|
|
15
15
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
16
|
+
import { customerDraftFactory } from "~src/testing/customer";
|
|
16
17
|
import { CommercetoolsMock } from "../index";
|
|
17
18
|
|
|
18
19
|
describe("Carts Query", () => {
|
|
@@ -81,6 +82,30 @@ describe("Carts Query", () => {
|
|
|
81
82
|
expect(myCart.custom?.type.id).toBe(myCart.custom?.type.obj?.id);
|
|
82
83
|
expect(myCart.custom?.type.obj?.description?.en).toBe("Test Type");
|
|
83
84
|
});
|
|
85
|
+
|
|
86
|
+
test("throw error when anonymousId and customerId are given", async () => {
|
|
87
|
+
const customerId = "400be09e-bfe8-4925-a307-4ef6280b063e";
|
|
88
|
+
const anonymousId = "a99f27d1-7e7e-4592-8d5a-aa5da1adfe24";
|
|
89
|
+
const response = await supertest(ctMock.app).post("/dummy/carts").send({
|
|
90
|
+
currency: "EUR",
|
|
91
|
+
anonymousId,
|
|
92
|
+
customerId,
|
|
93
|
+
});
|
|
94
|
+
expect(response.status).toBe(400);
|
|
95
|
+
expect(response.body.message).toBe(
|
|
96
|
+
"Can set only one of customer OR anonymousId",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("create cart with existing customer", async () => {
|
|
101
|
+
const customer = await customerDraftFactory(ctMock).create();
|
|
102
|
+
const response = await supertest(ctMock.app).post("/dummy/carts").send({
|
|
103
|
+
currency: "EUR",
|
|
104
|
+
customerId: customer.id,
|
|
105
|
+
});
|
|
106
|
+
expect(response.status).toBe(201);
|
|
107
|
+
expect(response.body.customerId).toBe(customer.id);
|
|
108
|
+
});
|
|
84
109
|
});
|
|
85
110
|
|
|
86
111
|
describe("Cart Update Actions", () => {
|
|
@@ -4,60 +4,21 @@ import type {
|
|
|
4
4
|
CustomerToken,
|
|
5
5
|
} from "@commercetools/platform-sdk";
|
|
6
6
|
import assert from "assert";
|
|
7
|
-
import { Factory } from "fishery";
|
|
8
7
|
import supertest from "supertest";
|
|
9
8
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
10
9
|
import { hashPassword } from "~src/lib/password";
|
|
10
|
+
import { customerDraftFactory } from "~src/testing/customer";
|
|
11
11
|
import { CommercetoolsMock, getBaseResourceProperties } from "../index";
|
|
12
12
|
|
|
13
13
|
const ctMock = new CommercetoolsMock();
|
|
14
14
|
|
|
15
|
-
const customerDraftFactory = Factory.define<
|
|
16
|
-
CustomerDraft,
|
|
17
|
-
CustomerDraft,
|
|
18
|
-
Customer
|
|
19
|
-
>(({ onCreate }) => {
|
|
20
|
-
onCreate(async (draft) => {
|
|
21
|
-
const response = await supertest(ctMock.app)
|
|
22
|
-
.post(`/dummy/customers`)
|
|
23
|
-
.send(draft);
|
|
24
|
-
|
|
25
|
-
return response.body.customer;
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
email: "customer@example.com",
|
|
30
|
-
firstName: "John",
|
|
31
|
-
lastName: "Doe",
|
|
32
|
-
locale: "nl-NL",
|
|
33
|
-
password: "my-secret-pw",
|
|
34
|
-
addresses: [
|
|
35
|
-
{
|
|
36
|
-
firstName: "John",
|
|
37
|
-
lastName: "Doe",
|
|
38
|
-
streetName: "Street name",
|
|
39
|
-
streetNumber: "42",
|
|
40
|
-
postalCode: "1234 AB",
|
|
41
|
-
city: "Utrecht",
|
|
42
|
-
country: "NL",
|
|
43
|
-
company: "Lab Digital",
|
|
44
|
-
phone: "+31612345678",
|
|
45
|
-
email: "customer@example.com",
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
isEmailVerified: false,
|
|
49
|
-
stores: [],
|
|
50
|
-
authenticationMode: "Password",
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
|
|
54
15
|
afterEach(() => {
|
|
55
16
|
ctMock.clear();
|
|
56
17
|
});
|
|
57
18
|
|
|
58
19
|
describe("Customer create", () => {
|
|
59
20
|
test("create new customer", async () => {
|
|
60
|
-
const draft = customerDraftFactory.build();
|
|
21
|
+
const draft = customerDraftFactory(ctMock).build();
|
|
61
22
|
|
|
62
23
|
const response = await supertest(ctMock.app)
|
|
63
24
|
.post(`/dummy/customers`)
|
|
@@ -109,7 +70,7 @@ describe("Customer create", () => {
|
|
|
109
70
|
|
|
110
71
|
describe("Customer Update Actions", () => {
|
|
111
72
|
test("addAddress", async () => {
|
|
112
|
-
const customer = await customerDraftFactory.create();
|
|
73
|
+
const customer = await customerDraftFactory(ctMock).create();
|
|
113
74
|
const response = await supertest(ctMock.app)
|
|
114
75
|
.post(`/dummy/customers/${customer.id}`)
|
|
115
76
|
.send({
|
|
@@ -134,7 +95,7 @@ describe("Customer Update Actions", () => {
|
|
|
134
95
|
});
|
|
135
96
|
|
|
136
97
|
test("removeAddress by ID", async () => {
|
|
137
|
-
const customer = await customerDraftFactory.create({
|
|
98
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
138
99
|
addresses: [
|
|
139
100
|
{
|
|
140
101
|
key: "address-key",
|
|
@@ -165,7 +126,7 @@ describe("Customer Update Actions", () => {
|
|
|
165
126
|
});
|
|
166
127
|
|
|
167
128
|
test("removeAddress by Key", async () => {
|
|
168
|
-
const customer = await customerDraftFactory.create({
|
|
129
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
169
130
|
addresses: [
|
|
170
131
|
{
|
|
171
132
|
key: "address-key",
|
|
@@ -196,7 +157,7 @@ describe("Customer Update Actions", () => {
|
|
|
196
157
|
});
|
|
197
158
|
|
|
198
159
|
test("changeAddress by ID", async () => {
|
|
199
|
-
const customer = await customerDraftFactory.create({
|
|
160
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
200
161
|
addresses: [
|
|
201
162
|
{
|
|
202
163
|
key: "address-key",
|
|
@@ -248,7 +209,7 @@ describe("Customer Update Actions", () => {
|
|
|
248
209
|
});
|
|
249
210
|
|
|
250
211
|
test("addBillingAddressId", async () => {
|
|
251
|
-
const customer = await customerDraftFactory.create({
|
|
212
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
252
213
|
addresses: [
|
|
253
214
|
{
|
|
254
215
|
key: "address-key",
|
|
@@ -280,7 +241,7 @@ describe("Customer Update Actions", () => {
|
|
|
280
241
|
});
|
|
281
242
|
|
|
282
243
|
test("removeBillingAddressId", async () => {
|
|
283
|
-
const customer = await customerDraftFactory.create({
|
|
244
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
284
245
|
addresses: [
|
|
285
246
|
{
|
|
286
247
|
key: "address-key",
|
|
@@ -318,7 +279,7 @@ describe("Customer Update Actions", () => {
|
|
|
318
279
|
});
|
|
319
280
|
|
|
320
281
|
test("setDefaultBillingAddress by ID", async () => {
|
|
321
|
-
const customer = await customerDraftFactory.create({
|
|
282
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
322
283
|
defaultBillingAddress: undefined,
|
|
323
284
|
defaultShippingAddress: undefined,
|
|
324
285
|
addresses: [
|
|
@@ -356,7 +317,7 @@ describe("Customer Update Actions", () => {
|
|
|
356
317
|
});
|
|
357
318
|
|
|
358
319
|
test("addShippingAddressId", async () => {
|
|
359
|
-
const customer = await customerDraftFactory.create({
|
|
320
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
360
321
|
addresses: [
|
|
361
322
|
{
|
|
362
323
|
key: "address-key",
|
|
@@ -387,7 +348,7 @@ describe("Customer Update Actions", () => {
|
|
|
387
348
|
});
|
|
388
349
|
|
|
389
350
|
test("removeShippingAddressId", async () => {
|
|
390
|
-
const customer = await customerDraftFactory.create({
|
|
351
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
391
352
|
addresses: [
|
|
392
353
|
{
|
|
393
354
|
key: "address-key",
|
|
@@ -425,7 +386,7 @@ describe("Customer Update Actions", () => {
|
|
|
425
386
|
});
|
|
426
387
|
|
|
427
388
|
test("setDefaultShippingAddress by ID", async () => {
|
|
428
|
-
const customer = await customerDraftFactory.create({
|
|
389
|
+
const customer = await customerDraftFactory(ctMock).create({
|
|
429
390
|
defaultBillingAddress: undefined,
|
|
430
391
|
defaultShippingAddress: undefined,
|
|
431
392
|
addresses: [
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
Category,
|
|
3
3
|
CategoryDraft,
|
|
4
4
|
Image,
|
|
5
|
+
InventoryEntryDraft,
|
|
5
6
|
PriceDraft,
|
|
6
7
|
Product,
|
|
7
8
|
ProductData,
|
|
@@ -20,7 +21,14 @@ import type {
|
|
|
20
21
|
} from "@commercetools/platform-sdk";
|
|
21
22
|
import assert from "assert";
|
|
22
23
|
import supertest from "supertest";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
afterAll,
|
|
26
|
+
beforeAll,
|
|
27
|
+
beforeEach,
|
|
28
|
+
describe,
|
|
29
|
+
expect,
|
|
30
|
+
test,
|
|
31
|
+
} from "vitest";
|
|
24
32
|
import { CommercetoolsMock } from "../index";
|
|
25
33
|
|
|
26
34
|
const productTypeDraft: ProductTypeDraft = {
|
|
@@ -492,6 +500,10 @@ describe("Product update actions", () => {
|
|
|
492
500
|
expect(response.status).toBe(201);
|
|
493
501
|
});
|
|
494
502
|
|
|
503
|
+
afterAll(async () => {
|
|
504
|
+
ctMock.clear();
|
|
505
|
+
});
|
|
506
|
+
|
|
495
507
|
test("setAttribute masterVariant (staged)", async () => {
|
|
496
508
|
assert(productPublished, "product not created");
|
|
497
509
|
|
|
@@ -1488,76 +1500,148 @@ describe("Product update actions", () => {
|
|
|
1488
1500
|
?.myCustomField,
|
|
1489
1501
|
).toBe("MyRandomValue");
|
|
1490
1502
|
});
|
|
1503
|
+
});
|
|
1491
1504
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
// Find product with sku "1337" to be part of the search results
|
|
1523
|
-
const productFound = results.find(
|
|
1524
|
-
(result) => result?.productProjection?.masterVariant?.sku === "1337",
|
|
1525
|
-
);
|
|
1526
|
-
expect(productFound).toBeDefined();
|
|
1527
|
-
|
|
1528
|
-
const priceCurrencyMatch = results.find((result) =>
|
|
1529
|
-
result?.productProjection?.masterVariant?.prices?.find(
|
|
1530
|
-
(price) => price?.value?.currencyCode === "EUR",
|
|
1531
|
-
),
|
|
1532
|
-
);
|
|
1533
|
-
expect(priceCurrencyMatch).toBeDefined();
|
|
1534
|
-
}
|
|
1535
|
-
{
|
|
1536
|
-
const body: ProductSearchRequest = {
|
|
1537
|
-
limit: 88,
|
|
1538
|
-
offset: 88,
|
|
1539
|
-
};
|
|
1540
|
-
|
|
1541
|
-
const response = await supertest(ctMock.app)
|
|
1542
|
-
.post("/dummy/products/search")
|
|
1543
|
-
.send(body);
|
|
1544
|
-
|
|
1545
|
-
const pagedSearchResponse: ProductPagedSearchResponse = response.body;
|
|
1546
|
-
expect(pagedSearchResponse.limit).toBe(88);
|
|
1547
|
-
expect(pagedSearchResponse.offset).toBe(88);
|
|
1548
|
-
expect(pagedSearchResponse.total).toBeGreaterThan(0);
|
|
1549
|
-
|
|
1550
|
-
// No results, since we start at offset 88
|
|
1551
|
-
const results: ProductSearchResult[] = pagedSearchResponse.results;
|
|
1552
|
-
expect(results).toBeDefined();
|
|
1553
|
-
expect(results.length).toBe(0);
|
|
1554
|
-
|
|
1555
|
-
// Product with sku "1337" should not be part of the results
|
|
1556
|
-
const productFound = results.find(
|
|
1557
|
-
(result) => result?.productProjection?.masterVariant?.sku === "1337",
|
|
1558
|
-
);
|
|
1559
|
-
expect(productFound).toBeUndefined();
|
|
1560
|
-
}
|
|
1505
|
+
// Test the general product search implementation
|
|
1506
|
+
describe("Product Search - Generic", () => {
|
|
1507
|
+
const ctMock = new CommercetoolsMock();
|
|
1508
|
+
|
|
1509
|
+
async function addInventoryEntry(sku: string, quantity: number) {
|
|
1510
|
+
const inventoryEntryDraft: InventoryEntryDraft = {
|
|
1511
|
+
key: `${sku}_stock`,
|
|
1512
|
+
sku,
|
|
1513
|
+
quantityOnStock: quantity,
|
|
1514
|
+
supplyChannel: {
|
|
1515
|
+
typeId: "channel",
|
|
1516
|
+
id: "dummy-inventory-channel",
|
|
1517
|
+
},
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
await supertest(ctMock.app)
|
|
1521
|
+
.post("/dummy/inventory")
|
|
1522
|
+
.send(inventoryEntryDraft);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
beforeAll(async () => {
|
|
1526
|
+
await beforeAllProductTests(ctMock);
|
|
1527
|
+
|
|
1528
|
+
new Array(24).fill(null).forEach(async () => {
|
|
1529
|
+
const response = await supertest(ctMock.app)
|
|
1530
|
+
.post("/dummy/products")
|
|
1531
|
+
.send(publishedProductDraft);
|
|
1532
|
+
|
|
1533
|
+
expect(response.status).toBe(201);
|
|
1561
1534
|
});
|
|
1562
1535
|
});
|
|
1536
|
+
|
|
1537
|
+
test("Pagination", async () => {
|
|
1538
|
+
{
|
|
1539
|
+
const body: ProductSearchRequest = {
|
|
1540
|
+
productProjectionParameters: {
|
|
1541
|
+
storeProjection: "dummy-store",
|
|
1542
|
+
localeProjection: ["en-US"],
|
|
1543
|
+
priceCurrency: "EUR",
|
|
1544
|
+
priceChannel: "dummy-channel",
|
|
1545
|
+
expand: ["categories[*]", "categories[*].ancestors[*]"],
|
|
1546
|
+
},
|
|
1547
|
+
limit: 24,
|
|
1548
|
+
};
|
|
1549
|
+
const response = await supertest(ctMock.app)
|
|
1550
|
+
.post("/dummy/products/search")
|
|
1551
|
+
.send(body);
|
|
1552
|
+
|
|
1553
|
+
const pagedSearchResponse: ProductPagedSearchResponse = response.body;
|
|
1554
|
+
expect(pagedSearchResponse.limit).toBe(24);
|
|
1555
|
+
expect(pagedSearchResponse.offset).toBe(0);
|
|
1556
|
+
expect(pagedSearchResponse.total).toBeGreaterThan(0);
|
|
1557
|
+
|
|
1558
|
+
// Deliberately not supported fow now
|
|
1559
|
+
expect(pagedSearchResponse.facets).toEqual([]);
|
|
1560
|
+
|
|
1561
|
+
const results: ProductSearchResult[] = pagedSearchResponse.results;
|
|
1562
|
+
expect(results).toBeDefined();
|
|
1563
|
+
expect(results.length).toBeGreaterThan(0);
|
|
1564
|
+
|
|
1565
|
+
// Find product with sku "1337" to be part of the search results
|
|
1566
|
+
const productFound = results.find(
|
|
1567
|
+
(result) => result?.productProjection?.masterVariant?.sku === "1337",
|
|
1568
|
+
);
|
|
1569
|
+
expect(productFound).toBeDefined();
|
|
1570
|
+
|
|
1571
|
+
const priceCurrencyMatch = results.find((result) =>
|
|
1572
|
+
result?.productProjection?.masterVariant?.prices?.find(
|
|
1573
|
+
(price) => price?.value?.currencyCode === "EUR",
|
|
1574
|
+
),
|
|
1575
|
+
);
|
|
1576
|
+
expect(priceCurrencyMatch).toBeDefined();
|
|
1577
|
+
}
|
|
1578
|
+
{
|
|
1579
|
+
const body: ProductSearchRequest = {
|
|
1580
|
+
limit: 88,
|
|
1581
|
+
offset: 88,
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
const response = await supertest(ctMock.app)
|
|
1585
|
+
.post("/dummy/products/search")
|
|
1586
|
+
.send(body);
|
|
1587
|
+
|
|
1588
|
+
const pagedSearchResponse: ProductPagedSearchResponse = response.body;
|
|
1589
|
+
expect(pagedSearchResponse.limit).toBe(88);
|
|
1590
|
+
expect(pagedSearchResponse.offset).toBe(88);
|
|
1591
|
+
expect(pagedSearchResponse.total).toBeGreaterThan(0);
|
|
1592
|
+
|
|
1593
|
+
// No results, since we start at offset 88
|
|
1594
|
+
const results: ProductSearchResult[] = pagedSearchResponse.results;
|
|
1595
|
+
expect(results).toBeDefined();
|
|
1596
|
+
expect(results.length).toBe(0);
|
|
1597
|
+
|
|
1598
|
+
// Product with sku "1337" should not be part of the results
|
|
1599
|
+
const productFound = results.find(
|
|
1600
|
+
(result) => result?.productProjection?.masterVariant?.sku === "1337",
|
|
1601
|
+
);
|
|
1602
|
+
expect(productFound).toBeUndefined();
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
test("Filter on inventory", async () => {
|
|
1607
|
+
const body: ProductSearchRequest = {
|
|
1608
|
+
query: {
|
|
1609
|
+
exact: {
|
|
1610
|
+
field: "variants.availability.isOnStockForChannel",
|
|
1611
|
+
value: "dummy-inventory-channel",
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
productProjectionParameters: {
|
|
1615
|
+
storeProjection: "dummy-store",
|
|
1616
|
+
localeProjection: ["en-US"],
|
|
1617
|
+
priceCurrency: "EUR",
|
|
1618
|
+
priceChannel: "dummy-channel",
|
|
1619
|
+
},
|
|
1620
|
+
limit: 1,
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
const response1 = await supertest(ctMock.app)
|
|
1624
|
+
.post("/dummy/products/search")
|
|
1625
|
+
.send(body);
|
|
1626
|
+
|
|
1627
|
+
const pagedSearchResponse1: ProductPagedSearchResponse = response1.body;
|
|
1628
|
+
|
|
1629
|
+
expect(pagedSearchResponse1.results.length).toBe(0);
|
|
1630
|
+
|
|
1631
|
+
await addInventoryEntry(
|
|
1632
|
+
publishedProductDraft.variants?.[0]?.sku as string,
|
|
1633
|
+
10,
|
|
1634
|
+
);
|
|
1635
|
+
|
|
1636
|
+
const response2 = await supertest(ctMock.app)
|
|
1637
|
+
.post("/dummy/products/search")
|
|
1638
|
+
.send(body);
|
|
1639
|
+
|
|
1640
|
+
const pagedSearchResponse2: ProductPagedSearchResponse = response2.body;
|
|
1641
|
+
|
|
1642
|
+
const productFound = pagedSearchResponse2.results.find(
|
|
1643
|
+
(result) => result?.productProjection?.masterVariant?.sku === "1337",
|
|
1644
|
+
);
|
|
1645
|
+
expect(productFound).toBeDefined();
|
|
1646
|
+
});
|
|
1563
1647
|
});
|