@reactionary/source 0.0.37 → 0.0.39
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/.env-template +10 -0
- package/.github/workflows/pull-request.yml +5 -3
- package/core/package.json +1 -2
- package/core/src/client/client.ts +4 -2
- package/core/src/index.ts +3 -9
- package/core/src/providers/analytics.provider.ts +6 -1
- package/core/src/providers/base.provider.ts +33 -3
- package/core/src/providers/cart.provider.ts +7 -1
- package/core/src/providers/category.provider.ts +91 -0
- package/core/src/providers/identity.provider.ts +5 -1
- package/core/src/providers/inventory.provider.ts +4 -0
- package/core/src/providers/price.provider.ts +54 -0
- package/core/src/providers/product.provider.ts +4 -0
- package/core/src/providers/search.provider.ts +6 -0
- package/core/src/schemas/capabilities.schema.ts +3 -2
- package/core/src/schemas/models/base.model.ts +42 -1
- package/core/src/schemas/models/cart.model.ts +27 -3
- package/core/src/schemas/models/category.model.ts +23 -0
- package/core/src/schemas/models/identifiers.model.ts +29 -1
- package/core/src/schemas/models/inventory.model.ts +6 -2
- package/core/src/schemas/models/price.model.ts +11 -3
- package/core/src/schemas/models/search.model.ts +4 -2
- package/core/src/schemas/queries/category.query.ts +32 -0
- package/core/src/schemas/queries/index.ts +9 -0
- package/core/src/schemas/queries/inventory.query.ts +18 -3
- package/core/src/schemas/session.schema.ts +13 -2
- package/examples/next/.swcrc +30 -0
- package/examples/next/eslint.config.mjs +21 -0
- package/examples/next/index.d.ts +6 -0
- package/examples/next/next-env.d.ts +5 -0
- package/examples/next/next.config.js +20 -0
- package/examples/next/project.json +9 -0
- package/examples/next/public/.gitkeep +0 -0
- package/examples/next/public/favicon.ico +0 -0
- package/examples/next/src/app/global.css +0 -0
- package/examples/next/src/app/layout.tsx +18 -0
- package/examples/next/src/app/page.module.scss +2 -0
- package/examples/next/src/app/page.tsx +51 -0
- package/examples/next/src/instrumentation.ts +9 -0
- package/examples/next/tsconfig.json +44 -0
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -1
- package/examples/node/src/basic/basic-node-setup.spec.ts +0 -1
- package/otel/README.md +152 -172
- package/otel/package.json +0 -1
- package/otel/src/index.ts +15 -5
- package/otel/src/metrics.ts +3 -3
- package/otel/src/test/otel.spec.ts +8 -0
- package/otel/src/trace-decorator.ts +87 -108
- package/otel/src/tracer.ts +3 -3
- package/package.json +2 -1
- package/providers/commercetools/package.json +1 -0
- package/providers/commercetools/src/core/initialize.ts +7 -3
- package/providers/commercetools/src/providers/cart.provider.ts +84 -8
- package/providers/commercetools/src/providers/category.provider.ts +244 -0
- package/providers/commercetools/src/providers/index.ts +7 -0
- package/providers/commercetools/src/providers/inventory.provider.ts +31 -14
- package/providers/commercetools/src/providers/price.provider.ts +74 -18
- package/providers/commercetools/src/providers/product.provider.ts +19 -15
- package/providers/commercetools/src/providers/search.provider.ts +9 -7
- package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
- package/providers/commercetools/src/schema/configuration.schema.ts +1 -1
- package/providers/commercetools/src/test/cart.provider.spec.ts +119 -0
- package/providers/commercetools/src/test/category.provider.spec.ts +180 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +80 -0
- package/providers/commercetools/src/test/product.provider.spec.ts +29 -14
- package/providers/commercetools/src/test/search.provider.spec.ts +51 -9
- package/providers/commercetools/src/test/test-utils.ts +35 -0
- package/providers/commercetools/tsconfig.lib.json +1 -1
- package/providers/fake/jest.config.ts +10 -0
- package/providers/fake/src/core/initialize.ts +15 -1
- package/providers/fake/src/index.ts +2 -9
- package/providers/fake/src/providers/cart.provider.ts +74 -15
- package/providers/fake/src/providers/category.provider.ts +152 -0
- package/providers/fake/src/providers/index.ts +8 -0
- package/providers/fake/src/providers/inventory.provider.ts +23 -9
- package/providers/fake/src/providers/price.provider.ts +46 -6
- package/providers/fake/src/providers/search.provider.ts +13 -4
- package/providers/fake/src/schema/capabilities.schema.ts +4 -2
- package/providers/fake/src/schema/configuration.schema.ts +5 -0
- package/providers/fake/src/test/cart.provider.spec.ts +126 -0
- package/providers/fake/src/test/category.provider.spec.ts +134 -0
- package/providers/fake/src/test/price.provider.spec.ts +80 -0
- package/providers/fake/src/test/test-utils.ts +42 -0
- package/providers/fake/tsconfig.json +4 -0
- package/providers/fake/tsconfig.lib.json +3 -1
- package/providers/fake/tsconfig.spec.json +16 -0
- package/trpc/package.json +1 -2
- package/trpc/src/client.ts +1 -3
- package/trpc/src/integration.spec.ts +16 -10
- package/trpc/src/transparent-client.spec.ts +23 -17
- package/tsconfig.base.json +2 -0
- package/core/src/decorators/trpc.decorators.ts +0 -144
- package/otel/src/sdk.ts +0 -57
|
@@ -10,16 +10,22 @@ import {
|
|
|
10
10
|
} from '@reactionary/core';
|
|
11
11
|
import z from 'zod';
|
|
12
12
|
import { FakeConfiguration } from '../schema/configuration.schema';
|
|
13
|
+
import { Faker, en, base } from '@faker-js/faker';
|
|
13
14
|
|
|
14
15
|
export class FakeCartProvider<
|
|
15
16
|
T extends Cart = Cart
|
|
16
17
|
> extends CartProvider<T> {
|
|
17
18
|
protected config: FakeConfiguration;
|
|
18
19
|
private carts: Map<string, T> = new Map();
|
|
20
|
+
private generator: Faker;
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
constructor(config: FakeConfiguration, schema: z.ZodType<T>, cache: Cache) {
|
|
21
24
|
super(schema, cache);
|
|
22
|
-
|
|
25
|
+
this.generator = new Faker({
|
|
26
|
+
locale: [en, base],
|
|
27
|
+
seed: config.seeds.product
|
|
28
|
+
});
|
|
23
29
|
this.config = config;
|
|
24
30
|
}
|
|
25
31
|
|
|
@@ -28,7 +34,16 @@ export class FakeCartProvider<
|
|
|
28
34
|
_session: Session
|
|
29
35
|
): Promise<T> {
|
|
30
36
|
const cartId = payload.cart.key;
|
|
31
|
-
|
|
37
|
+
|
|
38
|
+
if (payload.cart.key === '') {
|
|
39
|
+
const result = this.newModel();
|
|
40
|
+
result.meta = {
|
|
41
|
+
cache: { hit: false, key: 'empty' },
|
|
42
|
+
placeholder: true
|
|
43
|
+
};
|
|
44
|
+
return this.assert(result);
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
if (!this.carts.has(cartId)) {
|
|
33
48
|
const model = this.newModel();
|
|
34
49
|
Object.assign(model, {
|
|
@@ -44,7 +59,7 @@ export class FakeCartProvider<
|
|
|
44
59
|
});
|
|
45
60
|
this.carts.set(cartId, this.assert(model));
|
|
46
61
|
}
|
|
47
|
-
|
|
62
|
+
|
|
48
63
|
const cart = this.carts.get(cartId);
|
|
49
64
|
if (!cart) {
|
|
50
65
|
throw new Error(`Cart with id ${cartId} not found`);
|
|
@@ -56,22 +71,46 @@ export class FakeCartProvider<
|
|
|
56
71
|
payload: CartMutationItemAdd,
|
|
57
72
|
session: Session
|
|
58
73
|
): Promise<T> {
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
|
|
75
|
+
const cartId = payload.cart.key || `cart-${this.generator.string.uuid()}`;
|
|
76
|
+
const cart = await this.getById({ cart: { key: cartId } }, session);
|
|
77
|
+
|
|
61
78
|
const existingItemIndex = cart.items.findIndex(
|
|
62
79
|
item => item.product.key === payload.product.key
|
|
63
80
|
);
|
|
64
|
-
|
|
81
|
+
|
|
65
82
|
if (existingItemIndex >= 0) {
|
|
66
83
|
cart.items[existingItemIndex].quantity += payload.quantity;
|
|
67
84
|
} else {
|
|
85
|
+
const price = this.generator.number.int({ min: 100, max: 100000 }) / 100;
|
|
86
|
+
|
|
68
87
|
cart.items.push({
|
|
69
88
|
identifier: { key: `item-${Date.now()}` },
|
|
70
89
|
product: payload.product,
|
|
71
90
|
quantity: payload.quantity,
|
|
91
|
+
price: {
|
|
92
|
+
unitPrice: {
|
|
93
|
+
value: price,
|
|
94
|
+
currency: session.languageContext.currencyCode,
|
|
95
|
+
},
|
|
96
|
+
totalPrice: {
|
|
97
|
+
value: 0, // Will be calculated below
|
|
98
|
+
currency: session.languageContext.currencyCode,
|
|
99
|
+
},
|
|
100
|
+
totalDiscount: {
|
|
101
|
+
value: 0,
|
|
102
|
+
currency: session.languageContext.currencyCode,
|
|
103
|
+
},
|
|
104
|
+
unitDiscount: {
|
|
105
|
+
value: 0,
|
|
106
|
+
currency: session.languageContext.currencyCode,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
72
109
|
});
|
|
73
110
|
}
|
|
74
|
-
|
|
111
|
+
|
|
112
|
+
this.recalculateCart(cart);
|
|
113
|
+
|
|
75
114
|
return this.assert(cart);
|
|
76
115
|
}
|
|
77
116
|
|
|
@@ -79,12 +118,13 @@ export class FakeCartProvider<
|
|
|
79
118
|
payload: CartMutationItemRemove,
|
|
80
119
|
session: Session
|
|
81
120
|
): Promise<T> {
|
|
82
|
-
const
|
|
83
|
-
|
|
121
|
+
const cartId = payload.cart.key || `cart-${this.generator.string.uuid()}`;
|
|
122
|
+
const cart = await this.getById({ cart: { key: cartId } }, session);
|
|
123
|
+
|
|
84
124
|
cart.items = cart.items.filter(
|
|
85
125
|
item => item.identifier.key !== payload.item.key
|
|
86
126
|
);
|
|
87
|
-
|
|
127
|
+
this.recalculateCart(cart);
|
|
88
128
|
return this.assert(cart);
|
|
89
129
|
}
|
|
90
130
|
|
|
@@ -92,16 +132,35 @@ export class FakeCartProvider<
|
|
|
92
132
|
payload: CartMutationItemQuantityChange,
|
|
93
133
|
session: Session
|
|
94
134
|
): Promise<T> {
|
|
95
|
-
const
|
|
96
|
-
|
|
135
|
+
const cartId = payload.cart.key || `cart-${this.generator.string.uuid()}`;
|
|
136
|
+
const cart = await this.getById({ cart: { key: cartId } }, session);
|
|
137
|
+
|
|
97
138
|
const item = cart.items.find(
|
|
98
139
|
item => item.identifier.key === payload.item.key
|
|
99
140
|
);
|
|
100
|
-
|
|
141
|
+
|
|
101
142
|
if (item) {
|
|
102
143
|
item.quantity = payload.quantity;
|
|
103
144
|
}
|
|
104
|
-
|
|
145
|
+
this.recalculateCart(cart);
|
|
105
146
|
return this.assert(cart);
|
|
106
147
|
}
|
|
107
|
-
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
protected recalculateCart(cart: T) {
|
|
152
|
+
cart.items.forEach(item => {
|
|
153
|
+
item.price.totalPrice.value = item.price.unitPrice.value * item.quantity;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
cart.price.totalProductPrice = {
|
|
157
|
+
value: cart.items.reduce((sum, item) => sum + item.price.totalPrice.value, 0),
|
|
158
|
+
currency: cart.items[0]?.price.unitPrice.currency || 'USD',
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
cart.price.grandTotal = {
|
|
162
|
+
value: cart.items.reduce((sum, item) => sum + item.price.totalPrice.value, 0),
|
|
163
|
+
currency: cart.items[0]?.price.unitPrice.currency || 'USD',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Category, CategoryProvider, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories, Session } from "@reactionary/core";
|
|
2
|
+
import { FakeConfiguration } from "../schema/configuration.schema";
|
|
3
|
+
import { Cache as ReactionaryCache } from "@reactionary/core";
|
|
4
|
+
import z from "zod";
|
|
5
|
+
import { Faker, en, base } from '@faker-js/faker';
|
|
6
|
+
export class FakeCategoryProvider<
|
|
7
|
+
T extends Category = Category
|
|
8
|
+
> extends CategoryProvider<T> {
|
|
9
|
+
|
|
10
|
+
protected config: FakeConfiguration;
|
|
11
|
+
|
|
12
|
+
protected topCategories = new Array<T>();
|
|
13
|
+
protected childCategories = new Map<string, Array<T>>();
|
|
14
|
+
protected allCategories = new Map<string, T>();
|
|
15
|
+
|
|
16
|
+
protected categoryGenerator: Faker;
|
|
17
|
+
|
|
18
|
+
protected generateFakeCategory(parent: Category | undefined, index: number): T {
|
|
19
|
+
|
|
20
|
+
let name: string;
|
|
21
|
+
if (!parent) {
|
|
22
|
+
name = this.categoryGenerator.commerce.department();
|
|
23
|
+
} else {
|
|
24
|
+
name = `${parent.name}-${index}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const category: T = this.newModel();
|
|
28
|
+
category.identifier = { key: name.toLowerCase().replace(/\s+/g, '-') };
|
|
29
|
+
category.name = name;
|
|
30
|
+
category.text = this.categoryGenerator.lorem.sentences(3);
|
|
31
|
+
category.slug = category.identifier.key + '-slug';
|
|
32
|
+
if (parent) {
|
|
33
|
+
category.parentCategory = parent.identifier;
|
|
34
|
+
}
|
|
35
|
+
this.allCategories.set(category.identifier.key, category);
|
|
36
|
+
return category;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
constructor(config: FakeConfiguration, schema: z.ZodType<T>, cache: ReactionaryCache) {
|
|
41
|
+
super(schema, cache);
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.categoryGenerator = new Faker({
|
|
44
|
+
seed: this.config.seeds.category,
|
|
45
|
+
locale: [en, base],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Generate some top-level categories
|
|
49
|
+
for (let i = 0; i < 6; i++) {
|
|
50
|
+
const category: T = this.generateFakeCategory(undefined, i);
|
|
51
|
+
this.topCategories.push(category);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Generate two levels of child categories
|
|
55
|
+
this.topCategories.forEach((parentCategory) => {
|
|
56
|
+
const children = new Array<T>();
|
|
57
|
+
for (let j = 0; j < 5; j++) {
|
|
58
|
+
const childCategory: T = this.generateFakeCategory(parentCategory, j);
|
|
59
|
+
children.push(childCategory);
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const subCategoryChildren = new Array<T>();
|
|
63
|
+
for(let k = 0; k < 5; k++) {
|
|
64
|
+
const subChildCategory: T = this.generateFakeCategory(childCategory, k);
|
|
65
|
+
subCategoryChildren.push(subChildCategory);
|
|
66
|
+
}
|
|
67
|
+
this.childCategories.set(childCategory.identifier.key, subCategoryChildren);
|
|
68
|
+
}
|
|
69
|
+
this.childCategories.set(parentCategory.identifier.key, children);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
public override async getById(payload: CategoryQueryById, session: Session): Promise<T> {
|
|
76
|
+
const category = this.allCategories.get(payload.id.key);
|
|
77
|
+
if(!category) {
|
|
78
|
+
const dummyCategory = this.newModel();
|
|
79
|
+
dummyCategory.meta.placeholder = true;
|
|
80
|
+
dummyCategory.identifier = { key: payload.id.key };
|
|
81
|
+
return dummyCategory;
|
|
82
|
+
}
|
|
83
|
+
return category;
|
|
84
|
+
}
|
|
85
|
+
public override getBySlug(payload: CategoryQueryBySlug, session: Session): Promise<T | null> {
|
|
86
|
+
for(const p of this.allCategories.values()) {
|
|
87
|
+
if(p.slug === payload.slug) {
|
|
88
|
+
return Promise.resolve(p as T);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve(null);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public override getBreadcrumbPathToCategory(payload: CategoryQueryForBreadcrumb, session: Session): Promise<T[]> {
|
|
95
|
+
const path = new Array<T>();
|
|
96
|
+
let category = this.allCategories.get(payload.id.key);
|
|
97
|
+
path.push(category as T);
|
|
98
|
+
while(category?.parentCategory) {
|
|
99
|
+
category = this.allCategories.get(category.parentCategory.key);
|
|
100
|
+
if(category) {
|
|
101
|
+
path.unshift(category as T);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return Promise.resolve(path);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public override async findChildCategories(payload: CategoryQueryForChildCategories, session: Session): Promise<ReturnType<typeof this.parsePaginatedResult>> {
|
|
108
|
+
const children = this.childCategories.get(payload.parentId.key);
|
|
109
|
+
const page = children?.slice((payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize, payload.paginationOptions.pageNumber * payload.paginationOptions.pageSize);
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const res = {
|
|
113
|
+
meta: {
|
|
114
|
+
placeholder: false,
|
|
115
|
+
cache: {
|
|
116
|
+
hit: false,
|
|
117
|
+
key: 'child-categories-' + payload.parentId.key + '-' + payload.paginationOptions.pageNumber + '-' + payload.paginationOptions.pageSize,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
items: page ? page as T[] : [],
|
|
121
|
+
totalCount: children ? children.length : 0,
|
|
122
|
+
pageNumber: payload.paginationOptions.pageNumber,
|
|
123
|
+
pageSize: payload.paginationOptions.pageSize,
|
|
124
|
+
totalPages: children ? Math.ceil(children.length / payload.paginationOptions.pageSize) : 1,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return Promise.resolve(res);
|
|
128
|
+
}
|
|
129
|
+
public override findTopCategories(payload: CategoryQueryForTopCategories, session: Session): Promise<ReturnType<typeof this.parsePaginatedResult>> {
|
|
130
|
+
const children = this.topCategories;
|
|
131
|
+
const page = children?.slice((payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize, payload.paginationOptions.pageNumber * payload.paginationOptions.pageSize);
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
const res = {
|
|
135
|
+
meta: {
|
|
136
|
+
placeholder: false,
|
|
137
|
+
cache: {
|
|
138
|
+
hit: false,
|
|
139
|
+
key: 'top' + '-' + payload.paginationOptions.pageNumber + '-' + payload.paginationOptions.pageSize,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
items: page ? page as T[] : [],
|
|
143
|
+
totalCount: children ? children.length : 0,
|
|
144
|
+
pageNumber: payload.paginationOptions.pageNumber,
|
|
145
|
+
pageSize: payload.paginationOptions.pageSize,
|
|
146
|
+
totalPages: children ? Math.ceil(children.length / payload.paginationOptions.pageSize) : 1,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return Promise.resolve(res);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './analytics.provider';
|
|
2
|
+
export * from './cart.provider';
|
|
3
|
+
export * from './category.provider';
|
|
4
|
+
export * from './identity.provider';
|
|
5
|
+
export * from './inventory.provider';
|
|
6
|
+
export * from './price.provider';
|
|
7
|
+
export * from './product.provider';
|
|
8
|
+
export * from './search.provider';
|
|
@@ -26,29 +26,43 @@ export class FakeInventoryProvider<
|
|
|
26
26
|
): Promise<T> {
|
|
27
27
|
// Generate a simple hash from the SKU string for seeding
|
|
28
28
|
let hash = 0;
|
|
29
|
-
const skuString = payload.sku;
|
|
29
|
+
const skuString = payload.sku.key;
|
|
30
30
|
for (let i = 0; i < skuString.length; i++) {
|
|
31
31
|
hash = ((hash << 5) - hash) + skuString.charCodeAt(i);
|
|
32
32
|
hash = hash & hash; // Convert to 32bit integer
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
const generator = new Faker({
|
|
36
36
|
seed: hash || 42,
|
|
37
37
|
locale: [en, base],
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
const model = this.newModel();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
|
|
42
|
+
model.identifier = {
|
|
43
|
+
sku: { key: skuString},
|
|
44
|
+
channelId: {
|
|
45
|
+
key: 'online'
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
model.sku = skuString;
|
|
49
|
+
|
|
50
|
+
model.quantity = generator.number.int({ min: 0, max: 100 });
|
|
51
|
+
if (model.quantity > 0 ) {
|
|
52
|
+
model.status = 'inStock';
|
|
53
|
+
} else {
|
|
54
|
+
model.status = 'outOfStock';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
model.meta = {
|
|
44
59
|
cache: {
|
|
45
60
|
hit: false,
|
|
46
|
-
key:
|
|
61
|
+
key: this.generateCacheKeySingle(model.identifier, _session)
|
|
47
62
|
},
|
|
48
63
|
placeholder: false,
|
|
49
|
-
}
|
|
50
|
-
});
|
|
64
|
+
};
|
|
51
65
|
|
|
52
66
|
return this.assert(model);
|
|
53
67
|
}
|
|
54
|
-
}
|
|
68
|
+
}
|
|
@@ -20,10 +20,22 @@ export class FakePriceProvider<
|
|
|
20
20
|
this.config = config;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
public override async getBySKUs(payload: PriceQueryBySku[], session: Session): Promise<T[]> {
|
|
24
|
+
|
|
25
|
+
const promises = payload.map(p => this.getBySKU(p, session));
|
|
26
|
+
const result = await Promise.all(promises);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
public override async getBySKU(
|
|
24
31
|
payload: PriceQueryBySku,
|
|
25
32
|
_session: Session
|
|
26
33
|
): Promise<T> {
|
|
34
|
+
|
|
35
|
+
if (payload.sku.key === 'unknown-sku') {
|
|
36
|
+
return this.getEmptyPriceResult(payload.sku.key, _session.languageContext.currencyCode);
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
// Generate a simple hash from the SKU key string for seeding
|
|
28
40
|
let hash = 0;
|
|
29
41
|
const skuString = payload.sku.key;
|
|
@@ -31,30 +43,58 @@ export class FakePriceProvider<
|
|
|
31
43
|
hash = ((hash << 5) - hash) + skuString.charCodeAt(i);
|
|
32
44
|
hash = hash & hash; // Convert to 32bit integer
|
|
33
45
|
}
|
|
34
|
-
|
|
46
|
+
|
|
35
47
|
const generator = new Faker({
|
|
36
48
|
seed: hash || 42,
|
|
37
49
|
locale: [en, base],
|
|
38
50
|
});
|
|
39
51
|
|
|
52
|
+
|
|
40
53
|
const model = this.newModel();
|
|
41
54
|
Object.assign(model, {
|
|
42
55
|
identifier: {
|
|
43
56
|
sku: payload.sku,
|
|
44
57
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
currency:
|
|
58
|
+
unitPrice: {
|
|
59
|
+
value: generator.number.int({ min: 300, max: 100000 }) / 100,
|
|
60
|
+
currency: _session.languageContext.currencyCode,
|
|
48
61
|
},
|
|
49
62
|
meta: {
|
|
50
63
|
cache: {
|
|
51
64
|
hit: false,
|
|
52
|
-
key: payload.sku,
|
|
65
|
+
key: payload.sku.key,
|
|
53
66
|
},
|
|
54
67
|
placeholder: false,
|
|
55
68
|
},
|
|
56
69
|
});
|
|
57
70
|
|
|
71
|
+
if (skuString.includes('with-tiers')) {
|
|
72
|
+
const unitPrice = model.unitPrice?.value || 0;
|
|
73
|
+
// Ensure tiered prices are less than the unit price
|
|
74
|
+
const tier1Price = unitPrice * 0.8;
|
|
75
|
+
const tier2Price = tier1Price * 0.8;
|
|
76
|
+
model.tieredPrices = [
|
|
77
|
+
{
|
|
78
|
+
minimumQuantity: generator.number.int({ min: 2, max: 5 }),
|
|
79
|
+
price: {
|
|
80
|
+
value: tier1Price,
|
|
81
|
+
currency: _session.languageContext.currencyCode,
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
minimumQuantity: generator.number.int({ min: 6, max: 10 }),
|
|
86
|
+
price: {
|
|
87
|
+
value: tier2Price,
|
|
88
|
+
currency: _session.languageContext.currencyCode,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
} else {
|
|
93
|
+
model.tieredPrices = [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
58
98
|
return this.assert(model);
|
|
59
99
|
}
|
|
60
|
-
}
|
|
100
|
+
}
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SearchProvider,
|
|
3
|
-
SearchQueryByTerm,
|
|
4
3
|
SearchResult,
|
|
5
4
|
SearchResultFacet,
|
|
6
5
|
SearchResultProduct,
|
|
7
|
-
|
|
8
|
-
Cache as ReactinaryCache,
|
|
6
|
+
Cache as ReactionaryCache,
|
|
9
7
|
} from '@reactionary/core';
|
|
8
|
+
import type { SearchQueryByTerm, Session } from '@reactionary/core';
|
|
10
9
|
import z from 'zod';
|
|
11
10
|
import { FakeConfiguration } from '../schema/configuration.schema';
|
|
12
11
|
import { Faker, en, base } from '@faker-js/faker';
|
|
13
12
|
import { jitter } from '../utilities/jitter';
|
|
13
|
+
import { traced } from '@reactionary/otel';
|
|
14
14
|
|
|
15
15
|
export class FakeSearchProvider<
|
|
16
16
|
T extends SearchResult = SearchResult
|
|
17
17
|
> extends SearchProvider<T> {
|
|
18
18
|
protected config: FakeConfiguration;
|
|
19
19
|
|
|
20
|
-
constructor(config: FakeConfiguration, schema: z.ZodType<T>, cache:
|
|
20
|
+
constructor(config: FakeConfiguration, schema: z.ZodType<T>, cache: ReactionaryCache) {
|
|
21
21
|
super(schema, cache);
|
|
22
22
|
|
|
23
23
|
this.config = config;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
@traced()
|
|
26
27
|
public override async queryByTerm(
|
|
27
28
|
payload: SearchQueryByTerm,
|
|
28
29
|
_session: Session
|
|
@@ -119,6 +120,14 @@ export class FakeSearchProvider<
|
|
|
119
120
|
},
|
|
120
121
|
} satisfies SearchResult;
|
|
121
122
|
|
|
123
|
+
const foo = this.childFunction();
|
|
124
|
+
|
|
122
125
|
return this.schema.parse(result);
|
|
123
126
|
}
|
|
127
|
+
|
|
128
|
+
@traced()
|
|
129
|
+
protected childFunction() {
|
|
130
|
+
const foo = 42;
|
|
131
|
+
return foo;
|
|
132
|
+
}
|
|
124
133
|
}
|
|
@@ -4,7 +4,9 @@ import { z } from 'zod';
|
|
|
4
4
|
export const FakeCapabilitiesSchema = CapabilitiesSchema.pick({
|
|
5
5
|
product: true,
|
|
6
6
|
search: true,
|
|
7
|
-
identity: true
|
|
7
|
+
identity: true,
|
|
8
|
+
category: true,
|
|
9
|
+
cart: true,
|
|
8
10
|
}).partial();
|
|
9
11
|
|
|
10
|
-
export type FakeCapabilities = z.infer<typeof FakeCapabilitiesSchema>;
|
|
12
|
+
export type FakeCapabilities = z.infer<typeof FakeCapabilitiesSchema>;
|
|
@@ -10,6 +10,11 @@ export const FakeConfigurationSchema = z.looseInterface({
|
|
|
10
10
|
mean: 0,
|
|
11
11
|
deviation: 0,
|
|
12
12
|
}),
|
|
13
|
+
seeds: z.looseInterface({
|
|
14
|
+
product: z.number().min(0).max(10000).default(1),
|
|
15
|
+
search: z.number().min(0).max(10000).default(1),
|
|
16
|
+
category: z.number().min(0).max(10000).default(1),
|
|
17
|
+
}),
|
|
13
18
|
});
|
|
14
19
|
|
|
15
20
|
export type FakeConfiguration = z.infer<typeof FakeConfigurationSchema>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { CartSchema, CategorySchema, IdentitySchema, NoOpCache, ProductSchema, Session } from '@reactionary/core';
|
|
3
|
+
import { createAnonymousTestSession, getFakerTestConfiguration } from './test-utils';
|
|
4
|
+
import { FakeCartProvider } from '../providers/cart.provider';
|
|
5
|
+
import { FakeIdentityProvider } from '../providers';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const testData = {
|
|
9
|
+
skuWithoutTiers: 'SGB-01',
|
|
10
|
+
skuWithTiers: 'GMCT-01-with-tiers'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('Fake Cart Provider', () => {
|
|
14
|
+
let provider: FakeCartProvider;
|
|
15
|
+
let identityProvider: FakeIdentityProvider;
|
|
16
|
+
let session: Session;
|
|
17
|
+
|
|
18
|
+
beforeAll( () => {
|
|
19
|
+
provider = new FakeCartProvider(getFakerTestConfiguration(), CartSchema, new NoOpCache());
|
|
20
|
+
identityProvider = new FakeIdentityProvider(getFakerTestConfiguration(), IdentitySchema, new NoOpCache());
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
beforeEach( () => {
|
|
24
|
+
session = createAnonymousTestSession()
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('anonymous sessions', () => {
|
|
28
|
+
it('should be able to get an empty cart', async () => {
|
|
29
|
+
const cart = await provider.getById({
|
|
30
|
+
cart: { key: '' },
|
|
31
|
+
}, session);
|
|
32
|
+
|
|
33
|
+
expect(cart.identifier.key).toBeFalsy();
|
|
34
|
+
expect(cart.items.length).toBe(0);
|
|
35
|
+
expect(cart.meta?.placeholder).toBe(true);
|
|
36
|
+
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should be able to add an item to a cart', async () => {
|
|
40
|
+
const cart = await provider.add({
|
|
41
|
+
cart: { key: '' },
|
|
42
|
+
product: {
|
|
43
|
+
key: testData.skuWithoutTiers,
|
|
44
|
+
},
|
|
45
|
+
quantity: 1
|
|
46
|
+
}, session);
|
|
47
|
+
|
|
48
|
+
expect(cart.identifier.key).toBeDefined();
|
|
49
|
+
expect(cart.items.length).toBe(1);
|
|
50
|
+
expect(cart.items[0].product.key).toBe(testData.skuWithoutTiers);
|
|
51
|
+
expect(cart.items[0].quantity).toBe(1);
|
|
52
|
+
|
|
53
|
+
expect(cart.items[0].price.totalPrice.value).toBeGreaterThan(0);
|
|
54
|
+
expect(cart.items[0].price.totalPrice.currency).toBe(session.languageContext.currencyCode);
|
|
55
|
+
|
|
56
|
+
expect(cart.price.grandTotal.value).toBeGreaterThan(0);
|
|
57
|
+
expect(cart.price.grandTotal.currency).toBe(session.languageContext.currencyCode);
|
|
58
|
+
|
|
59
|
+
expect(cart.price.grandTotal.value).toBe(cart.items[0].price.totalPrice.value);
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
expect(cart.meta?.placeholder).toBeFalsy();
|
|
63
|
+
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
it('should be able to change quantity of an item in a cart', async () => {
|
|
68
|
+
|
|
69
|
+
const cart = await provider.add({
|
|
70
|
+
cart: { key: '' },
|
|
71
|
+
product: {
|
|
72
|
+
key: testData.skuWithoutTiers,
|
|
73
|
+
},
|
|
74
|
+
quantity: 1
|
|
75
|
+
}, session);
|
|
76
|
+
|
|
77
|
+
const updatedCart = await provider.changeQuantity({
|
|
78
|
+
cart: cart.identifier,
|
|
79
|
+
item: cart.items[0].identifier,
|
|
80
|
+
quantity: 3
|
|
81
|
+
}, session);
|
|
82
|
+
|
|
83
|
+
expect(updatedCart.identifier.key).toBe(cart.identifier.key);
|
|
84
|
+
expect(updatedCart.items.length).toBe(1);
|
|
85
|
+
expect(updatedCart.items[0].product.key).toBe(testData.skuWithoutTiers);
|
|
86
|
+
expect(updatedCart.items[0].quantity).toBe(3);
|
|
87
|
+
|
|
88
|
+
expect(updatedCart.items[0].price.totalPrice.value).toBe(cart.items[0].price.totalPrice.value * 3);
|
|
89
|
+
expect(updatedCart.items[0].price.unitPrice.value).toBe(cart.items[0].price.unitPrice.value);
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should be able to remove an item from a cart', async () => {
|
|
95
|
+
|
|
96
|
+
const cart = await provider.add({
|
|
97
|
+
cart: { key: '' },
|
|
98
|
+
product: {
|
|
99
|
+
key: testData.skuWithoutTiers,
|
|
100
|
+
},
|
|
101
|
+
quantity: 1
|
|
102
|
+
}, session);
|
|
103
|
+
|
|
104
|
+
const updatedCart = await provider.remove({
|
|
105
|
+
cart: cart.identifier,
|
|
106
|
+
item: cart.items[0].identifier,
|
|
107
|
+
}, session);
|
|
108
|
+
expect(updatedCart.identifier.key).toBe(cart.identifier.key);
|
|
109
|
+
expect(updatedCart.items.length).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
it('should be able to create a cart for an anonymous user, then login and merge the cart', async () => {
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should be able to create a cart for an anonymous user, then register and merge the cart', async () => {
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should be able to clear the cart', async () => { });
|
|
120
|
+
|
|
121
|
+
it('should be able to check out the cart', async () => { });
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
});
|