@labdigital/commercetools-mock 0.7.1 → 0.9.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.d.ts +26 -1
- package/dist/index.global.js +387 -92
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +384 -89
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +384 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/helpers.ts +24 -0
- package/src/lib/predicateParser.test.ts +16 -0
- package/src/lib/predicateParser.ts +8 -1
- package/src/lib/projectionSearchFilter.test.ts +6 -0
- package/src/lib/projectionSearchFilter.ts +173 -74
- package/src/product-projection-search.ts +210 -10
- package/src/repositories/abstract.ts +23 -5
- package/src/repositories/extension.ts +18 -1
- package/src/repositories/helpers.ts +37 -3
- package/src/repositories/product-projection.ts +1 -0
- package/src/repositories/project.ts +10 -5
- package/src/repositories/state.ts +14 -0
- package/src/services/product-projection.test.ts +170 -3
- package/src/validate.js +0 -1
|
@@ -4,9 +4,25 @@ import {
|
|
|
4
4
|
Product,
|
|
5
5
|
ProductProjection,
|
|
6
6
|
QueryParam,
|
|
7
|
+
FacetResults,
|
|
8
|
+
FacetTerm,
|
|
9
|
+
TermFacetResult,
|
|
10
|
+
RangeFacetResult,
|
|
11
|
+
FilteredFacetResult,
|
|
7
12
|
} from '@commercetools/platform-sdk'
|
|
13
|
+
import { ByProjectKeyProductProjectionsSearchRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/search/by-project-key-product-projections-search-request-builder'
|
|
14
|
+
import { nestedLookup } from './helpers'
|
|
15
|
+
import { ProductService } from './services/product'
|
|
16
|
+
import { Writable } from './types'
|
|
8
17
|
import { CommercetoolsError } from './exceptions'
|
|
9
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
FilterExpression,
|
|
20
|
+
generateFacetFunc,
|
|
21
|
+
getVariants,
|
|
22
|
+
parseFilterExpression,
|
|
23
|
+
RangeExpression,
|
|
24
|
+
resolveVariantValue,
|
|
25
|
+
} from './lib/projectionSearchFilter'
|
|
10
26
|
import { applyPriceSelector } from './priceSelector'
|
|
11
27
|
import { AbstractStorage } from './storage'
|
|
12
28
|
|
|
@@ -68,10 +84,9 @@ export class ProductProjectionSearch {
|
|
|
68
84
|
)
|
|
69
85
|
|
|
70
86
|
// Filters can modify the output. So clone the resources first.
|
|
71
|
-
resources = resources
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
)
|
|
87
|
+
resources = resources.filter(resource =>
|
|
88
|
+
filters.every(f => f(resource, markMatchingVariant))
|
|
89
|
+
)
|
|
75
90
|
} catch (err) {
|
|
76
91
|
throw new CommercetoolsError<InvalidInputError>(
|
|
77
92
|
{
|
|
@@ -84,6 +99,7 @@ export class ProductProjectionSearch {
|
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
// TODO: Calculate facets
|
|
102
|
+
const facets = this.getFacets(params, resources)
|
|
87
103
|
|
|
88
104
|
// Apply filters post facetting
|
|
89
105
|
if (params['filter.query']) {
|
|
@@ -91,10 +107,10 @@ export class ProductProjectionSearch {
|
|
|
91
107
|
const filters = params['filter.query'].map(f =>
|
|
92
108
|
parseFilterExpression(f, params.staged ?? false)
|
|
93
109
|
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
|
|
111
|
+
resources = resources.filter(resource =>
|
|
112
|
+
filters.every(f => f(resource, markMatchingVariant))
|
|
113
|
+
)
|
|
98
114
|
} catch (err) {
|
|
99
115
|
throw new CommercetoolsError<InvalidInputError>(
|
|
100
116
|
{
|
|
@@ -127,7 +143,7 @@ export class ProductProjectionSearch {
|
|
|
127
143
|
offset: offset,
|
|
128
144
|
limit: limit,
|
|
129
145
|
results: resources.map(this.transform),
|
|
130
|
-
facets:
|
|
146
|
+
facets: facets,
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
149
|
|
|
@@ -139,6 +155,9 @@ export class ProductProjectionSearch {
|
|
|
139
155
|
lastModifiedAt: product.lastModifiedAt,
|
|
140
156
|
version: product.version,
|
|
141
157
|
name: obj.name,
|
|
158
|
+
key: product.key,
|
|
159
|
+
description: obj.description,
|
|
160
|
+
metaDescription: obj.metaDescription,
|
|
142
161
|
slug: obj.slug,
|
|
143
162
|
categories: obj.categories,
|
|
144
163
|
masterVariant: obj.masterVariant,
|
|
@@ -146,4 +165,185 @@ export class ProductProjectionSearch {
|
|
|
146
165
|
productType: product.productType,
|
|
147
166
|
}
|
|
148
167
|
}
|
|
168
|
+
|
|
169
|
+
getFacets(
|
|
170
|
+
params: ProductProjectionSearchParams,
|
|
171
|
+
products: Product[]
|
|
172
|
+
): FacetResults {
|
|
173
|
+
if (!params.facet) return {}
|
|
174
|
+
const staged = false
|
|
175
|
+
const result: FacetResults = {}
|
|
176
|
+
|
|
177
|
+
for (const facet of params.facet) {
|
|
178
|
+
const expression = generateFacetFunc(facet)
|
|
179
|
+
|
|
180
|
+
// Term Facet
|
|
181
|
+
if (expression.type === 'TermExpression') {
|
|
182
|
+
result[facet] = this.termFacet(expression.source, products, staged)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Range Facet
|
|
186
|
+
if (expression.type === 'RangeExpression') {
|
|
187
|
+
result[expression.source] = this.rangeFacet(
|
|
188
|
+
expression.source,
|
|
189
|
+
expression.children,
|
|
190
|
+
products,
|
|
191
|
+
staged
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// FilteredFacet
|
|
196
|
+
if (expression.type === 'FilterExpression') {
|
|
197
|
+
result[expression.source] = this.filterFacet(
|
|
198
|
+
expression.source,
|
|
199
|
+
expression.children,
|
|
200
|
+
products,
|
|
201
|
+
staged
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* TODO: This implemention needs the following additional features:
|
|
211
|
+
* - counting products
|
|
212
|
+
* - correct dataType
|
|
213
|
+
*/
|
|
214
|
+
termFacet(
|
|
215
|
+
facet: string,
|
|
216
|
+
products: Product[],
|
|
217
|
+
staged: boolean
|
|
218
|
+
): TermFacetResult {
|
|
219
|
+
const result: Writable<TermFacetResult> = {
|
|
220
|
+
type: 'terms',
|
|
221
|
+
dataType: 'text',
|
|
222
|
+
missing: 0,
|
|
223
|
+
total: 0,
|
|
224
|
+
other: 0,
|
|
225
|
+
terms: [],
|
|
226
|
+
}
|
|
227
|
+
const terms: Record<any, number> = {}
|
|
228
|
+
|
|
229
|
+
if (facet.startsWith('variants.')) {
|
|
230
|
+
products.forEach(p => {
|
|
231
|
+
const variants = getVariants(p, staged)
|
|
232
|
+
variants.forEach(v => {
|
|
233
|
+
result.total++
|
|
234
|
+
|
|
235
|
+
let value = resolveVariantValue(v, facet)
|
|
236
|
+
if (value === undefined) {
|
|
237
|
+
result.missing++
|
|
238
|
+
} else {
|
|
239
|
+
if (typeof value === 'number') {
|
|
240
|
+
value = Number(value).toFixed(1)
|
|
241
|
+
}
|
|
242
|
+
terms[value] = value in terms ? terms[value] + 1 : 1
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
} else {
|
|
247
|
+
products.forEach(p => {
|
|
248
|
+
const value = nestedLookup(p, facet)
|
|
249
|
+
result.total++
|
|
250
|
+
if (value === undefined) {
|
|
251
|
+
result.missing++
|
|
252
|
+
} else {
|
|
253
|
+
terms[value] = value in terms ? terms[value] + 1 : 1
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
for (const term in terms) {
|
|
258
|
+
result.terms.push({
|
|
259
|
+
term: term as any,
|
|
260
|
+
count: terms[term],
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
return result
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
filterFacet(
|
|
267
|
+
source: string,
|
|
268
|
+
filters: FilterExpression[] | undefined,
|
|
269
|
+
products: Product[],
|
|
270
|
+
staged: boolean
|
|
271
|
+
): FilteredFacetResult {
|
|
272
|
+
let count = 0
|
|
273
|
+
if (source.startsWith('variants.')) {
|
|
274
|
+
for (const p of products) {
|
|
275
|
+
for (const v of getVariants(p, staged)) {
|
|
276
|
+
const val = resolveVariantValue(v, source)
|
|
277
|
+
if (filters?.some(f => f.match(val))) {
|
|
278
|
+
count++
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
throw new Error('not supported')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
type: 'filter',
|
|
288
|
+
count: count,
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
rangeFacet(
|
|
293
|
+
source: string,
|
|
294
|
+
ranges: RangeExpression[] | undefined,
|
|
295
|
+
products: Product[],
|
|
296
|
+
staged: boolean
|
|
297
|
+
): RangeFacetResult {
|
|
298
|
+
const counts =
|
|
299
|
+
ranges?.map(range => {
|
|
300
|
+
if (source.startsWith('variants.')) {
|
|
301
|
+
const values = []
|
|
302
|
+
for (const p of products) {
|
|
303
|
+
for (const v of getVariants(p, staged)) {
|
|
304
|
+
const val = resolveVariantValue(v, source)
|
|
305
|
+
if (val === undefined) {
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (range.match(val)) {
|
|
310
|
+
values.push(val)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const numValues = values.length
|
|
316
|
+
return {
|
|
317
|
+
type: 'double',
|
|
318
|
+
from: range.start || 0,
|
|
319
|
+
fromStr: range.start !== null ? Number(range.start).toFixed(1) : '',
|
|
320
|
+
to: range.stop || 0,
|
|
321
|
+
toStr: range.stop !== null ? Number(range.stop).toFixed(1) : '',
|
|
322
|
+
count: numValues,
|
|
323
|
+
// totalCount: 0,
|
|
324
|
+
total: values.reduce((a, b) => a + b, 0),
|
|
325
|
+
min: numValues > 0 ? Math.min(...values) : 0,
|
|
326
|
+
max: numValues > 0 ? Math.max(...values) : 0,
|
|
327
|
+
mean: numValues > 0 ? mean(values) : 0,
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
throw new Error('not supported')
|
|
331
|
+
}
|
|
332
|
+
}) || []
|
|
333
|
+
const data: RangeFacetResult = {
|
|
334
|
+
type: 'range',
|
|
335
|
+
// @ts-ignore
|
|
336
|
+
dataType: 'number',
|
|
337
|
+
ranges: counts,
|
|
338
|
+
}
|
|
339
|
+
return data
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const mean = (arr: number[]) => {
|
|
344
|
+
let total = 0
|
|
345
|
+
for (let i = 0; i < arr.length; i++) {
|
|
346
|
+
total += arr[i]
|
|
347
|
+
}
|
|
348
|
+
return total / arr.length
|
|
149
349
|
}
|
|
@@ -65,8 +65,18 @@ export abstract class AbstractRepository {
|
|
|
65
65
|
if (!deepEqual(modifiedResource, resource)) {
|
|
66
66
|
this.save(context, modifiedResource)
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
|
|
69
|
+
const result = this.postProcessResource(modifiedResource)
|
|
70
|
+
if (!result) {
|
|
71
|
+
throw new Error("invalid post process action")
|
|
72
|
+
}
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
postProcessResource(resource: BaseResource | null): BaseResource | null {
|
|
77
|
+
return resource
|
|
69
78
|
}
|
|
79
|
+
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
export abstract class AbstractResourceRepository extends AbstractRepository {
|
|
@@ -79,12 +89,17 @@ export abstract class AbstractResourceRepository extends AbstractRepository {
|
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
query(context: RepositoryContext, params: QueryParams = {}) {
|
|
82
|
-
|
|
92
|
+
const result = this._storage.query(context.projectKey, this.getTypeId(), {
|
|
83
93
|
expand: params.expand,
|
|
84
94
|
where: params.where,
|
|
85
95
|
offset: params.offset,
|
|
86
96
|
limit: params.limit,
|
|
87
97
|
})
|
|
98
|
+
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
result.results = result.results.map(this.postProcessResource)
|
|
101
|
+
|
|
102
|
+
return result
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
get(
|
|
@@ -92,7 +107,8 @@ export abstract class AbstractResourceRepository extends AbstractRepository {
|
|
|
92
107
|
id: string,
|
|
93
108
|
params: GetParams = {}
|
|
94
109
|
): BaseResource | null {
|
|
95
|
-
|
|
110
|
+
const resource = this._storage.get(context.projectKey, this.getTypeId(), id, params)
|
|
111
|
+
return this.postProcessResource(resource)
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
getByKey(
|
|
@@ -100,12 +116,13 @@ export abstract class AbstractResourceRepository extends AbstractRepository {
|
|
|
100
116
|
key: string,
|
|
101
117
|
params: GetParams = {}
|
|
102
118
|
): BaseResource | null {
|
|
103
|
-
|
|
119
|
+
const resource = this._storage.getByKey(
|
|
104
120
|
context.projectKey,
|
|
105
121
|
this.getTypeId(),
|
|
106
122
|
key,
|
|
107
123
|
params
|
|
108
124
|
)
|
|
125
|
+
return this.postProcessResource(resource)
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
delete(
|
|
@@ -113,12 +130,13 @@ export abstract class AbstractResourceRepository extends AbstractRepository {
|
|
|
113
130
|
id: string,
|
|
114
131
|
params: GetParams = {}
|
|
115
132
|
): BaseResource | null {
|
|
116
|
-
|
|
133
|
+
const resource = this._storage.delete(
|
|
117
134
|
context.projectKey,
|
|
118
135
|
this.getTypeId(),
|
|
119
136
|
id,
|
|
120
137
|
params
|
|
121
138
|
)
|
|
139
|
+
return this.postProcessResource(resource)
|
|
122
140
|
}
|
|
123
141
|
|
|
124
142
|
save(context: RepositoryContext, resource: BaseResource) {
|
|
@@ -10,13 +10,30 @@ import {
|
|
|
10
10
|
} from '@commercetools/platform-sdk'
|
|
11
11
|
import { Writable } from '../types'
|
|
12
12
|
import { getBaseResourceProperties } from '../helpers'
|
|
13
|
-
import { AbstractResourceRepository, RepositoryContext } from './abstract'
|
|
13
|
+
import { AbstractResourceRepository, GetParams, RepositoryContext } from './abstract'
|
|
14
|
+
import { maskSecretValue } from '../lib/masking'
|
|
14
15
|
|
|
15
16
|
export class ExtensionRepository extends AbstractResourceRepository {
|
|
16
17
|
getTypeId(): ReferenceTypeId {
|
|
17
18
|
return 'extension'
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
postProcessResource(resource: Extension) {
|
|
22
|
+
if (resource) {
|
|
23
|
+
if (resource.destination.type === "HTTP" &&
|
|
24
|
+
resource.destination.authentication?.type === "AuthorizationHeader"
|
|
25
|
+
) {
|
|
26
|
+
return maskSecretValue(
|
|
27
|
+
resource, 'destination.authentication.headerValue')
|
|
28
|
+
}
|
|
29
|
+
else if (resource.destination.type == "AWSLambda") {
|
|
30
|
+
return maskSecretValue(
|
|
31
|
+
resource, 'destination.accessSecret')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return resource
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
create(context: RepositoryContext, draft: ExtensionDraft): Extension {
|
|
21
38
|
const resource: Extension = {
|
|
22
39
|
...getBaseResourceProperties(),
|
|
@@ -75,10 +75,44 @@ export const createPrice = (draft: PriceDraft): Price => {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export const createTypedMoney = (value: Money): TypedMoney => {
|
|
78
|
+
|
|
79
|
+
// Taken from https://docs.adyen.com/development-resources/currency-codes
|
|
80
|
+
let fractionDigits = 2
|
|
81
|
+
switch (value.currencyCode.toUpperCase()) {
|
|
82
|
+
case 'BHD':
|
|
83
|
+
case 'IQD':
|
|
84
|
+
case 'JOD':
|
|
85
|
+
case 'KWD':
|
|
86
|
+
case 'LYD':
|
|
87
|
+
case 'OMR':
|
|
88
|
+
case 'TND':
|
|
89
|
+
fractionDigits = 3
|
|
90
|
+
break
|
|
91
|
+
case 'CVE':
|
|
92
|
+
case 'DJF':
|
|
93
|
+
case 'GNF':
|
|
94
|
+
case 'IDR':
|
|
95
|
+
case 'JPY':
|
|
96
|
+
case 'KMF':
|
|
97
|
+
case 'KRW':
|
|
98
|
+
case 'PYG':
|
|
99
|
+
case 'RWF':
|
|
100
|
+
case 'UGX':
|
|
101
|
+
case 'VND':
|
|
102
|
+
case 'VUV':
|
|
103
|
+
case 'XAF':
|
|
104
|
+
case 'XOF':
|
|
105
|
+
case 'XPF':
|
|
106
|
+
fractionDigits = 0
|
|
107
|
+
break
|
|
108
|
+
default:
|
|
109
|
+
fractionDigits = 2
|
|
110
|
+
}
|
|
111
|
+
|
|
78
112
|
return {
|
|
79
113
|
type: 'centPrecision',
|
|
80
|
-
fractionDigits: 2,
|
|
81
114
|
...value,
|
|
115
|
+
fractionDigits: fractionDigits,
|
|
82
116
|
}
|
|
83
117
|
}
|
|
84
118
|
|
|
@@ -135,10 +169,10 @@ export const getReferenceFromResourceIdentifier = <T extends Reference>(
|
|
|
135
169
|
)
|
|
136
170
|
}
|
|
137
171
|
|
|
138
|
-
return
|
|
172
|
+
return {
|
|
139
173
|
typeId: resourceIdentifier.typeId,
|
|
140
174
|
id: resource?.id,
|
|
141
|
-
} as unknown
|
|
175
|
+
} as unknown as T
|
|
142
176
|
}
|
|
143
177
|
|
|
144
178
|
export const getRepositoryContext = (request: Request): RepositoryContext => {
|
|
@@ -39,6 +39,7 @@ export class ProductProjectionRepository extends AbstractResourceRepository {
|
|
|
39
39
|
const results = this._searchService.search(context.projectKey, {
|
|
40
40
|
filter: QueryParamsAsArray(query.filter),
|
|
41
41
|
'filter.query': QueryParamsAsArray(query['filter.query']),
|
|
42
|
+
facet: QueryParamsAsArray(query.facet),
|
|
42
43
|
offset: query.offset ? Number(query.offset) : undefined,
|
|
43
44
|
limit: query.limit ? Number(query.limit) : undefined,
|
|
44
45
|
expand: QueryParamsAsArray(query.expand),
|
|
@@ -23,11 +23,16 @@ import { maskSecretValue } from '../lib/masking'
|
|
|
23
23
|
export class ProjectRepository extends AbstractRepository {
|
|
24
24
|
get(context: RepositoryContext): Project | null {
|
|
25
25
|
const resource = this._storage.getProject(context.projectKey)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
return this.postProcessResource(resource)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
postProcessResource(resource: any): any {
|
|
30
|
+
if (resource) {
|
|
31
|
+
return maskSecretValue(
|
|
32
|
+
resource, 'externalOAuth.authorizationHeader')
|
|
33
|
+
}
|
|
34
|
+
return resource
|
|
35
|
+
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
save(context: RepositoryContext, resource: Project) {
|
|
@@ -2,12 +2,14 @@ import { getBaseResourceProperties } from '../helpers'
|
|
|
2
2
|
import { getReferenceFromResourceIdentifier } from './helpers'
|
|
3
3
|
import {
|
|
4
4
|
ReferenceTypeId,
|
|
5
|
+
StateReference,
|
|
5
6
|
State,
|
|
6
7
|
StateChangeKeyAction,
|
|
7
8
|
StateDraft,
|
|
8
9
|
StateSetDescriptionAction,
|
|
9
10
|
StateSetNameAction,
|
|
10
11
|
StateSetRolesAction,
|
|
12
|
+
StateSetTransitionsAction,
|
|
11
13
|
StateUpdateAction,
|
|
12
14
|
} from '@commercetools/platform-sdk'
|
|
13
15
|
import { AbstractResourceRepository, RepositoryContext } from './abstract'
|
|
@@ -71,5 +73,17 @@ export class StateRepository extends AbstractResourceRepository {
|
|
|
71
73
|
) => {
|
|
72
74
|
resource.roles = roles
|
|
73
75
|
},
|
|
76
|
+
setTransitions: (
|
|
77
|
+
context: RepositoryContext,
|
|
78
|
+
resource: Writable<State>,
|
|
79
|
+
{ transitions }: StateSetTransitionsAction
|
|
80
|
+
) => {
|
|
81
|
+
resource.transitions = transitions?.map((resourceId): StateReference => {
|
|
82
|
+
return {
|
|
83
|
+
id: resourceId.id || "",
|
|
84
|
+
typeId: "state",
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
74
88
|
}
|
|
75
89
|
}
|
|
@@ -50,10 +50,29 @@ beforeEach(async () => {
|
|
|
50
50
|
attributes: [
|
|
51
51
|
{
|
|
52
52
|
name: 'number',
|
|
53
|
-
value:
|
|
53
|
+
value: 4 as any,
|
|
54
54
|
},
|
|
55
55
|
],
|
|
56
56
|
},
|
|
57
|
+
variants: [
|
|
58
|
+
{
|
|
59
|
+
sku: 'my-other-sku',
|
|
60
|
+
prices: [
|
|
61
|
+
{
|
|
62
|
+
value: {
|
|
63
|
+
currencyCode: 'EUR',
|
|
64
|
+
centAmount: 91789,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
attributes: [
|
|
69
|
+
{
|
|
70
|
+
name: 'number',
|
|
71
|
+
value: 50 as any,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
57
76
|
name: {
|
|
58
77
|
'nl-NL': 'test product',
|
|
59
78
|
},
|
|
@@ -96,7 +115,26 @@ beforeEach(async () => {
|
|
|
96
115
|
images: [],
|
|
97
116
|
attributes: productDraft.masterVariant?.attributes,
|
|
98
117
|
},
|
|
99
|
-
variants: [
|
|
118
|
+
variants: [
|
|
119
|
+
{
|
|
120
|
+
id: 2,
|
|
121
|
+
sku: 'my-other-sku',
|
|
122
|
+
prices: [
|
|
123
|
+
{
|
|
124
|
+
id: product.masterData.current.variants[0].prices[0].id,
|
|
125
|
+
value: {
|
|
126
|
+
type: 'centPrecision',
|
|
127
|
+
currencyCode: 'EUR',
|
|
128
|
+
centAmount: 91789,
|
|
129
|
+
fractionDigits: 2,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
assets: [],
|
|
134
|
+
images: [],
|
|
135
|
+
attributes: productDraft.variants![0].attributes,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
100
138
|
name: productDraft.name,
|
|
101
139
|
slug: productDraft.slug,
|
|
102
140
|
categories: [],
|
|
@@ -210,7 +248,7 @@ describe('Product Projection Search - Filters', () => {
|
|
|
210
248
|
const response = await supertest(ctMock.app)
|
|
211
249
|
.get('/dummy/product-projections/search')
|
|
212
250
|
.query({
|
|
213
|
-
filter: ['variants.attributes.number:range(
|
|
251
|
+
filter: ['variants.attributes.number:range(5 TO 10)'],
|
|
214
252
|
})
|
|
215
253
|
|
|
216
254
|
const result: ProductProjectionPagedSearchResponse = response.body
|
|
@@ -220,3 +258,132 @@ describe('Product Projection Search - Filters', () => {
|
|
|
220
258
|
})
|
|
221
259
|
})
|
|
222
260
|
})
|
|
261
|
+
|
|
262
|
+
describe('Product Projection Search - Facets', () => {
|
|
263
|
+
test('termExpr - variants.attributes.number', async () => {
|
|
264
|
+
const response = await supertest(ctMock.app)
|
|
265
|
+
.get('/dummy/product-projections/search')
|
|
266
|
+
.query({
|
|
267
|
+
facet: ['variants.attributes.number'],
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const result: ProductProjectionPagedSearchResponse = response.body
|
|
271
|
+
expect(result).toMatchObject({
|
|
272
|
+
count: 1,
|
|
273
|
+
facets: {
|
|
274
|
+
'variants.attributes.number': {
|
|
275
|
+
type: 'terms',
|
|
276
|
+
dataType: 'text',
|
|
277
|
+
missing: 0,
|
|
278
|
+
total: 2,
|
|
279
|
+
terms: [
|
|
280
|
+
{
|
|
281
|
+
term: '4.0',
|
|
282
|
+
count: 1,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
term: '50.0',
|
|
286
|
+
count: 1,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
results: [
|
|
292
|
+
{
|
|
293
|
+
masterVariant: { sku: 'my-sku' },
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('filterExpr - variants.attributes.number', async () => {
|
|
300
|
+
const response = await supertest(ctMock.app)
|
|
301
|
+
.get('/dummy/product-projections/search')
|
|
302
|
+
.query({
|
|
303
|
+
facet: ['variants.attributes.number:3,4'],
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const result: ProductProjectionPagedSearchResponse = response.body
|
|
307
|
+
expect(result).toMatchObject({
|
|
308
|
+
count: 1,
|
|
309
|
+
facets: {
|
|
310
|
+
'variants.attributes.number': {
|
|
311
|
+
type: 'filter',
|
|
312
|
+
count: 1,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
results: [
|
|
316
|
+
{
|
|
317
|
+
masterVariant: { sku: 'my-sku' },
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test('rangeExpr - variants.attributes.number', async () => {
|
|
324
|
+
const response = await supertest(ctMock.app)
|
|
325
|
+
.get('/dummy/product-projections/search')
|
|
326
|
+
.query({
|
|
327
|
+
facet: [
|
|
328
|
+
'variants.attributes.number:range(* TO 5), (5 TO 25), (25 TO 100)',
|
|
329
|
+
],
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
const result: ProductProjectionPagedSearchResponse = response.body
|
|
333
|
+
expect(result).toMatchObject({
|
|
334
|
+
count: 1,
|
|
335
|
+
facets: {
|
|
336
|
+
'variants.attributes.number': {
|
|
337
|
+
type: 'range',
|
|
338
|
+
dataType: 'number',
|
|
339
|
+
ranges: [
|
|
340
|
+
{
|
|
341
|
+
type: 'double',
|
|
342
|
+
from: 0.0,
|
|
343
|
+
fromStr: '',
|
|
344
|
+
to: 5.0,
|
|
345
|
+
toStr: '5.0',
|
|
346
|
+
count: 1,
|
|
347
|
+
// totalCount: 1,
|
|
348
|
+
total: 4.0,
|
|
349
|
+
min: 4.0,
|
|
350
|
+
max: 4.0,
|
|
351
|
+
mean: 4.0,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
type: 'double',
|
|
355
|
+
from: 5.0,
|
|
356
|
+
fromStr: '5.0',
|
|
357
|
+
to: 25.0,
|
|
358
|
+
toStr: '25.0',
|
|
359
|
+
count: 0,
|
|
360
|
+
// totalCount: 0,
|
|
361
|
+
total: 0.0,
|
|
362
|
+
min: 0.0,
|
|
363
|
+
max: 0.0,
|
|
364
|
+
mean: 0.0,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
type: 'double',
|
|
368
|
+
from: 25.0,
|
|
369
|
+
fromStr: '25.0',
|
|
370
|
+
to: 100.0,
|
|
371
|
+
toStr: '100.0',
|
|
372
|
+
count: 1,
|
|
373
|
+
// totalCount: 1,
|
|
374
|
+
total: 50,
|
|
375
|
+
min: 50.0,
|
|
376
|
+
max: 50.0,
|
|
377
|
+
mean: 50.0,
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
results: [
|
|
383
|
+
{
|
|
384
|
+
masterVariant: { sku: 'my-sku' },
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
})
|