@mohasinac/appkit 3.0.6 → 3.1.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 (186) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +42 -0
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +86 -0
  3. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.d.ts +14 -0
  4. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +46 -0
  5. package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +28 -0
  6. package/dist/_internal/client/features/lottery/LotteryDetailView.js +17 -0
  7. package/dist/_internal/client/features/lottery/LotteryEntriesView.d.ts +23 -0
  8. package/dist/_internal/client/features/lottery/LotteryEntriesView.js +58 -0
  9. package/dist/_internal/client/features/lottery/LotteryListView.d.ts +21 -0
  10. package/dist/_internal/client/features/lottery/LotteryListView.js +25 -0
  11. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +19 -0
  12. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -0
  13. package/dist/_internal/client/features/lottery/LotterySlotGrid.d.ts +13 -0
  14. package/dist/_internal/client/features/lottery/LotterySlotGrid.js +21 -0
  15. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.d.ts +28 -0
  16. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +18 -0
  17. package/dist/_internal/server/features/auctions/__tests__/service.test.d.ts +1 -0
  18. package/dist/_internal/server/features/auctions/__tests__/service.test.js +140 -0
  19. package/dist/_internal/server/features/checkout/__tests__/actions.test.d.ts +1 -0
  20. package/dist/_internal/server/features/checkout/__tests__/actions.test.js +600 -0
  21. package/dist/_internal/server/features/lottery/actions.d.ts +43 -0
  22. package/dist/_internal/server/features/lottery/actions.js +243 -0
  23. package/dist/_internal/server/features/lottery/adapters.d.ts +9 -0
  24. package/dist/_internal/server/features/lottery/adapters.js +21 -0
  25. package/dist/_internal/server/features/lottery/data.d.ts +20 -0
  26. package/dist/_internal/server/features/lottery/data.js +41 -0
  27. package/dist/_internal/server/features/lottery/metadata.d.ts +6 -0
  28. package/dist/_internal/server/features/lottery/metadata.js +31 -0
  29. package/dist/_internal/server/features/refunds/__tests__/actions.test.d.ts +1 -0
  30. package/dist/_internal/server/features/refunds/__tests__/actions.test.js +263 -0
  31. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.d.ts +1 -0
  32. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.js +249 -0
  33. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.d.ts +1 -0
  34. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.js +201 -0
  35. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.d.ts +1 -0
  36. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.js +169 -0
  37. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.d.ts +1 -0
  38. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.js +148 -0
  39. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.d.ts +1 -0
  40. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.js +210 -0
  41. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.d.ts +1 -0
  42. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.js +190 -0
  43. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.d.ts +1 -0
  44. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.js +161 -0
  45. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.d.ts +1 -0
  46. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.js +302 -0
  47. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.d.ts +1 -0
  48. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.js +252 -0
  49. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.d.ts +1 -0
  50. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.js +129 -0
  51. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  52. package/dist/_internal/shared/actions/action-registry.js +46 -0
  53. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  54. package/dist/client.d.ts +9 -0
  55. package/dist/client.js +9 -0
  56. package/dist/constants/field-names.d.ts +5 -1
  57. package/dist/constants/field-names.js +5 -1
  58. package/dist/features/addresses/repository/__tests__/addresses.repository.test.d.ts +1 -0
  59. package/dist/features/addresses/repository/__tests__/addresses.repository.test.js +386 -0
  60. package/dist/features/admin/api/bids/route.d.ts +2 -2
  61. package/dist/features/admin/api/coupons/route.d.ts +2 -2
  62. package/dist/features/admin/api/products/route.d.ts +2 -2
  63. package/dist/features/admin/api/reviews/route.d.ts +2 -2
  64. package/dist/features/admin/repository/__tests__/notification.repository.test.d.ts +1 -0
  65. package/dist/features/admin/repository/__tests__/notification.repository.test.js +356 -0
  66. package/dist/features/admin/repository/__tests__/site-settings.repository.test.d.ts +1 -0
  67. package/dist/features/admin/repository/__tests__/site-settings.repository.test.js +298 -0
  68. package/dist/features/auctions/repository/__tests__/bid.repository.test.d.ts +1 -0
  69. package/dist/features/auctions/repository/__tests__/bid.repository.test.js +290 -0
  70. package/dist/features/auth/api/route.d.ts +2 -2
  71. package/dist/features/auth/repository/__tests__/session.repository.test.d.ts +1 -0
  72. package/dist/features/auth/repository/__tests__/session.repository.test.js +310 -0
  73. package/dist/features/auth/repository/__tests__/session.test.d.ts +1 -0
  74. package/dist/features/auth/repository/__tests__/session.test.js +146 -0
  75. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.d.ts +1 -0
  76. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.js +171 -0
  77. package/dist/features/auth/repository/__tests__/sms-counter.test.d.ts +1 -0
  78. package/dist/features/auth/repository/__tests__/sms-counter.test.js +88 -0
  79. package/dist/features/auth/repository/__tests__/token.repository.test.d.ts +1 -0
  80. package/dist/features/auth/repository/__tests__/token.repository.test.js +260 -0
  81. package/dist/features/auth/repository/__tests__/token.test.d.ts +1 -0
  82. package/dist/features/auth/repository/__tests__/token.test.js +140 -0
  83. package/dist/features/auth/repository/__tests__/user.repository.test.d.ts +1 -0
  84. package/dist/features/auth/repository/__tests__/user.repository.test.js +352 -0
  85. package/dist/features/auth/repository/__tests__/user.test.d.ts +1 -0
  86. package/dist/features/auth/repository/__tests__/user.test.js +226 -0
  87. package/dist/features/auth/repository/session.repository.js +10 -1
  88. package/dist/features/cart/repository/__tests__/cart.repository.test.d.ts +1 -0
  89. package/dist/features/cart/repository/__tests__/cart.repository.test.js +371 -0
  90. package/dist/features/cart/repository/cart.repository.js +2 -2
  91. package/dist/features/categories/api/[id]/route.d.ts +4 -4
  92. package/dist/features/categories/api/route.d.ts +2 -2
  93. package/dist/features/categories/repository/__tests__/categories.repository.test.d.ts +1 -0
  94. package/dist/features/categories/repository/__tests__/categories.repository.test.js +813 -0
  95. package/dist/features/events/components/EventCard.js +1 -0
  96. package/dist/features/events/repository/__tests__/event.repository.test.d.ts +1 -0
  97. package/dist/features/events/repository/__tests__/event.repository.test.js +299 -0
  98. package/dist/features/events/schemas/firestore.d.ts +3 -0
  99. package/dist/features/events/schemas/index.d.ts +834 -14
  100. package/dist/features/events/schemas/index.js +80 -1
  101. package/dist/features/events/types/index.d.ts +4 -1
  102. package/dist/features/faq/repository/__tests__/faqs.repository.test.d.ts +1 -0
  103. package/dist/features/faq/repository/__tests__/faqs.repository.test.js +311 -0
  104. package/dist/features/history/repository/__tests__/user-history.repository.test.d.ts +1 -0
  105. package/dist/features/history/repository/__tests__/user-history.repository.test.js +180 -0
  106. package/dist/features/homepage/api/[id]/route.d.ts +4 -4
  107. package/dist/features/homepage/api/carousel/[id]/route.d.ts +4 -4
  108. package/dist/features/homepage/api/carousel/route.d.ts +2 -2
  109. package/dist/features/homepage/api/route.d.ts +2 -2
  110. package/dist/features/homepage/repository/__tests__/carousel.repository.test.d.ts +1 -0
  111. package/dist/features/homepage/repository/__tests__/carousel.repository.test.js +182 -0
  112. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.d.ts +1 -0
  113. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.js +185 -0
  114. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +33 -0
  115. package/dist/features/lottery/repository/lottery-entry.repository.js +130 -0
  116. package/dist/features/lottery/schemas/firestore.d.ts +49 -0
  117. package/dist/features/lottery/schemas/firestore.js +25 -0
  118. package/dist/features/lottery/schemas/zod.d.ts +113 -0
  119. package/dist/features/lottery/schemas/zod.js +39 -0
  120. package/dist/features/lottery/types/index.d.ts +67 -0
  121. package/dist/features/lottery/types/index.js +55 -0
  122. package/dist/features/messages/repository/__tests__/conversations.repository.test.d.ts +1 -0
  123. package/dist/features/messages/repository/__tests__/conversations.repository.test.js +257 -0
  124. package/dist/features/orders/repository/__tests__/orders.repository.test.d.ts +1 -0
  125. package/dist/features/orders/repository/__tests__/orders.repository.test.js +211 -0
  126. package/dist/features/payments/repository/__tests__/payout.repository.test.d.ts +1 -0
  127. package/dist/features/payments/repository/__tests__/payout.repository.test.js +292 -0
  128. package/dist/features/products/api/[id]/route.d.ts +4 -4
  129. package/dist/features/products/api/route.d.ts +2 -2
  130. package/dist/features/products/repository/__tests__/product-features.repository.test.d.ts +1 -0
  131. package/dist/features/products/repository/__tests__/product-features.repository.test.js +329 -0
  132. package/dist/features/products/repository/__tests__/products.repository.test.d.ts +1 -0
  133. package/dist/features/products/repository/__tests__/products.repository.test.js +607 -0
  134. package/dist/features/products/schemas/firestore.d.ts +15 -1
  135. package/dist/features/products/schemas/index.d.ts +12 -12
  136. package/dist/features/products/schemas/index.js +4 -2
  137. package/dist/features/products/types/index.d.ts +1 -1
  138. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.d.ts +1 -0
  139. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.js +219 -0
  140. package/dist/features/promotions/repository/__tests__/coupons.repository.test.d.ts +1 -0
  141. package/dist/features/promotions/repository/__tests__/coupons.repository.test.js +480 -0
  142. package/dist/features/reviews/api/[id]/route.d.ts +4 -4
  143. package/dist/features/reviews/api/route.d.ts +2 -2
  144. package/dist/features/reviews/repository/__tests__/reviews.repository.test.d.ts +1 -0
  145. package/dist/features/reviews/repository/__tests__/reviews.repository.test.js +327 -0
  146. package/dist/features/scams/repository/__tests__/scammer.repository.test.d.ts +1 -0
  147. package/dist/features/scams/repository/__tests__/scammer.repository.test.js +385 -0
  148. package/dist/features/search/repository/__tests__/search.repository.test.d.ts +1 -0
  149. package/dist/features/search/repository/__tests__/search.repository.test.js +369 -0
  150. package/dist/features/seller/api/coupons/route.d.ts +2 -2
  151. package/dist/features/seller/api/offers/route.d.ts +2 -2
  152. package/dist/features/seller/api/products/route.d.ts +2 -2
  153. package/dist/features/seller/api/store/route.d.ts +2 -2
  154. package/dist/features/seller/repository/__tests__/offer.repository.test.d.ts +1 -0
  155. package/dist/features/seller/repository/__tests__/offer.repository.test.js +458 -0
  156. package/dist/features/stores/repository/__tests__/store.repository.test.d.ts +1 -0
  157. package/dist/features/stores/repository/__tests__/store.repository.test.js +261 -0
  158. package/dist/features/support/repository/__tests__/support.repository.test.d.ts +1 -0
  159. package/dist/features/support/repository/__tests__/support.repository.test.js +312 -0
  160. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.d.ts +1 -0
  161. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.js +139 -0
  162. package/dist/index.d.ts +14 -0
  163. package/dist/index.js +17 -0
  164. package/dist/next/api/routeHandler.d.ts +1 -1
  165. package/dist/next/routing/route-map.d.ts +13 -0
  166. package/dist/next/routing/route-map.js +6 -0
  167. package/dist/repositories/index.d.ts +1 -0
  168. package/dist/repositories/index.js +2 -0
  169. package/dist/security/index.d.ts +1 -1
  170. package/dist/security/index.js +1 -1
  171. package/dist/security/pii-schemas.d.ts +2 -0
  172. package/dist/security/pii-schemas.js +6 -0
  173. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  174. package/dist/seed/events-seed-data.js +85 -2
  175. package/dist/seed/grouped-listings-seed-data.d.ts +3 -1
  176. package/dist/seed/index.d.ts +1 -0
  177. package/dist/seed/index.js +1 -0
  178. package/dist/seed/lottery-entries-seed-data.d.ts +2 -0
  179. package/dist/seed/lottery-entries-seed-data.js +108 -0
  180. package/dist/seed/manifest.js +1 -0
  181. package/dist/seed/products-preorders-seed-data.d.ts +3 -1
  182. package/dist/server.d.ts +6 -0
  183. package/dist/server.js +6 -0
  184. package/dist/styles.css +1 -1
  185. package/dist/tailwind-utilities.css +1 -1
  186. package/package.json +1 -1
@@ -0,0 +1,371 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { makeMockDb, makeSnap } from "../../../../../tests/helpers/mock-firestore";
3
+ const { db, mockDocRef, mockCollection, mockQuery } = makeMockDb();
4
+ vi.mock("../../../../providers/db-firebase/admin", () => ({
5
+ getAdminDb: () => db,
6
+ }));
7
+ vi.mock("../../../../providers/db-firebase", async (importOriginal) => {
8
+ const actual = await importOriginal();
9
+ return {
10
+ ...actual,
11
+ prepareForFirestore: (d) => d,
12
+ };
13
+ });
14
+ vi.mock("../../../../errors/normalize", () => ({
15
+ normalizeError: vi.fn(),
16
+ }));
17
+ vi.mock("../../../../security", () => ({
18
+ decryptPii: (v) => v,
19
+ encryptPii: (v) => v,
20
+ }));
21
+ import { CartRepository } from "../cart.repository";
22
+ import { ValidationError, NotFoundError } from "../../../../errors";
23
+ const repo = new CartRepository();
24
+ function makeItem(overrides = {}) {
25
+ return {
26
+ itemId: "item-1",
27
+ productId: "product-x",
28
+ productTitle: "Test Product",
29
+ productImage: "img.jpg",
30
+ price: 1000,
31
+ currency: "INR",
32
+ quantity: 1,
33
+ storeId: "store-1",
34
+ storeName: "Store",
35
+ listingType: "standard",
36
+ locked: false,
37
+ addedAt: new Date(),
38
+ updatedAt: new Date(),
39
+ ...overrides,
40
+ };
41
+ }
42
+ function makeCart(overrides = {}) {
43
+ return {
44
+ id: "user-1",
45
+ userId: "user-1",
46
+ items: [],
47
+ appliedCoupons: [],
48
+ createdAt: new Date(),
49
+ updatedAt: new Date(),
50
+ ...overrides,
51
+ };
52
+ }
53
+ function makeAddInput(overrides = {}) {
54
+ return {
55
+ productId: "product-new",
56
+ productTitle: "New Product",
57
+ productImage: "new.jpg",
58
+ price: 500,
59
+ currency: "INR",
60
+ quantity: 1,
61
+ storeId: "store-1",
62
+ storeName: "Store",
63
+ listingType: "standard",
64
+ ...overrides,
65
+ };
66
+ }
67
+ beforeEach(() => {
68
+ vi.clearAllMocks();
69
+ db.collection.mockReturnValue(mockCollection);
70
+ mockCollection.doc.mockReturnValue(mockDocRef);
71
+ mockCollection.where.mockReturnValue(mockQuery);
72
+ mockQuery.where.mockReturnValue(mockQuery);
73
+ mockQuery.limit.mockReturnValue(mockQuery);
74
+ mockQuery.get.mockResolvedValue({ docs: [], empty: true });
75
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
76
+ mockDocRef.set.mockResolvedValue(undefined);
77
+ mockDocRef.update.mockResolvedValue(undefined);
78
+ });
79
+ // ---------------------------------------------------------------------------
80
+ // getOrCreate
81
+ // ---------------------------------------------------------------------------
82
+ describe("CartRepository.getOrCreate", () => {
83
+ it("existing cart → returns it without writing", async () => {
84
+ const cart = makeCart({ items: [makeItem()] });
85
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
86
+ const result = await repo.getOrCreate("user-1");
87
+ expect(result.id).toBe("user-1");
88
+ expect(mockDocRef.set).not.toHaveBeenCalled();
89
+ });
90
+ it("no cart → creates empty cart and returns it", async () => {
91
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
92
+ const result = await repo.getOrCreate("user-2");
93
+ expect(result.items).toHaveLength(0);
94
+ expect(mockDocRef.set).toHaveBeenCalledOnce();
95
+ });
96
+ it("created cart has userId and id set", async () => {
97
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
98
+ const result = await repo.getOrCreate("user-3");
99
+ expect(result.userId).toBe("user-3");
100
+ expect(result.id).toBe("user-3");
101
+ });
102
+ });
103
+ // ---------------------------------------------------------------------------
104
+ // addItem
105
+ // ---------------------------------------------------------------------------
106
+ describe("CartRepository.addItem", () => {
107
+ it("new productId → appended to items array", async () => {
108
+ const cart = makeCart({ items: [] });
109
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
110
+ const result = await repo.addItem("user-1", makeAddInput({ productId: "product-a" }));
111
+ expect(result.items).toHaveLength(1);
112
+ expect(result.items[0].productId).toBe("product-a");
113
+ });
114
+ it("duplicate productId (no offerId) → quantity merged, no duplicate item", async () => {
115
+ const cart = makeCart({ items: [makeItem({ productId: "product-x", quantity: 2 })] });
116
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
117
+ const result = await repo.addItem("user-1", makeAddInput({ productId: "product-x", quantity: 3 }));
118
+ expect(result.items).toHaveLength(1);
119
+ expect(result.items[0].quantity).toBe(5);
120
+ });
121
+ it("duplicate productId with offerId → treated as separate item", async () => {
122
+ const cart = makeCart({
123
+ items: [makeItem({ productId: "product-x", offerId: "offer-1" })],
124
+ });
125
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
126
+ const result = await repo.addItem("user-1", makeAddInput({ productId: "product-x", offerId: "offer-2" }));
127
+ expect(result.items).toHaveLength(2);
128
+ });
129
+ it("same offerId already in cart → idempotent, cart unchanged", async () => {
130
+ const cart = makeCart({
131
+ items: [makeItem({ productId: "product-x", offerId: "offer-1" })],
132
+ });
133
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
134
+ const result = await repo.addItem("user-1", makeAddInput({ productId: "product-x", offerId: "offer-1" }));
135
+ expect(result.items).toHaveLength(1);
136
+ expect(mockDocRef.set).not.toHaveBeenCalled();
137
+ });
138
+ it("stores bundleCategorySlug and bundleProductIds when present", async () => {
139
+ const cart = makeCart({ items: [] });
140
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
141
+ const result = await repo.addItem("user-1", makeAddInput({
142
+ bundleCategorySlug: "category-pokemon",
143
+ bundleProductIds: ["product-a", "product-b"],
144
+ }));
145
+ expect(result.items[0].bundleCategorySlug).toBe("category-pokemon");
146
+ expect(result.items[0].bundleProductIds).toEqual(["product-a", "product-b"]);
147
+ });
148
+ it("assigns a random itemId to new items", async () => {
149
+ const cart = makeCart({ items: [] });
150
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
151
+ const result = await repo.addItem("user-1", makeAddInput());
152
+ expect(result.items[0].itemId).toBeTruthy();
153
+ expect(typeof result.items[0].itemId).toBe("string");
154
+ });
155
+ });
156
+ // ---------------------------------------------------------------------------
157
+ // removeItem
158
+ // ---------------------------------------------------------------------------
159
+ describe("CartRepository.removeItem", () => {
160
+ it("unlocked item → removed, remaining items intact", async () => {
161
+ const item1 = makeItem({ itemId: "item-1", productId: "p1" });
162
+ const item2 = makeItem({ itemId: "item-2", productId: "p2" });
163
+ const cart = makeCart({ items: [item1, item2] });
164
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
165
+ const result = await repo.removeItem("user-1", "item-1");
166
+ expect(result.items).toHaveLength(1);
167
+ expect(result.items[0].itemId).toBe("item-2");
168
+ });
169
+ it("locked item → throws ValidationError", async () => {
170
+ const lockedItem = makeItem({ itemId: "item-locked", locked: true });
171
+ const cart = makeCart({ items: [lockedItem] });
172
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
173
+ await expect(repo.removeItem("user-1", "item-locked")).rejects.toBeInstanceOf(ValidationError);
174
+ });
175
+ it("non-existent itemId → throws NotFoundError", async () => {
176
+ const cart = makeCart({ items: [makeItem()] });
177
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
178
+ await expect(repo.removeItem("user-1", "item-nonexistent")).rejects.toBeInstanceOf(NotFoundError);
179
+ });
180
+ });
181
+ // ---------------------------------------------------------------------------
182
+ // updateItem
183
+ // ---------------------------------------------------------------------------
184
+ describe("CartRepository.updateItem", () => {
185
+ it("unlocked item → quantity updated", async () => {
186
+ const item = makeItem({ itemId: "item-1", quantity: 1 });
187
+ const cart = makeCart({ items: [item] });
188
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
189
+ const result = await repo.updateItem("user-1", "item-1", { quantity: 5 });
190
+ expect(result.items[0].quantity).toBe(5);
191
+ });
192
+ it("locked item → throws ValidationError", async () => {
193
+ const item = makeItem({ itemId: "item-1", locked: true });
194
+ const cart = makeCart({ items: [item] });
195
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
196
+ await expect(repo.updateItem("user-1", "item-1", { quantity: 5 })).rejects.toBeInstanceOf(ValidationError);
197
+ });
198
+ it("non-existent itemId → throws NotFoundError", async () => {
199
+ const cart = makeCart({ items: [] });
200
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
201
+ await expect(repo.updateItem("user-1", "item-x", { quantity: 1 })).rejects.toBeInstanceOf(NotFoundError);
202
+ });
203
+ });
204
+ // ---------------------------------------------------------------------------
205
+ // clearCart
206
+ // ---------------------------------------------------------------------------
207
+ describe("CartRepository.clearCart", () => {
208
+ it("unlocked items removed, locked items remain", async () => {
209
+ const unlocked = makeItem({ itemId: "item-u", locked: false });
210
+ const locked = makeItem({ itemId: "item-l", locked: true });
211
+ const cart = makeCart({ items: [unlocked, locked] });
212
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
213
+ const result = await repo.clearCart("user-1");
214
+ expect(result.items).toHaveLength(1);
215
+ expect(result.items[0].itemId).toBe("item-l");
216
+ });
217
+ it("no locked items → items cleared to empty", async () => {
218
+ const cart = makeCart({ items: [makeItem({ locked: false })] });
219
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
220
+ const result = await repo.clearCart("user-1");
221
+ expect(result.items).toHaveLength(0);
222
+ });
223
+ });
224
+ // ---------------------------------------------------------------------------
225
+ // getSubtotal
226
+ // ---------------------------------------------------------------------------
227
+ describe("CartRepository.getSubtotal", () => {
228
+ it("single item: price * quantity", () => {
229
+ const cart = makeCart({ items: [makeItem({ price: 200, quantity: 3 })] });
230
+ expect(repo.getSubtotal(cart)).toBe(600);
231
+ });
232
+ it("multiple items: summed correctly", () => {
233
+ const cart = makeCart({
234
+ items: [
235
+ makeItem({ price: 1000, quantity: 1 }),
236
+ makeItem({ itemId: "item-2", productId: "p2", price: 500, quantity: 2 }),
237
+ ],
238
+ });
239
+ expect(repo.getSubtotal(cart)).toBe(2000);
240
+ });
241
+ it("empty cart → 0", () => {
242
+ expect(repo.getSubtotal(makeCart())).toBe(0);
243
+ });
244
+ it("uses item.price (not lockedPrice) in the calculation", () => {
245
+ const item = makeItem({ price: 1000, quantity: 1, lockedPrice: 750 });
246
+ const cart = makeCart({ items: [item] });
247
+ // current implementation uses price, not lockedPrice
248
+ expect(repo.getSubtotal(cart)).toBe(1000);
249
+ });
250
+ });
251
+ // ---------------------------------------------------------------------------
252
+ // getItemCount
253
+ // ---------------------------------------------------------------------------
254
+ describe("CartRepository.getItemCount", () => {
255
+ it("returns sum of all item quantities (not distinct item count)", () => {
256
+ const cart = makeCart({
257
+ items: [
258
+ makeItem({ quantity: 3 }),
259
+ makeItem({ itemId: "item-2", productId: "p2", quantity: 2 }),
260
+ ],
261
+ });
262
+ // 3 + 2 = 5 total quantity units
263
+ expect(repo.getItemCount(cart)).toBe(5);
264
+ });
265
+ it("single item qty 1 → 1", () => {
266
+ const cart = makeCart({ items: [makeItem({ quantity: 1 })] });
267
+ expect(repo.getItemCount(cart)).toBe(1);
268
+ });
269
+ it("empty cart → 0", () => {
270
+ expect(repo.getItemCount(makeCart())).toBe(0);
271
+ });
272
+ });
273
+ // ---------------------------------------------------------------------------
274
+ // addCoupon / removeCoupon / clearAllCoupons
275
+ // ---------------------------------------------------------------------------
276
+ describe("CartRepository.addCoupon", () => {
277
+ it("stores coupon in appliedCoupons", async () => {
278
+ const cart = makeCart({ appliedCoupons: [] });
279
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
280
+ await repo.addCoupon("user-1", { code: "SAVE10", scope: "admin", discountAmount: 100 });
281
+ expect(mockDocRef.set).toHaveBeenCalledWith(expect.objectContaining({
282
+ appliedCoupons: expect.arrayContaining([
283
+ expect.objectContaining({ code: "SAVE10" }),
284
+ ]),
285
+ }), { merge: true });
286
+ });
287
+ it("same code already applied → replaces it (upsert)", async () => {
288
+ const cart = makeCart({
289
+ appliedCoupons: [{ code: "SAVE10", scope: "admin", discountAmount: 50 }],
290
+ });
291
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
292
+ await repo.addCoupon("user-1", { code: "SAVE10", scope: "admin", discountAmount: 100 });
293
+ const setCall = mockDocRef.set.mock.calls[0][0];
294
+ const codes = setCall.appliedCoupons.map((c) => c.code);
295
+ expect(codes.filter((c) => c === "SAVE10")).toHaveLength(1);
296
+ expect(setCall.appliedCoupons[0].discountAmount).toBe(100);
297
+ });
298
+ });
299
+ describe("CartRepository.removeCoupon", () => {
300
+ it("removes coupon matching given code, leaves others", async () => {
301
+ const cart = makeCart({
302
+ appliedCoupons: [
303
+ { code: "SAVE10", scope: "admin" },
304
+ { code: "STORE5", scope: "seller" },
305
+ ],
306
+ });
307
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
308
+ await repo.removeCoupon("user-1", "SAVE10");
309
+ const setCall = mockDocRef.set.mock.calls[0][0];
310
+ expect(setCall.appliedCoupons).toHaveLength(1);
311
+ expect(setCall.appliedCoupons[0].code).toBe("STORE5");
312
+ });
313
+ it("code not present → no change, no error", async () => {
314
+ const cart = makeCart({ appliedCoupons: [{ code: "STORE5", scope: "seller" }] });
315
+ mockDocRef.get.mockResolvedValue(makeSnap(cart, "user-1"));
316
+ await repo.removeCoupon("user-1", "NOTHERE");
317
+ const setCall = mockDocRef.set.mock.calls[0][0];
318
+ expect(setCall.appliedCoupons).toHaveLength(1);
319
+ });
320
+ });
321
+ describe("CartRepository.clearAllCoupons", () => {
322
+ it("sets appliedCoupons: []", async () => {
323
+ await repo.clearAllCoupons("user-1");
324
+ expect(mockDocRef.set).toHaveBeenCalledWith(expect.objectContaining({ appliedCoupons: [] }), { merge: true });
325
+ });
326
+ });
327
+ // ---------------------------------------------------------------------------
328
+ // setSelectedItems
329
+ // ---------------------------------------------------------------------------
330
+ describe("CartRepository.setSelectedItems", () => {
331
+ it("stores itemIds array in selectedItemIds", async () => {
332
+ await repo.setSelectedItems("user-1", ["item-1", "item-2"]);
333
+ expect(mockDocRef.set).toHaveBeenCalledWith(expect.objectContaining({ selectedItemIds: ["item-1", "item-2"] }), { merge: true });
334
+ });
335
+ it("null → clears selectedItemIds (all items selected)", async () => {
336
+ await repo.setSelectedItems("user-1", null);
337
+ expect(mockDocRef.set).toHaveBeenCalledWith(expect.objectContaining({ selectedItemIds: null }), { merge: true });
338
+ });
339
+ });
340
+ // ---------------------------------------------------------------------------
341
+ // getStaleRefs
342
+ // ---------------------------------------------------------------------------
343
+ describe("CartRepository.getStaleRefs", () => {
344
+ it("queries where updatedAt < cutoff date", async () => {
345
+ mockQuery.get.mockResolvedValue({ docs: [] });
346
+ await repo.getStaleRefs(30);
347
+ expect(mockCollection.where).toHaveBeenCalledWith("updatedAt", "<", expect.any(Date));
348
+ });
349
+ it("default ttl is 30 days", async () => {
350
+ const before = new Date();
351
+ before.setDate(before.getDate() - 30);
352
+ mockQuery.get.mockResolvedValue({ docs: [] });
353
+ await repo.getStaleRefs();
354
+ const whereCall = mockCollection.where.mock.calls[0];
355
+ const cutoff = whereCall[2];
356
+ expect(cutoff.getTime()).toBeLessThanOrEqual(new Date().getTime());
357
+ expect(cutoff.getTime()).toBeGreaterThanOrEqual(before.getTime() - 1000);
358
+ });
359
+ it("returns refs for stale carts", async () => {
360
+ const fakeRef = { id: "old-cart" };
361
+ mockQuery.get.mockResolvedValue({ docs: [{ ref: fakeRef }] });
362
+ const refs = await repo.getStaleRefs(30);
363
+ expect(refs).toHaveLength(1);
364
+ expect(refs[0]).toBe(fakeRef);
365
+ });
366
+ it("no stale carts → empty array", async () => {
367
+ mockQuery.get.mockResolvedValue({ docs: [] });
368
+ const refs = await repo.getStaleRefs(30);
369
+ expect(refs).toHaveLength(0);
370
+ });
371
+ });
@@ -151,7 +151,7 @@ export class CartRepository extends BaseRepository {
151
151
  return updatedCart;
152
152
  }
153
153
  catch (error) {
154
- if (error instanceof DatabaseError || error instanceof NotFoundError) {
154
+ if (error instanceof DatabaseError || error instanceof NotFoundError || error instanceof ValidationError) {
155
155
  throw error;
156
156
  }
157
157
  throw new DatabaseError("Failed to update cart item", error);
@@ -180,7 +180,7 @@ export class CartRepository extends BaseRepository {
180
180
  return updatedCart;
181
181
  }
182
182
  catch (error) {
183
- if (error instanceof DatabaseError || error instanceof NotFoundError) {
183
+ if (error instanceof DatabaseError || error instanceof NotFoundError || error instanceof ValidationError) {
184
184
  throw error;
185
185
  }
186
186
  throw new DatabaseError("Failed to remove cart item", error);
@@ -15,9 +15,9 @@ export declare function GET(_request: Request, context: {
15
15
  }>;
16
16
  }): Promise<NextResponse>;
17
17
  export { GET as categoryItemGET };
18
- export declare const categoryItemPATCH: (request: Request, context: {
18
+ export declare const categoryItemPATCH: (request: Request, context?: {
19
19
  params: Promise<Record<string, string>>;
20
- }) => Promise<Response>;
21
- export declare const categoryItemDELETE: (request: Request, context: {
20
+ } | undefined) => Promise<Response>;
21
+ export declare const categoryItemDELETE: (request: Request, context?: {
22
22
  params: Promise<Record<string, string>>;
23
- }) => Promise<Response>;
23
+ } | undefined) => Promise<Response>;
@@ -14,6 +14,6 @@
14
14
  */
15
15
  import { NextResponse } from "next/server.js";
16
16
  export declare function GET(request: Request): Promise<NextResponse>;
17
- export declare const POST: (request: Request, context: {
17
+ export declare const POST: (request: Request, context?: {
18
18
  params: Promise<Record<string, string>>;
19
- }) => Promise<Response>;
19
+ } | undefined) => Promise<Response>;