@labdigital/commercetools-mock 2.65.1 → 3.0.0-beta.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.
Files changed (281) hide show
  1. package/README.md +31 -8
  2. package/dist/abstract-BKFcva6S.mjs +1044 -0
  3. package/dist/abstract-BKFcva6S.mjs.map +1 -0
  4. package/dist/config-BcNSzPZz.d.mts +1718 -0
  5. package/dist/index.d.mts +50 -1633
  6. package/dist/index.mjs +3771 -2654
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/storage/sqlite.d.mts +59 -0
  9. package/dist/storage/sqlite.mjs +234 -0
  10. package/dist/storage/sqlite.mjs.map +1 -0
  11. package/package.json +26 -29
  12. package/src/ctMock.ts +125 -136
  13. package/src/helpers.ts +14 -6
  14. package/src/index.ts +5 -0
  15. package/src/lib/masking.ts +4 -5
  16. package/src/lib/product-review-statistics.test.ts +257 -294
  17. package/src/lib/review-statistics.ts +17 -4
  18. package/src/oauth/helpers.ts +7 -4
  19. package/src/oauth/server.test.ts +102 -62
  20. package/src/oauth/server.ts +215 -213
  21. package/src/oauth/store.ts +20 -6
  22. package/src/orderSearch.ts +3 -3
  23. package/src/product-projection-search.ts +38 -20
  24. package/src/product-search-availability.test.ts +31 -52
  25. package/src/product-search.ts +19 -10
  26. package/src/projectAPI.ts +6 -22
  27. package/src/repositories/abstract.ts +182 -48
  28. package/src/repositories/as-associate.test.ts +19 -19
  29. package/src/repositories/associate-role.ts +12 -23
  30. package/src/repositories/attribute-group.test.ts +23 -23
  31. package/src/repositories/attribute-group.ts +6 -4
  32. package/src/repositories/business-unit.test.ts +63 -57
  33. package/src/repositories/business-unit.ts +107 -55
  34. package/src/repositories/cart/actions.ts +96 -65
  35. package/src/repositories/cart/helpers.ts +15 -11
  36. package/src/repositories/cart/index.test.ts +136 -30
  37. package/src/repositories/cart/index.ts +76 -59
  38. package/src/repositories/cart-discount/actions.ts +12 -44
  39. package/src/repositories/cart-discount/index.ts +20 -8
  40. package/src/repositories/category/actions.ts +27 -27
  41. package/src/repositories/category/index.test.ts +13 -9
  42. package/src/repositories/category/index.ts +40 -23
  43. package/src/repositories/channel.test.ts +53 -51
  44. package/src/repositories/channel.ts +12 -22
  45. package/src/repositories/custom-object.ts +34 -25
  46. package/src/repositories/customer/actions.ts +47 -25
  47. package/src/repositories/customer/index.test.ts +11 -11
  48. package/src/repositories/customer/index.ts +65 -35
  49. package/src/repositories/customer-group.test.ts +44 -42
  50. package/src/repositories/customer-group.ts +12 -22
  51. package/src/repositories/discount-code/actions.ts +3 -19
  52. package/src/repositories/discount-code/index.ts +9 -4
  53. package/src/repositories/discount-group/index.ts +8 -3
  54. package/src/repositories/extension.test.ts +27 -27
  55. package/src/repositories/extension.ts +10 -5
  56. package/src/repositories/helpers.ts +126 -47
  57. package/src/repositories/inventory-entry/actions.ts +3 -24
  58. package/src/repositories/inventory-entry/index.ts +19 -11
  59. package/src/repositories/my-customer.ts +13 -12
  60. package/src/repositories/my-order.ts +5 -2
  61. package/src/repositories/order/actions.ts +89 -56
  62. package/src/repositories/order/index.test.ts +36 -31
  63. package/src/repositories/order/index.ts +83 -49
  64. package/src/repositories/order-edit.ts +8 -3
  65. package/src/repositories/payment/actions.ts +64 -44
  66. package/src/repositories/payment/helpers.ts +3 -3
  67. package/src/repositories/payment/index.ts +28 -12
  68. package/src/repositories/product/actions.ts +133 -98
  69. package/src/repositories/product/helpers.ts +29 -16
  70. package/src/repositories/product/index.ts +42 -25
  71. package/src/repositories/product-discount.ts +6 -4
  72. package/src/repositories/product-projection.ts +41 -21
  73. package/src/repositories/product-selection.ts +8 -15
  74. package/src/repositories/product-tailoring.ts +22 -3
  75. package/src/repositories/product-type.ts +45 -4
  76. package/src/repositories/project.ts +57 -13
  77. package/src/repositories/quote/actions.ts +5 -28
  78. package/src/repositories/quote/index.ts +29 -6
  79. package/src/repositories/quote-request/actions.ts +5 -28
  80. package/src/repositories/quote-request/index.test.ts +3 -3
  81. package/src/repositories/quote-request/index.ts +31 -11
  82. package/src/repositories/quote-staged/actions.ts +5 -28
  83. package/src/repositories/quote-staged/index.ts +22 -8
  84. package/src/repositories/recurrence-policy/index.ts +6 -4
  85. package/src/repositories/recurring-order/actions.ts +7 -32
  86. package/src/repositories/recurring-order/index.ts +8 -6
  87. package/src/repositories/review.test.ts +147 -142
  88. package/src/repositories/review.ts +31 -37
  89. package/src/repositories/shipping-method/actions.ts +11 -28
  90. package/src/repositories/shipping-method/index.ts +26 -15
  91. package/src/repositories/shopping-list/actions.ts +21 -31
  92. package/src/repositories/shopping-list/index.ts +44 -22
  93. package/src/repositories/standalone-price.ts +6 -4
  94. package/src/repositories/state.ts +15 -9
  95. package/src/repositories/store.ts +21 -32
  96. package/src/repositories/subscription.test.ts +22 -22
  97. package/src/repositories/subscription.ts +8 -3
  98. package/src/repositories/tax-category/index.ts +8 -3
  99. package/src/repositories/type/actions.ts +21 -3
  100. package/src/repositories/type/index.ts +5 -3
  101. package/src/repositories/zone.test.ts +112 -77
  102. package/src/repositories/zone.ts +5 -3
  103. package/src/schemas/generated/associate-role.ts +13 -0
  104. package/src/schemas/generated/attribute-group.ts +12 -0
  105. package/src/schemas/generated/business-unit.ts +38 -0
  106. package/src/schemas/generated/cart-discount.ts +33 -0
  107. package/src/schemas/generated/cart.ts +61 -0
  108. package/src/schemas/generated/category.ts +25 -0
  109. package/src/schemas/generated/channel.ts +21 -0
  110. package/src/schemas/generated/common.ts +1372 -0
  111. package/src/schemas/generated/custom-object.ts +11 -0
  112. package/src/schemas/generated/customer-group.ts +11 -0
  113. package/src/schemas/generated/customer.ts +47 -0
  114. package/src/schemas/generated/discount-code.ts +25 -0
  115. package/src/schemas/generated/discount-group.ts +13 -0
  116. package/src/schemas/generated/extension.ts +15 -0
  117. package/src/schemas/generated/index.ts +42 -0
  118. package/src/schemas/generated/inventory-entry.ts +20 -0
  119. package/src/schemas/generated/my-quote-request.ts +10 -0
  120. package/src/schemas/generated/order-edit.ts +18 -0
  121. package/src/schemas/generated/order-from-cart.ts +25 -0
  122. package/src/schemas/generated/payment.ts +30 -0
  123. package/src/schemas/generated/product-discount.ts +20 -0
  124. package/src/schemas/generated/product-selection.ts +18 -0
  125. package/src/schemas/generated/product-tailoring.ts +26 -0
  126. package/src/schemas/generated/product-type.ts +12 -0
  127. package/src/schemas/generated/product.ts +37 -0
  128. package/src/schemas/generated/quote-request.ts +19 -0
  129. package/src/schemas/generated/quote.ts +18 -0
  130. package/src/schemas/generated/recurrence-policy.ts +15 -0
  131. package/src/schemas/generated/recurring-order.ts +19 -0
  132. package/src/schemas/generated/review.ts +24 -0
  133. package/src/schemas/generated/shipping-method.ts +24 -0
  134. package/src/schemas/generated/shopping-list.ts +28 -0
  135. package/src/schemas/generated/staged-quote.ts +18 -0
  136. package/src/schemas/generated/standalone-price.ts +32 -0
  137. package/src/schemas/generated/state.ts +20 -0
  138. package/src/schemas/generated/store.ts +23 -0
  139. package/src/schemas/generated/subscription.ts +20 -0
  140. package/src/schemas/generated/tax-category.ts +12 -0
  141. package/src/schemas/generated/type.ts +17 -0
  142. package/src/schemas/generated/zone.ts +12 -0
  143. package/src/schemas/update-request.ts +3 -5
  144. package/src/server.ts +32 -4
  145. package/src/services/abstract.ts +207 -101
  146. package/src/services/as-associate-cart.test.ts +28 -36
  147. package/src/services/as-associate-cart.ts +15 -12
  148. package/src/services/as-associate-order.test.ts +33 -40
  149. package/src/services/as-associate-order.ts +15 -12
  150. package/src/services/as-associate-quote-request.ts +15 -12
  151. package/src/services/as-associate-shopping-list.test.ts +25 -35
  152. package/src/services/as-associate-shopping-list.ts +15 -12
  153. package/src/services/as-associate.test.ts +21 -15
  154. package/src/services/as-associate.ts +23 -22
  155. package/src/services/associate-roles.test.ts +16 -22
  156. package/src/services/associate-roles.ts +2 -2
  157. package/src/services/attribute-group.test.ts +40 -44
  158. package/src/services/attribute-group.ts +2 -2
  159. package/src/services/business-units.test.ts +227 -163
  160. package/src/services/business-units.ts +2 -2
  161. package/src/services/cart-discount.test.ts +253 -187
  162. package/src/services/cart-discount.ts +2 -2
  163. package/src/services/cart.test.ts +833 -832
  164. package/src/services/cart.ts +31 -12
  165. package/src/services/category.test.ts +208 -130
  166. package/src/services/category.ts +2 -2
  167. package/src/services/channel.test.ts +39 -44
  168. package/src/services/channel.ts +2 -2
  169. package/src/services/custom-object.test.ts +103 -79
  170. package/src/services/custom-object.ts +106 -38
  171. package/src/services/customer-group.test.ts +39 -44
  172. package/src/services/customer-group.ts +2 -2
  173. package/src/services/customer.test.ts +357 -292
  174. package/src/services/customer.ts +70 -23
  175. package/src/services/discount-code.test.ts +57 -68
  176. package/src/services/discount-code.ts +2 -2
  177. package/src/services/discount-group.test.ts +111 -134
  178. package/src/services/discount-group.ts +2 -2
  179. package/src/services/draft-validation.test.ts +255 -0
  180. package/src/services/extension.test.ts +39 -44
  181. package/src/services/extension.ts +2 -2
  182. package/src/services/inventory-entry.test.ts +106 -87
  183. package/src/services/inventory-entry.ts +2 -2
  184. package/src/services/my-business-unit.test.ts +82 -112
  185. package/src/services/my-business-unit.ts +25 -19
  186. package/src/services/my-cart.test.ts +46 -41
  187. package/src/services/my-cart.ts +32 -28
  188. package/src/services/my-customer.test.ts +153 -88
  189. package/src/services/my-customer.ts +130 -61
  190. package/src/services/my-order.ts +15 -12
  191. package/src/services/my-payment.test.ts +30 -24
  192. package/src/services/my-payment.ts +2 -2
  193. package/src/services/my-shopping-list.ts +2 -2
  194. package/src/services/order.test.ts +332 -276
  195. package/src/services/order.ts +45 -27
  196. package/src/services/payment.test.ts +31 -29
  197. package/src/services/payment.ts +2 -2
  198. package/src/services/product-discount.test.ts +39 -46
  199. package/src/services/product-discount.ts +2 -2
  200. package/src/services/product-projection.test.ts +176 -166
  201. package/src/services/product-projection.ts +31 -15
  202. package/src/services/product-selection.test.ts +17 -9
  203. package/src/services/product-selection.ts +2 -2
  204. package/src/services/product-type.test.ts +80 -21
  205. package/src/services/product-type.ts +2 -2
  206. package/src/services/product.test.ts +569 -534
  207. package/src/services/product.ts +14 -7
  208. package/src/services/project.test.ts +22 -12
  209. package/src/services/project.ts +28 -13
  210. package/src/services/quote-request.test.ts +36 -39
  211. package/src/services/quote-request.ts +2 -2
  212. package/src/services/quote-staged.ts +2 -2
  213. package/src/services/quote.ts +2 -2
  214. package/src/services/recurrence-policy.test.ts +114 -139
  215. package/src/services/recurrence-policy.ts +2 -2
  216. package/src/services/recurring-order.test.ts +149 -194
  217. package/src/services/recurring-order.ts +2 -2
  218. package/src/services/reviews.test.ts +127 -106
  219. package/src/services/reviews.ts +2 -2
  220. package/src/services/shipping-method.test.ts +96 -125
  221. package/src/services/shipping-method.ts +24 -12
  222. package/src/services/shopping-list.test.ts +183 -141
  223. package/src/services/shopping-list.ts +2 -2
  224. package/src/services/standalone-price.test.ts +60 -46
  225. package/src/services/standalone-price.ts +2 -2
  226. package/src/services/state.test.ts +20 -25
  227. package/src/services/state.ts +2 -2
  228. package/src/services/store.test.ts +26 -45
  229. package/src/services/store.ts +2 -2
  230. package/src/services/subscription.test.ts +39 -44
  231. package/src/services/subscription.ts +2 -2
  232. package/src/services/tax-category.test.ts +33 -36
  233. package/src/services/tax-category.ts +2 -2
  234. package/src/services/type.test.ts +45 -44
  235. package/src/services/type.ts +2 -2
  236. package/src/services/zone.test.ts +40 -44
  237. package/src/services/zone.ts +2 -2
  238. package/src/shipping.ts +41 -11
  239. package/src/storage/abstract.ts +248 -17
  240. package/src/storage/in-memory.ts +147 -290
  241. package/src/storage/sqlite.ts +429 -0
  242. package/src/storage/storage-map.ts +75 -0
  243. package/src/storage/storage.test-helpers.ts +97 -0
  244. package/src/storage/storage.test.ts +802 -0
  245. package/src/testing/associate-role.ts +28 -0
  246. package/src/testing/attribute-group.ts +27 -0
  247. package/src/testing/business-unit.ts +9 -8
  248. package/src/testing/cart-discount.ts +34 -0
  249. package/src/testing/cart.ts +20 -0
  250. package/src/testing/category.ts +25 -0
  251. package/src/testing/channel.ts +23 -0
  252. package/src/testing/custom-object.ts +27 -0
  253. package/src/testing/customer-group.ts +26 -0
  254. package/src/testing/customer.ts +36 -33
  255. package/src/testing/discount-code.ts +29 -0
  256. package/src/testing/discount-group.ts +27 -0
  257. package/src/testing/extension.ts +32 -0
  258. package/src/testing/index.ts +33 -0
  259. package/src/testing/inventory-entry.ts +26 -0
  260. package/src/testing/order.ts +27 -0
  261. package/src/testing/payment.ts +23 -0
  262. package/src/testing/product-discount.ts +33 -0
  263. package/src/testing/product-selection.ts +28 -0
  264. package/src/testing/product-type.ts +27 -0
  265. package/src/testing/product.ts +38 -0
  266. package/src/testing/quote-request.ts +29 -0
  267. package/src/testing/recurrence-policy.ts +33 -0
  268. package/src/testing/recurring-order.ts +32 -0
  269. package/src/testing/review.ts +24 -0
  270. package/src/testing/shipping-method.ts +31 -0
  271. package/src/testing/shopping-list.ts +25 -0
  272. package/src/testing/standalone-price.ts +31 -0
  273. package/src/testing/state.ts +21 -0
  274. package/src/testing/store.ts +26 -0
  275. package/src/testing/subscription.ts +38 -0
  276. package/src/testing/tax-category.ts +27 -0
  277. package/src/testing/type.ts +9 -6
  278. package/src/testing/zone.ts +22 -0
  279. package/src/validate.test.ts +122 -0
  280. package/src/validate.ts +78 -7
  281. package/src/.env +0 -0
@@ -1,7 +1,9 @@
1
1
  import type { Product } from "@commercetools/platform-sdk";
2
- import supertest from "supertest";
3
2
  import { beforeEach, describe, expect, test } from "vitest";
4
3
  import { CommercetoolsMock } from "#src/index.ts";
4
+ import { productDraftFactory } from "#src/testing/product.ts";
5
+ import { productTypeDraftFactory } from "#src/testing/product-type.ts";
6
+ import { reviewDraftFactory } from "#src/testing/review.ts";
5
7
 
6
8
  describe("Product Review Statistics", () => {
7
9
  let ctMock: CommercetoolsMock;
@@ -10,335 +12,296 @@ describe("Product Review Statistics", () => {
10
12
  beforeEach(async () => {
11
13
  ctMock = new CommercetoolsMock();
12
14
 
13
- // Create a product
14
- const productResponse = await supertest(ctMock.app)
15
- .post("/dummy/products")
16
- .send({
17
- name: { en: "Test Product" },
18
- slug: { en: "test-product" },
19
- productType: {
20
- typeId: "product-type",
21
- key: "dummy-product-type",
22
- },
23
- masterVariant: {
24
- sku: "test-sku-1",
25
- prices: [
26
- {
27
- value: {
28
- currencyCode: "EUR",
29
- centAmount: 1000,
30
- },
15
+ const productType = await productTypeDraftFactory(ctMock).create();
16
+
17
+ product = await productDraftFactory(ctMock).create({
18
+ productType: {
19
+ typeId: "product-type",
20
+ id: productType.id,
21
+ },
22
+ masterVariant: {
23
+ sku: "test-sku-1",
24
+ prices: [
25
+ {
26
+ value: {
27
+ currencyCode: "EUR",
28
+ centAmount: 1000,
31
29
  },
32
- ],
33
- },
34
- });
35
- expect(productResponse.status).toBe(201);
36
- product = productResponse.body;
30
+ },
31
+ ],
32
+ },
33
+ });
37
34
  });
38
35
 
39
36
  test("product has no review statistics when no reviews exist", async () => {
40
- const response = await supertest(ctMock.app).get(
41
- `/dummy/products/${product.id}`,
42
- );
37
+ const response = await ctMock.app.inject({
38
+ method: "GET",
39
+ url: `/dummy/products/${product.id}`,
40
+ });
43
41
 
44
- expect(response.status).toBe(200);
45
- expect(response.body.reviewRatingStatistics).toBeUndefined();
42
+ expect(response.statusCode).toBe(200);
43
+ expect(response.json().reviewRatingStatistics).toBeUndefined();
46
44
  });
47
45
 
48
46
  test("product has review statistics when reviews exist", async () => {
49
- // Create reviews for the product
50
- await supertest(ctMock.app)
51
- .post("/dummy/reviews")
52
- .send({
53
- authorName: "John Doe",
54
- title: "Great product!",
55
- text: "I really love this product.",
56
- rating: 5,
57
- target: {
58
- typeId: "product",
59
- id: product.id,
60
- },
61
- });
62
-
63
- await supertest(ctMock.app)
64
- .post("/dummy/reviews")
65
- .send({
66
- authorName: "Jane Smith",
67
- title: "Good product",
68
- text: "Pretty good overall.",
69
- rating: 4,
70
- target: {
71
- typeId: "product",
72
- id: product.id,
73
- },
74
- });
75
-
76
- await supertest(ctMock.app)
77
- .post("/dummy/reviews")
78
- .send({
79
- authorName: "Bob Wilson",
80
- title: "Excellent!",
81
- text: "Amazing quality.",
82
- rating: 5,
83
- target: {
84
- typeId: "product",
85
- id: product.id,
86
- },
87
- });
88
-
89
- const response = await supertest(ctMock.app).get(
90
- `/dummy/products/${product.id}`,
91
- );
92
-
93
- expect(response.status).toBe(200);
94
- expect(response.body.reviewRatingStatistics).toBeDefined();
95
- expect(response.body.reviewRatingStatistics.count).toBe(3);
96
- expect(response.body.reviewRatingStatistics.averageRating).toBe(4.66667);
97
- expect(response.body.reviewRatingStatistics.highestRating).toBe(5);
98
- expect(response.body.reviewRatingStatistics.lowestRating).toBe(4);
99
- expect(response.body.reviewRatingStatistics.ratingsDistribution).toEqual({
47
+ await reviewDraftFactory(ctMock).create({
48
+ authorName: "John Doe",
49
+ title: "Great product!",
50
+ text: "I really love this product.",
51
+ rating: 5,
52
+ target: {
53
+ typeId: "product",
54
+ id: product.id,
55
+ },
56
+ });
57
+
58
+ await reviewDraftFactory(ctMock).create({
59
+ authorName: "Jane Smith",
60
+ title: "Good product",
61
+ text: "Pretty good overall.",
62
+ rating: 4,
63
+ target: {
64
+ typeId: "product",
65
+ id: product.id,
66
+ },
67
+ });
68
+
69
+ await reviewDraftFactory(ctMock).create({
70
+ authorName: "Bob Wilson",
71
+ title: "Excellent!",
72
+ text: "Amazing quality.",
73
+ rating: 5,
74
+ target: {
75
+ typeId: "product",
76
+ id: product.id,
77
+ },
78
+ });
79
+
80
+ const response = await ctMock.app.inject({
81
+ method: "GET",
82
+ url: `/dummy/products/${product.id}`,
83
+ });
84
+
85
+ expect(response.statusCode).toBe(200);
86
+ const body = response.json();
87
+ expect(body.reviewRatingStatistics).toBeDefined();
88
+ expect(body.reviewRatingStatistics.count).toBe(3);
89
+ expect(body.reviewRatingStatistics.averageRating).toBe(4.66667);
90
+ expect(body.reviewRatingStatistics.highestRating).toBe(5);
91
+ expect(body.reviewRatingStatistics.lowestRating).toBe(4);
92
+ expect(body.reviewRatingStatistics.ratingsDistribution).toEqual({
100
93
  "4": 1,
101
94
  "5": 2,
102
95
  });
103
96
  });
104
97
 
105
98
  test("product projection has review statistics", async () => {
106
- // Create a review for the product
107
- await supertest(ctMock.app)
108
- .post("/dummy/reviews")
109
- .send({
110
- authorName: "Test User",
111
- title: "Test Review",
112
- text: "Test review text.",
113
- rating: 3,
114
- target: {
115
- typeId: "product",
116
- id: product.id,
117
- },
118
- });
119
-
120
- const response = await supertest(ctMock.app).get(
121
- `/dummy/product-projections/${product.id}`,
122
- );
123
-
124
- expect(response.status).toBe(200);
125
- expect(response.body.reviewRatingStatistics).toBeDefined();
126
- expect(response.body.reviewRatingStatistics.count).toBe(1);
127
- expect(response.body.reviewRatingStatistics.averageRating).toBe(3);
128
- expect(response.body.reviewRatingStatistics.highestRating).toBe(3);
129
- expect(response.body.reviewRatingStatistics.lowestRating).toBe(3);
130
- expect(response.body.reviewRatingStatistics.ratingsDistribution).toEqual({
99
+ await reviewDraftFactory(ctMock).create({
100
+ authorName: "Test User",
101
+ title: "Test Review",
102
+ text: "Test review text.",
103
+ rating: 3,
104
+ target: {
105
+ typeId: "product",
106
+ id: product.id,
107
+ },
108
+ });
109
+
110
+ const response = await ctMock.app.inject({
111
+ method: "GET",
112
+ url: `/dummy/product-projections/${product.id}`,
113
+ });
114
+
115
+ expect(response.statusCode).toBe(200);
116
+ const body = response.json();
117
+ expect(body.reviewRatingStatistics).toBeDefined();
118
+ expect(body.reviewRatingStatistics.count).toBe(1);
119
+ expect(body.reviewRatingStatistics.averageRating).toBe(3);
120
+ expect(body.reviewRatingStatistics.highestRating).toBe(3);
121
+ expect(body.reviewRatingStatistics.lowestRating).toBe(3);
122
+ expect(body.reviewRatingStatistics.ratingsDistribution).toEqual({
131
123
  "3": 1,
132
124
  });
133
125
  });
134
126
 
135
127
  test("product query includes review statistics", async () => {
136
- // Create reviews for the product
137
- await supertest(ctMock.app)
138
- .post("/dummy/reviews")
139
- .send({
140
- authorName: "Reviewer 1",
141
- rating: 2,
142
- target: {
143
- typeId: "product",
144
- id: product.id,
145
- },
146
- });
147
-
148
- await supertest(ctMock.app)
149
- .post("/dummy/reviews")
150
- .send({
151
- authorName: "Reviewer 2",
152
- rating: 4,
153
- target: {
154
- typeId: "product",
155
- id: product.id,
156
- },
157
- });
158
-
159
- const response = await supertest(ctMock.app).get("/dummy/products");
160
-
161
- expect(response.status).toBe(200);
162
- expect(response.body.results).toHaveLength(1);
163
- expect(response.body.results[0].reviewRatingStatistics).toBeDefined();
164
- expect(response.body.results[0].reviewRatingStatistics.count).toBe(2);
165
- expect(response.body.results[0].reviewRatingStatistics.averageRating).toBe(
166
- 3,
167
- );
168
- expect(response.body.results[0].reviewRatingStatistics.highestRating).toBe(
169
- 4,
170
- );
171
- expect(response.body.results[0].reviewRatingStatistics.lowestRating).toBe(
172
- 2,
173
- );
128
+ await reviewDraftFactory(ctMock).create({
129
+ authorName: "Reviewer 1",
130
+ rating: 2,
131
+ target: {
132
+ typeId: "product",
133
+ id: product.id,
134
+ },
135
+ });
136
+
137
+ await reviewDraftFactory(ctMock).create({
138
+ authorName: "Reviewer 2",
139
+ rating: 4,
140
+ target: {
141
+ typeId: "product",
142
+ id: product.id,
143
+ },
144
+ });
145
+
146
+ const response = await ctMock.app.inject({
147
+ method: "GET",
148
+ url: "/dummy/products",
149
+ });
150
+
151
+ expect(response.statusCode).toBe(200);
152
+ const body = response.json();
153
+ expect(body.results).toHaveLength(1);
154
+ expect(body.results[0].reviewRatingStatistics).toBeDefined();
155
+ expect(body.results[0].reviewRatingStatistics.count).toBe(2);
156
+ expect(body.results[0].reviewRatingStatistics.averageRating).toBe(3);
157
+ expect(body.results[0].reviewRatingStatistics.highestRating).toBe(4);
158
+ expect(body.results[0].reviewRatingStatistics.lowestRating).toBe(2);
174
159
  });
175
160
 
176
161
  test("only reviews with includedInStatistics=true are counted", async () => {
177
- // Create reviews - both will be included by default
178
- const _review1Response = await supertest(ctMock.app)
179
- .post("/dummy/reviews")
180
- .send({
181
- authorName: "Reviewer 1",
182
- rating: 5,
183
- target: {
184
- typeId: "product",
185
- id: product.id,
186
- },
187
- });
188
-
189
- const _review2Response = await supertest(ctMock.app)
190
- .post("/dummy/reviews")
191
- .send({
192
- authorName: "Reviewer 2",
193
- rating: 1,
194
- target: {
195
- typeId: "product",
196
- id: product.id,
197
- },
198
- });
199
-
200
- // Check that both reviews are included by default
201
- const response = await supertest(ctMock.app).get(
202
- `/dummy/products/${product.id}`,
203
- );
204
-
205
- expect(response.status).toBe(200);
206
- expect(response.body.reviewRatingStatistics).toBeDefined();
207
- expect(response.body.reviewRatingStatistics.count).toBe(2);
208
- expect(response.body.reviewRatingStatistics.averageRating).toBe(3);
209
-
210
- // Now exclude one review from statistics by updating it
211
- // (Note: In a real implementation, this would be done via state transitions,
212
- // but for now we can test the filtering works with includedInStatistics directly)
162
+ await reviewDraftFactory(ctMock).create({
163
+ authorName: "Reviewer 1",
164
+ rating: 5,
165
+ target: {
166
+ typeId: "product",
167
+ id: product.id,
168
+ },
169
+ });
170
+
171
+ await reviewDraftFactory(ctMock).create({
172
+ authorName: "Reviewer 2",
173
+ rating: 1,
174
+ target: {
175
+ typeId: "product",
176
+ id: product.id,
177
+ },
178
+ });
179
+
180
+ const response = await ctMock.app.inject({
181
+ method: "GET",
182
+ url: `/dummy/products/${product.id}`,
183
+ });
184
+
185
+ expect(response.statusCode).toBe(200);
186
+ const body = response.json();
187
+ expect(body.reviewRatingStatistics).toBeDefined();
188
+ expect(body.reviewRatingStatistics.count).toBe(2);
189
+ expect(body.reviewRatingStatistics.averageRating).toBe(3);
213
190
  });
214
191
 
215
192
  test("reviews without ratings are not included in statistics", async () => {
216
- // Create a review without rating
217
- await supertest(ctMock.app)
218
- .post("/dummy/reviews")
219
- .send({
220
- authorName: "No Rating User",
221
- title: "No rating review",
222
- text: "This review has no rating.",
223
- target: {
224
- typeId: "product",
225
- id: product.id,
226
- },
227
- });
228
-
229
- // Create a review with rating
230
- await supertest(ctMock.app)
231
- .post("/dummy/reviews")
232
- .send({
233
- authorName: "Rated User",
234
- title: "Rated review",
235
- rating: 4,
236
- target: {
237
- typeId: "product",
238
- id: product.id,
239
- },
240
- });
241
-
242
- const response = await supertest(ctMock.app).get(
243
- `/dummy/products/${product.id}`,
244
- );
245
-
246
- expect(response.status).toBe(200);
247
- // Only the review with rating should be counted
248
- expect(response.body.reviewRatingStatistics).toBeDefined();
249
- expect(response.body.reviewRatingStatistics.count).toBe(1);
250
- expect(response.body.reviewRatingStatistics.averageRating).toBe(4);
193
+ await reviewDraftFactory(ctMock).create({
194
+ authorName: "No Rating User",
195
+ title: "No rating review",
196
+ text: "This review has no rating.",
197
+ rating: undefined,
198
+ target: {
199
+ typeId: "product",
200
+ id: product.id,
201
+ },
202
+ });
203
+
204
+ await reviewDraftFactory(ctMock).create({
205
+ authorName: "Rated User",
206
+ title: "Rated review",
207
+ rating: 4,
208
+ target: {
209
+ typeId: "product",
210
+ id: product.id,
211
+ },
212
+ });
213
+
214
+ const response = await ctMock.app.inject({
215
+ method: "GET",
216
+ url: `/dummy/products/${product.id}`,
217
+ });
218
+
219
+ expect(response.statusCode).toBe(200);
220
+ const body = response.json();
221
+ expect(body.reviewRatingStatistics).toBeDefined();
222
+ expect(body.reviewRatingStatistics.count).toBe(1);
223
+ expect(body.reviewRatingStatistics.averageRating).toBe(4);
251
224
  });
252
225
 
253
226
  test("reviews on other products are excluded from statistics", async () => {
254
- // Create another product
255
- const otherProductResponse = await supertest(ctMock.app)
256
- .post("/dummy/products")
257
- .send({
258
- name: { en: "Other Product" },
259
- slug: { en: "other-product" },
260
- productType: {
261
- typeId: "product-type",
262
- key: "dummy-product-type",
263
- },
264
- masterVariant: {
265
- sku: "other-sku",
266
- prices: [
267
- {
268
- value: {
269
- currencyCode: "EUR",
270
- centAmount: 2000,
271
- },
227
+ const otherProduct = await productDraftFactory(ctMock).create({
228
+ productType: {
229
+ typeId: "product-type",
230
+ id: product.productType.id,
231
+ },
232
+ masterVariant: {
233
+ sku: "other-sku",
234
+ prices: [
235
+ {
236
+ value: {
237
+ currencyCode: "EUR",
238
+ centAmount: 2000,
272
239
  },
273
- ],
274
- },
275
- });
276
- expect(otherProductResponse.status).toBe(201);
277
- const otherProduct = otherProductResponse.body;
278
-
279
- // Create reviews for both products
280
- await supertest(ctMock.app)
281
- .post("/dummy/reviews")
282
- .send({
283
- authorName: "User A",
284
- title: "Review for first product",
285
- rating: 5,
286
- target: {
287
- typeId: "product",
288
- id: product.id,
289
- },
290
- });
291
-
292
- await supertest(ctMock.app)
293
- .post("/dummy/reviews")
294
- .send({
295
- authorName: "User B",
296
- title: "Review for second product",
297
- rating: 1,
298
- target: {
299
- typeId: "product",
300
- id: otherProduct.id,
301
- },
302
- });
303
-
304
- await supertest(ctMock.app)
305
- .post("/dummy/reviews")
306
- .send({
307
- authorName: "User C",
308
- title: "Another review for first product",
309
- rating: 3,
310
- target: {
311
- typeId: "product",
312
- id: product.id,
313
- },
314
- });
240
+ },
241
+ ],
242
+ },
243
+ });
244
+
245
+ await reviewDraftFactory(ctMock).create({
246
+ authorName: "User A",
247
+ title: "Review for first product",
248
+ rating: 5,
249
+ target: {
250
+ typeId: "product",
251
+ id: product.id,
252
+ },
253
+ });
254
+
255
+ await reviewDraftFactory(ctMock).create({
256
+ authorName: "User B",
257
+ title: "Review for second product",
258
+ rating: 1,
259
+ target: {
260
+ typeId: "product",
261
+ id: otherProduct.id,
262
+ },
263
+ });
264
+
265
+ await reviewDraftFactory(ctMock).create({
266
+ authorName: "User C",
267
+ title: "Another review for first product",
268
+ rating: 3,
269
+ target: {
270
+ typeId: "product",
271
+ id: product.id,
272
+ },
273
+ });
315
274
 
316
275
  // Check statistics for the first product - should only include its own reviews
317
- const response1 = await supertest(ctMock.app).get(
318
- `/dummy/products/${product.id}`,
319
- );
320
- expect(response1.status).toBe(200);
321
- expect(response1.body.reviewRatingStatistics).toBeDefined();
322
- expect(response1.body.reviewRatingStatistics.count).toBe(2); // Only reviews for this product
323
- expect(response1.body.reviewRatingStatistics.averageRating).toBe(4); // (5 + 3) / 2 = 4
324
- expect(response1.body.reviewRatingStatistics.highestRating).toBe(5);
325
- expect(response1.body.reviewRatingStatistics.lowestRating).toBe(3);
326
- expect(response1.body.reviewRatingStatistics.ratingsDistribution).toEqual({
276
+ const response1 = await ctMock.app.inject({
277
+ method: "GET",
278
+ url: `/dummy/products/${product.id}`,
279
+ });
280
+ expect(response1.statusCode).toBe(200);
281
+ const body1 = response1.json();
282
+ expect(body1.reviewRatingStatistics).toBeDefined();
283
+ expect(body1.reviewRatingStatistics.count).toBe(2);
284
+ expect(body1.reviewRatingStatistics.averageRating).toBe(4);
285
+ expect(body1.reviewRatingStatistics.highestRating).toBe(5);
286
+ expect(body1.reviewRatingStatistics.lowestRating).toBe(3);
287
+ expect(body1.reviewRatingStatistics.ratingsDistribution).toEqual({
327
288
  "3": 1,
328
289
  "5": 1,
329
290
  });
330
291
 
331
292
  // Check statistics for the second product - should only include its own review
332
- const response2 = await supertest(ctMock.app).get(
333
- `/dummy/products/${otherProduct.id}`,
334
- );
335
- expect(response2.status).toBe(200);
336
- expect(response2.body.reviewRatingStatistics).toBeDefined();
337
- expect(response2.body.reviewRatingStatistics.count).toBe(1); // Only reviews for this product
338
- expect(response2.body.reviewRatingStatistics.averageRating).toBe(1);
339
- expect(response2.body.reviewRatingStatistics.highestRating).toBe(1);
340
- expect(response2.body.reviewRatingStatistics.lowestRating).toBe(1);
341
- expect(response2.body.reviewRatingStatistics.ratingsDistribution).toEqual({
293
+ const response2 = await ctMock.app.inject({
294
+ method: "GET",
295
+ url: `/dummy/products/${otherProduct.id}`,
296
+ });
297
+ expect(response2.statusCode).toBe(200);
298
+ const body2 = response2.json();
299
+ expect(body2.reviewRatingStatistics).toBeDefined();
300
+ expect(body2.reviewRatingStatistics.count).toBe(1);
301
+ expect(body2.reviewRatingStatistics.averageRating).toBe(1);
302
+ expect(body2.reviewRatingStatistics.highestRating).toBe(1);
303
+ expect(body2.reviewRatingStatistics.lowestRating).toBe(1);
304
+ expect(body2.reviewRatingStatistics.ratingsDistribution).toEqual({
342
305
  "1": 1,
343
306
  });
344
307
  });
@@ -5,14 +5,27 @@ import type {
5
5
  import type { AbstractStorage } from "../storage/index.ts";
6
6
 
7
7
  export class ReviewStatisticsService {
8
- constructor(private _storage: AbstractStorage) {}
8
+ private _storage: AbstractStorage;
9
9
 
10
- calculateProductReviewStatistics(
10
+ constructor(_storage: AbstractStorage) {
11
+ this._storage = _storage;
12
+ }
13
+
14
+ async calculateProductReviewStatistics(
11
15
  projectKey: string,
12
16
  productId: string,
13
- ): ReviewRatingStatistics | undefined {
17
+ ): Promise<ReviewRatingStatistics | undefined> {
18
+ // Fast path: if there are no reviews at all, skip the expensive load
19
+ const reviewCount = await this._storage.count(projectKey, "review");
20
+ if (reviewCount === 0) {
21
+ return undefined;
22
+ }
23
+
14
24
  // Get all reviews for this product
15
- const allReviews = this._storage.all(projectKey, "review") as Review[];
25
+ const allReviews = (await this._storage.all(
26
+ projectKey,
27
+ "review",
28
+ )) as Review[];
16
29
  const productReviews = allReviews.filter(
17
30
  (review) =>
18
31
  review.target?.typeId === "product" &&
@@ -1,8 +1,11 @@
1
- import type { Request } from "express";
1
+ import type { FastifyRequest } from "fastify";
2
2
 
3
- export const getBearerToken = (request: Request): string | undefined => {
4
- const authHeader = request.header("Authorization");
5
- const match = authHeader?.match(/^Bearer\s(?<token>[^\s]+)$/);
3
+ export const getBearerToken = (request: FastifyRequest): string | undefined => {
4
+ const authHeader = request.headers.authorization;
5
+ const normalizedAuthHeader = Array.isArray(authHeader)
6
+ ? authHeader[0]
7
+ : authHeader;
8
+ const match = normalizedAuthHeader?.match(/^Bearer\s(?<token>[^\s]+)$/);
6
9
  if (match) {
7
10
  return match.groups?.token;
8
11
  }