@mohasinac/appkit 3.0.6 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +42 -0
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +86 -0
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.d.ts +14 -0
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +46 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +28 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +17 -0
- package/dist/_internal/client/features/lottery/LotteryEntriesView.d.ts +23 -0
- package/dist/_internal/client/features/lottery/LotteryEntriesView.js +58 -0
- package/dist/_internal/client/features/lottery/LotteryListView.d.ts +21 -0
- package/dist/_internal/client/features/lottery/LotteryListView.js +25 -0
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +19 -0
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -0
- package/dist/_internal/client/features/lottery/LotterySlotGrid.d.ts +13 -0
- package/dist/_internal/client/features/lottery/LotterySlotGrid.js +21 -0
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.d.ts +28 -0
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +18 -0
- package/dist/_internal/server/features/auctions/__tests__/service.test.d.ts +1 -0
- package/dist/_internal/server/features/auctions/__tests__/service.test.js +140 -0
- package/dist/_internal/server/features/checkout/__tests__/actions.test.d.ts +1 -0
- package/dist/_internal/server/features/checkout/__tests__/actions.test.js +600 -0
- package/dist/_internal/server/features/lottery/actions.d.ts +43 -0
- package/dist/_internal/server/features/lottery/actions.js +243 -0
- package/dist/_internal/server/features/lottery/adapters.d.ts +9 -0
- package/dist/_internal/server/features/lottery/adapters.js +21 -0
- package/dist/_internal/server/features/lottery/data.d.ts +20 -0
- package/dist/_internal/server/features/lottery/data.js +41 -0
- package/dist/_internal/server/features/lottery/metadata.d.ts +6 -0
- package/dist/_internal/server/features/lottery/metadata.js +31 -0
- package/dist/_internal/server/features/refunds/__tests__/actions.test.d.ts +1 -0
- package/dist/_internal/server/features/refunds/__tests__/actions.test.js +263 -0
- package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.js +249 -0
- package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.js +201 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.js +169 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.js +148 -0
- package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.js +210 -0
- package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.js +190 -0
- package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.js +161 -0
- package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.js +302 -0
- package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.js +252 -0
- package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.js +129 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +46 -0
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/client.d.ts +9 -0
- package/dist/client.js +9 -0
- package/dist/constants/field-names.d.ts +5 -1
- package/dist/constants/field-names.js +5 -1
- package/dist/features/addresses/repository/__tests__/addresses.repository.test.d.ts +1 -0
- package/dist/features/addresses/repository/__tests__/addresses.repository.test.js +386 -0
- package/dist/features/admin/api/bids/route.d.ts +2 -2
- package/dist/features/admin/api/coupons/route.d.ts +2 -2
- package/dist/features/admin/api/products/route.d.ts +2 -2
- package/dist/features/admin/api/reviews/route.d.ts +2 -2
- package/dist/features/admin/repository/__tests__/notification.repository.test.d.ts +1 -0
- package/dist/features/admin/repository/__tests__/notification.repository.test.js +356 -0
- package/dist/features/admin/repository/__tests__/site-settings.repository.test.d.ts +1 -0
- package/dist/features/admin/repository/__tests__/site-settings.repository.test.js +298 -0
- package/dist/features/auctions/repository/__tests__/bid.repository.test.d.ts +1 -0
- package/dist/features/auctions/repository/__tests__/bid.repository.test.js +290 -0
- package/dist/features/auth/api/route.d.ts +2 -2
- package/dist/features/auth/repository/__tests__/session.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/session.repository.test.js +310 -0
- package/dist/features/auth/repository/__tests__/session.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/session.test.js +146 -0
- package/dist/features/auth/repository/__tests__/sms-counter.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/sms-counter.repository.test.js +171 -0
- package/dist/features/auth/repository/__tests__/sms-counter.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/sms-counter.test.js +88 -0
- package/dist/features/auth/repository/__tests__/token.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/token.repository.test.js +260 -0
- package/dist/features/auth/repository/__tests__/token.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/token.test.js +140 -0
- package/dist/features/auth/repository/__tests__/user.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/user.repository.test.js +352 -0
- package/dist/features/auth/repository/__tests__/user.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/user.test.js +226 -0
- package/dist/features/auth/repository/session.repository.js +10 -1
- package/dist/features/cart/repository/__tests__/cart.repository.test.d.ts +1 -0
- package/dist/features/cart/repository/__tests__/cart.repository.test.js +371 -0
- package/dist/features/cart/repository/cart.repository.js +2 -2
- package/dist/features/categories/api/[id]/route.d.ts +4 -4
- package/dist/features/categories/api/route.d.ts +2 -2
- package/dist/features/categories/repository/__tests__/categories.repository.test.d.ts +1 -0
- package/dist/features/categories/repository/__tests__/categories.repository.test.js +813 -0
- package/dist/features/events/components/EventCard.js +1 -0
- package/dist/features/events/repository/__tests__/event.repository.test.d.ts +1 -0
- package/dist/features/events/repository/__tests__/event.repository.test.js +299 -0
- package/dist/features/events/schemas/firestore.d.ts +3 -0
- package/dist/features/events/schemas/index.d.ts +834 -14
- package/dist/features/events/schemas/index.js +80 -1
- package/dist/features/events/types/index.d.ts +4 -1
- package/dist/features/faq/repository/__tests__/faqs.repository.test.d.ts +1 -0
- package/dist/features/faq/repository/__tests__/faqs.repository.test.js +311 -0
- package/dist/features/history/repository/__tests__/user-history.repository.test.d.ts +1 -0
- package/dist/features/history/repository/__tests__/user-history.repository.test.js +180 -0
- package/dist/features/homepage/api/[id]/route.d.ts +4 -4
- package/dist/features/homepage/api/carousel/[id]/route.d.ts +4 -4
- package/dist/features/homepage/api/carousel/route.d.ts +2 -2
- package/dist/features/homepage/api/route.d.ts +2 -2
- package/dist/features/homepage/repository/__tests__/carousel.repository.test.d.ts +1 -0
- package/dist/features/homepage/repository/__tests__/carousel.repository.test.js +182 -0
- package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.d.ts +1 -0
- package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.js +185 -0
- package/dist/features/lottery/repository/lottery-entry.repository.d.ts +33 -0
- package/dist/features/lottery/repository/lottery-entry.repository.js +130 -0
- package/dist/features/lottery/schemas/firestore.d.ts +49 -0
- package/dist/features/lottery/schemas/firestore.js +25 -0
- package/dist/features/lottery/schemas/zod.d.ts +113 -0
- package/dist/features/lottery/schemas/zod.js +39 -0
- package/dist/features/lottery/types/index.d.ts +67 -0
- package/dist/features/lottery/types/index.js +55 -0
- package/dist/features/messages/repository/__tests__/conversations.repository.test.d.ts +1 -0
- package/dist/features/messages/repository/__tests__/conversations.repository.test.js +257 -0
- package/dist/features/orders/repository/__tests__/orders.repository.test.d.ts +1 -0
- package/dist/features/orders/repository/__tests__/orders.repository.test.js +211 -0
- package/dist/features/payments/repository/__tests__/payout.repository.test.d.ts +1 -0
- package/dist/features/payments/repository/__tests__/payout.repository.test.js +292 -0
- package/dist/features/products/api/[id]/route.d.ts +4 -4
- package/dist/features/products/api/route.d.ts +2 -2
- package/dist/features/products/repository/__tests__/product-features.repository.test.d.ts +1 -0
- package/dist/features/products/repository/__tests__/product-features.repository.test.js +329 -0
- package/dist/features/products/repository/__tests__/products.repository.test.d.ts +1 -0
- package/dist/features/products/repository/__tests__/products.repository.test.js +607 -0
- package/dist/features/products/schemas/firestore.d.ts +15 -1
- package/dist/features/products/schemas/index.d.ts +12 -12
- package/dist/features/products/schemas/index.js +4 -2
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.d.ts +1 -0
- package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.js +219 -0
- package/dist/features/promotions/repository/__tests__/coupons.repository.test.d.ts +1 -0
- package/dist/features/promotions/repository/__tests__/coupons.repository.test.js +480 -0
- package/dist/features/reviews/api/[id]/route.d.ts +4 -4
- package/dist/features/reviews/api/route.d.ts +2 -2
- package/dist/features/reviews/repository/__tests__/reviews.repository.test.d.ts +1 -0
- package/dist/features/reviews/repository/__tests__/reviews.repository.test.js +327 -0
- package/dist/features/scams/repository/__tests__/scammer.repository.test.d.ts +1 -0
- package/dist/features/scams/repository/__tests__/scammer.repository.test.js +385 -0
- package/dist/features/search/repository/__tests__/search.repository.test.d.ts +1 -0
- package/dist/features/search/repository/__tests__/search.repository.test.js +369 -0
- package/dist/features/seller/api/coupons/route.d.ts +2 -2
- package/dist/features/seller/api/offers/route.d.ts +2 -2
- package/dist/features/seller/api/products/route.d.ts +2 -2
- package/dist/features/seller/api/store/route.d.ts +2 -2
- package/dist/features/seller/repository/__tests__/offer.repository.test.d.ts +1 -0
- package/dist/features/seller/repository/__tests__/offer.repository.test.js +458 -0
- package/dist/features/stores/repository/__tests__/store.repository.test.d.ts +1 -0
- package/dist/features/stores/repository/__tests__/store.repository.test.js +261 -0
- package/dist/features/support/repository/__tests__/support.repository.test.d.ts +1 -0
- package/dist/features/support/repository/__tests__/support.repository.test.js +312 -0
- package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.d.ts +1 -0
- package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.js +139 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +17 -0
- package/dist/next/api/routeHandler.d.ts +1 -1
- package/dist/next/routing/route-map.d.ts +13 -0
- package/dist/next/routing/route-map.js +6 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-schemas.d.ts +2 -0
- package/dist/security/pii-schemas.js +6 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/events-seed-data.js +85 -2
- package/dist/seed/grouped-listings-seed-data.d.ts +3 -1
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/lottery-entries-seed-data.d.ts +2 -0
- package/dist/seed/lottery-entries-seed-data.js +108 -0
- package/dist/seed/manifest.js +1 -0
- package/dist/seed/products-preorders-seed-data.d.ts +3 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.js +6 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- 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>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|