@mohasinac/appkit 3.0.4 → 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.
- 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/auctions/data.d.ts +2 -4
- package/dist/_internal/server/features/auctions/data.js +3 -9
- 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/classified/adapters.d.ts +2 -15
- package/dist/_internal/server/features/classified/adapters.js +2 -17
- package/dist/_internal/server/features/classified/data.d.ts +1 -6
- package/dist/_internal/server/features/classified/data.js +2 -10
- package/dist/_internal/server/features/digital-code/adapters.d.ts +2 -15
- package/dist/_internal/server/features/digital-code/adapters.js +2 -17
- package/dist/_internal/server/features/digital-code/data.d.ts +1 -6
- package/dist/_internal/server/features/digital-code/data.js +2 -10
- package/dist/_internal/server/features/live/adapters.d.ts +2 -15
- package/dist/_internal/server/features/live/adapters.js +2 -17
- package/dist/_internal/server/features/live/data.d.ts +1 -6
- package/dist/_internal/server/features/live/data.js +2 -10
- 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/pre-orders/data.d.ts +2 -4
- package/dist/_internal/server/features/pre-orders/data.js +3 -9
- package/dist/_internal/server/features/prize-draws/actions.d.ts +2 -0
- package/dist/_internal/server/features/prize-draws/actions.js +22 -0
- package/dist/_internal/server/features/prize-draws/data.d.ts +1 -17
- package/dist/_internal/server/features/prize-draws/data.js +2 -20
- package/dist/_internal/server/features/prize-draws/index.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/index.js +2 -0
- package/dist/_internal/server/features/prize-draws/service.d.ts +3 -0
- package/dist/_internal/server/features/prize-draws/service.js +19 -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/features/shared/listing-adapters.d.ts +19 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +20 -0
- package/dist/_internal/server/features/shared/listing-data-factory.d.ts +9 -0
- package/dist/_internal/server/features/shared/listing-data-factory.js +33 -0
- package/dist/_internal/server/features/stores/data.d.ts +3 -14
- package/dist/_internal/server/features/stores/data.js +6 -44
- 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/cart/schema.d.ts +8 -8
- package/dist/_internal/shared/features/cart/schema.js +3 -0
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/_internal/shared/listing-types/_registry.js +2 -0
- package/dist/_internal/shared/listing-types/auction/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/auction/schema.js +7 -1
- package/dist/_internal/shared/listing-types/classified/schema.d.ts +2 -1
- package/dist/_internal/shared/listing-types/classified/schema.js +3 -1
- package/dist/_internal/shared/listing-types/digital-code/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/digital-code/schema.js +7 -1
- package/dist/_internal/shared/listing-types/live/schema.d.ts +2 -1
- package/dist/_internal/shared/listing-types/live/schema.js +3 -1
- package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/pre-order/schema.js +7 -1
- package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/prize-draw/schema.js +13 -1
- package/dist/_internal/shared/listing-types/standard/schema.d.ts +2 -1
- package/dist/_internal/shared/listing-types/standard/schema.js +4 -1
- package/dist/_internal/shared/types/base-document.d.ts +15 -0
- package/dist/_internal/shared/types/base-document.js +1 -0
- package/dist/_internal/shared/types/index.d.ts +2 -0
- package/dist/_internal/shared/types/index.js +1 -0
- package/dist/client.d.ts +13 -4
- package/dist/client.js +11 -2
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +4 -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/addresses/schemas/firestore.d.ts +2 -4
- 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/components/AdminFulfillmentView.d.ts +6 -0
- package/dist/features/admin/components/AdminFulfillmentView.js +107 -0
- package/dist/features/admin/components/AdminProductEditorView.js +21 -9
- package/dist/features/admin/components/AdminProductsView.js +3 -0
- package/dist/features/admin/components/DataListingView.d.ts +1 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- 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/admin/schemas/firestore.d.ts +3 -8
- 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/auctions/schemas/firestore.d.ts +2 -4
- package/dist/features/auctions/schemas/index.d.ts +2 -2
- 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/before-after/schemas/index.d.ts +2 -2
- package/dist/features/blog/schemas/firestore.d.ts +2 -4
- package/dist/features/blog/schemas/index.d.ts +4 -4
- 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/cart/schemas/firestore.d.ts +2 -4
- 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/categories/schemas/firestore.d.ts +2 -4
- package/dist/features/categories/schemas/index.d.ts +4 -4
- 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 +5 -4
- package/dist/features/events/schemas/index.d.ts +836 -16
- 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/faq/schemas/firestore.d.ts +2 -4
- package/dist/features/faq/schemas/index.d.ts +2 -2
- package/dist/features/grouped/schemas/index.d.ts +2 -2
- 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/homepage/schemas/firestore.d.ts +4 -12
- package/dist/features/homepage/schemas/index.d.ts +2 -2
- 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/messages/schemas/firestore.d.ts +2 -4
- package/dist/features/messages/schemas/index.d.ts +2 -2
- 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/orders/repository/orders.repository.d.ts +5 -0
- package/dist/features/orders/repository/orders.repository.js +26 -0
- package/dist/features/orders/schemas/firestore.d.ts +9 -5
- package/dist/features/orders/schemas/firestore.js +3 -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/payments/schemas/firestore.d.ts +2 -4
- package/dist/features/pre-orders/schemas/index.d.ts +2 -2
- 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/constants/action-defs.d.ts +16 -0
- package/dist/features/products/constants/action-defs.js +16 -0
- package/dist/features/products/constants/listing-tabs.d.ts +48 -0
- package/dist/features/products/constants/listing-tabs.js +12 -0
- 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/repository/products.repository.d.ts +37 -0
- package/dist/features/products/repository/products.repository.js +28 -1
- package/dist/features/products/schemas/firestore.d.ts +23 -7
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +18 -18
- package/dist/features/products/schemas/index.js +4 -2
- package/dist/features/products/types/index.d.ts +3 -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/promotions/schemas/firestore.d.ts +2 -4
- package/dist/features/promotions/schemas/index.d.ts +4 -4
- 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/reviews/schemas/firestore.d.ts +2 -4
- package/dist/features/reviews/schemas/index.d.ts +2 -2
- 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/scams/schemas/firestore.d.ts +2 -4
- package/dist/features/scams/schemas/index.d.ts +4 -4
- 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/search/schemas/index.d.ts +2 -2
- 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/components/BarcodeField.d.ts +17 -0
- package/dist/features/seller/components/BarcodeField.js +30 -0
- package/dist/features/seller/components/FulfillmentView.d.ts +5 -0
- package/dist/features/seller/components/FulfillmentView.js +120 -0
- package/dist/features/seller/components/PrintCenterView.d.ts +3 -1
- package/dist/features/seller/components/PrintCenterView.js +52 -4
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/index.d.ts +4 -0
- package/dist/features/seller/components/index.js +2 -0
- 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/seller/schemas/index.d.ts +2 -2
- package/dist/features/store-extensions/schemas/firestore.d.ts +12 -44
- package/dist/features/stores/components/StoreClassifiedsListing.d.ts +5 -0
- package/dist/features/stores/components/StoreClassifiedsListing.js +48 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.js +20 -0
- package/dist/features/stores/components/StoreDetailLayoutView.js +23 -2
- package/dist/features/stores/components/StoreDigitalCodesListing.d.ts +5 -0
- package/dist/features/stores/components/StoreDigitalCodesListing.js +48 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.js +20 -0
- package/dist/features/stores/components/StoreLiveItemsListing.d.ts +5 -0
- package/dist/features/stores/components/StoreLiveItemsListing.js +48 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.js +20 -0
- package/dist/features/stores/components/index.d.ts +9 -0
- package/dist/features/stores/components/index.js +3 -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/stores/schemas/firestore.d.ts +2 -4
- package/dist/features/stores/schemas/index.d.ts +4 -4
- 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/support/schemas/firestore.d.ts +2 -4
- package/dist/features/support/schemas/index.d.ts +2 -2
- 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 +24 -0
- package/dist/next/routing/route-map.js +11 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/schemas/registry.d.ts +24 -24
- 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 +5 -2
- 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 +5 -2
- package/dist/seed/products-standard-seed-data.js +8 -0
- package/dist/server-entry.d.ts +3 -0
- package/dist/server-entry.js +3 -0
- package/dist/server.d.ts +10 -4
- package/dist/server.js +6 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/id-generators.d.ts +8 -0
- package/dist/utils/id-generators.js +14 -0
- package/package.json +2 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { makeMockDb, makeSnap } from "../../../../../tests/helpers/mock-firestore";
|
|
3
|
+
const { db, mockDocRef, mockCollection } = makeMockDb();
|
|
4
|
+
const { mockCacheManager, mockDecryptSecret } = vi.hoisted(() => ({
|
|
5
|
+
mockCacheManager: {
|
|
6
|
+
get: vi.fn().mockReturnValue(null),
|
|
7
|
+
set: vi.fn(),
|
|
8
|
+
delete: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
mockDecryptSecret: vi.fn((v) => v.replace("enc:", "")),
|
|
11
|
+
}));
|
|
12
|
+
vi.mock("../../../../providers/db-firebase/admin", () => ({
|
|
13
|
+
getAdminDb: () => db,
|
|
14
|
+
}));
|
|
15
|
+
vi.mock("../../../../providers/db-firebase", async (importOriginal) => {
|
|
16
|
+
const actual = await importOriginal();
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
prepareForFirestore: (d) => d,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
vi.mock("../../../../core", () => ({
|
|
23
|
+
cacheManager: mockCacheManager,
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("../../../../security", () => ({
|
|
26
|
+
encryptSecret: (v) => `enc:${v}`,
|
|
27
|
+
decryptSecret: mockDecryptSecret,
|
|
28
|
+
maskSecret: (v) => `${v.slice(0, 4)}***`,
|
|
29
|
+
}));
|
|
30
|
+
vi.mock("../../../../errors/normalize", () => ({
|
|
31
|
+
normalizeError: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
vi.mock("../../../../monitoring", () => ({
|
|
34
|
+
serverLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
35
|
+
}));
|
|
36
|
+
import { SiteSettingsRepository } from "../site-settings.repository";
|
|
37
|
+
// Create a fresh repo per test to avoid shared state from `siteSettingsRepository` singleton
|
|
38
|
+
function makeRepo() {
|
|
39
|
+
return new SiteSettingsRepository();
|
|
40
|
+
}
|
|
41
|
+
function makeSettingsDoc(overrides = {}) {
|
|
42
|
+
return {
|
|
43
|
+
id: "global",
|
|
44
|
+
siteName: "LetItRip",
|
|
45
|
+
credentials: {},
|
|
46
|
+
featureFlags: { useMockPayment: false, useMockShipping: false },
|
|
47
|
+
features: [],
|
|
48
|
+
createdAt: new Date(),
|
|
49
|
+
updatedAt: new Date(),
|
|
50
|
+
...overrides,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
vi.clearAllMocks();
|
|
55
|
+
mockCacheManager.get.mockReturnValue(null);
|
|
56
|
+
mockDecryptSecret.mockImplementation((v) => v.replace("enc:", ""));
|
|
57
|
+
db.collection.mockReturnValue(mockCollection);
|
|
58
|
+
mockCollection.doc.mockReturnValue(mockDocRef);
|
|
59
|
+
const settingsDoc = makeSettingsDoc();
|
|
60
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
61
|
+
mockDocRef.set.mockResolvedValue(undefined);
|
|
62
|
+
mockDocRef.update.mockResolvedValue(undefined);
|
|
63
|
+
});
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// getSingleton
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
describe("SiteSettingsRepository.getSingleton", () => {
|
|
68
|
+
it("returns cached value without hitting Firestore when cache is warm", async () => {
|
|
69
|
+
const cached = makeSettingsDoc({ siteName: "Cached LetItRip" });
|
|
70
|
+
mockCacheManager.get.mockReturnValue(cached);
|
|
71
|
+
const repo = makeRepo();
|
|
72
|
+
const result = await repo.getSingleton();
|
|
73
|
+
expect(result.siteName).toBe("Cached LetItRip");
|
|
74
|
+
expect(mockDocRef.get).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
it("fetches from Firestore when cache is cold (null)", async () => {
|
|
77
|
+
mockCacheManager.get.mockReturnValue(null);
|
|
78
|
+
const repo = makeRepo();
|
|
79
|
+
await repo.getSingleton();
|
|
80
|
+
expect(mockDocRef.get).toHaveBeenCalledOnce();
|
|
81
|
+
});
|
|
82
|
+
it("populates cache after Firestore fetch", async () => {
|
|
83
|
+
mockCacheManager.get.mockReturnValue(null);
|
|
84
|
+
const settingsDoc = makeSettingsDoc();
|
|
85
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
86
|
+
const repo = makeRepo();
|
|
87
|
+
await repo.getSingleton();
|
|
88
|
+
expect(mockCacheManager.set).toHaveBeenCalledWith(expect.stringContaining("site-settings"), expect.anything(), expect.objectContaining({ ttl: expect.any(Number) }));
|
|
89
|
+
});
|
|
90
|
+
it("creates default settings when document does not exist", async () => {
|
|
91
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
92
|
+
const repo = makeRepo();
|
|
93
|
+
await repo.getSingleton();
|
|
94
|
+
expect(mockDocRef.set).toHaveBeenCalledOnce();
|
|
95
|
+
});
|
|
96
|
+
it("returns default settings when document does not exist", async () => {
|
|
97
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
98
|
+
const repo = makeRepo();
|
|
99
|
+
const result = await repo.getSingleton();
|
|
100
|
+
expect(result.id).toBe("global");
|
|
101
|
+
expect(result.createdAt).toBeInstanceOf(Date);
|
|
102
|
+
});
|
|
103
|
+
it("caches the newly-created default settings", async () => {
|
|
104
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
105
|
+
const repo = makeRepo();
|
|
106
|
+
await repo.getSingleton();
|
|
107
|
+
expect(mockCacheManager.set).toHaveBeenCalledWith(expect.stringContaining("site-settings"), expect.anything(), expect.objectContaining({ ttl: expect.any(Number) }));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// updateSingleton
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
describe("SiteSettingsRepository.updateSingleton", () => {
|
|
114
|
+
it("clears cache before writing", async () => {
|
|
115
|
+
const repo = makeRepo();
|
|
116
|
+
await repo.updateSingleton({ branding: { siteName: "New Name" } });
|
|
117
|
+
expect(mockCacheManager.delete).toHaveBeenCalledWith(expect.stringContaining("site-settings"));
|
|
118
|
+
// delete happens before the update
|
|
119
|
+
const deleteCallOrder = mockCacheManager.delete.mock.invocationCallOrder[0];
|
|
120
|
+
const updateCallOrder = mockDocRef.update.mock.invocationCallOrder[0];
|
|
121
|
+
expect(deleteCallOrder).toBeLessThan(updateCallOrder);
|
|
122
|
+
});
|
|
123
|
+
it("writes updatedAt timestamp", async () => {
|
|
124
|
+
const repo = makeRepo();
|
|
125
|
+
await repo.updateSingleton({ branding: { siteName: "New" } });
|
|
126
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
127
|
+
expect(updateArg.updatedAt).toBeInstanceOf(Date);
|
|
128
|
+
});
|
|
129
|
+
it("encrypts credential values before writing", async () => {
|
|
130
|
+
const settingsDoc = makeSettingsDoc({ credentials: {} });
|
|
131
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
132
|
+
const repo = makeRepo();
|
|
133
|
+
await repo.updateSingleton({
|
|
134
|
+
credentials: { razorpayKeyId: "rzp_live_abc123" },
|
|
135
|
+
});
|
|
136
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
137
|
+
const creds = updateArg.credentials;
|
|
138
|
+
expect(creds.razorpayKeyId).toBe("enc:rzp_live_abc123");
|
|
139
|
+
});
|
|
140
|
+
it("does not overwrite existing credential with empty string", async () => {
|
|
141
|
+
const settingsDoc = makeSettingsDoc({
|
|
142
|
+
credentials: { razorpayKeyId: "enc:existing-key" },
|
|
143
|
+
});
|
|
144
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
145
|
+
const repo = makeRepo();
|
|
146
|
+
await repo.updateSingleton({
|
|
147
|
+
credentials: { razorpayKeyId: "" },
|
|
148
|
+
});
|
|
149
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
150
|
+
const creds = updateArg.credentials;
|
|
151
|
+
// Empty string → keep existing
|
|
152
|
+
expect(creds.razorpayKeyId).toBe("enc:existing-key");
|
|
153
|
+
});
|
|
154
|
+
it("re-caches the latest settings after update", async () => {
|
|
155
|
+
const repo = makeRepo();
|
|
156
|
+
await repo.updateSingleton({ branding: { siteName: "Updated" } });
|
|
157
|
+
// getSingleton is called after update to get latest — cacheManager.set re-called
|
|
158
|
+
expect(mockCacheManager.set).toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// getDecryptedCredentials
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
describe("SiteSettingsRepository.getDecryptedCredentials", () => {
|
|
165
|
+
it("returns decrypted API key values", async () => {
|
|
166
|
+
const settingsDoc = makeSettingsDoc({
|
|
167
|
+
credentials: { razorpayKeyId: "enc:rzp_live_abc123" },
|
|
168
|
+
});
|
|
169
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
170
|
+
const repo = makeRepo();
|
|
171
|
+
const result = await repo.getDecryptedCredentials();
|
|
172
|
+
expect(result.razorpayKeyId).toBe("rzp_live_abc123");
|
|
173
|
+
});
|
|
174
|
+
it("falls back to empty string when decryption fails", async () => {
|
|
175
|
+
mockDecryptSecret.mockImplementationOnce(() => {
|
|
176
|
+
throw new Error("auth-tag mismatch");
|
|
177
|
+
});
|
|
178
|
+
const settingsDoc = makeSettingsDoc({
|
|
179
|
+
credentials: { razorpayKeyId: "corrupt-blob" },
|
|
180
|
+
});
|
|
181
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
182
|
+
const repo = makeRepo();
|
|
183
|
+
const result = await repo.getDecryptedCredentials();
|
|
184
|
+
expect(result.razorpayKeyId).toBe("");
|
|
185
|
+
});
|
|
186
|
+
it("returns empty object when no credentials stored", async () => {
|
|
187
|
+
const settingsDoc = makeSettingsDoc({ credentials: {} });
|
|
188
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
189
|
+
const repo = makeRepo();
|
|
190
|
+
const result = await repo.getDecryptedCredentials();
|
|
191
|
+
expect(Object.keys(result)).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// getCredentialsMasked
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
describe("SiteSettingsRepository.getCredentialsMasked", () => {
|
|
198
|
+
it("replaces credential values with masked versions", async () => {
|
|
199
|
+
const settingsDoc = makeSettingsDoc({
|
|
200
|
+
credentials: { razorpayKeyId: "enc:rzp_live_abc123" },
|
|
201
|
+
});
|
|
202
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
203
|
+
const repo = makeRepo();
|
|
204
|
+
const result = await repo.getCredentialsMasked();
|
|
205
|
+
// maskSecret("rzp_live_abc123") → "rzp_***" per our mock
|
|
206
|
+
expect(result.razorpayKeyId).toBe("rzp_***");
|
|
207
|
+
});
|
|
208
|
+
it("does not expose raw or encrypted credential values", async () => {
|
|
209
|
+
const settingsDoc = makeSettingsDoc({
|
|
210
|
+
credentials: { razorpayKeyId: "enc:secret-key" },
|
|
211
|
+
});
|
|
212
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
213
|
+
const repo = makeRepo();
|
|
214
|
+
const result = await repo.getCredentialsMasked();
|
|
215
|
+
expect(result.razorpayKeyId).not.toContain("secret-key");
|
|
216
|
+
expect(result.razorpayKeyId).not.toContain("enc:");
|
|
217
|
+
});
|
|
218
|
+
it("empty/falsy credential value → key omitted from masked output", async () => {
|
|
219
|
+
// getDecryptedCredentials skips falsy values; getCredentialsMasked
|
|
220
|
+
// only iterates decrypted keys → empty stored credential does not appear in output
|
|
221
|
+
const settingsDoc = makeSettingsDoc({ credentials: { razorpayKeyId: "" } });
|
|
222
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
223
|
+
const repo = makeRepo();
|
|
224
|
+
const result = await repo.getCredentialsMasked();
|
|
225
|
+
expect(result).not.toHaveProperty("razorpayKeyId");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// getFeatures / updateFeatures
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
describe("SiteSettingsRepository.getFeatures", () => {
|
|
232
|
+
it("returns the features array from settings", async () => {
|
|
233
|
+
const features = [{ id: "f-1", name: "Fast Checkout", description: "Quick buy", icon: "zap", enabled: true }];
|
|
234
|
+
const settingsDoc = makeSettingsDoc({ features });
|
|
235
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
236
|
+
const repo = makeRepo();
|
|
237
|
+
const result = await repo.getFeatures();
|
|
238
|
+
expect(result).toHaveLength(1);
|
|
239
|
+
expect(result[0].name).toBe("Fast Checkout");
|
|
240
|
+
});
|
|
241
|
+
it("returns empty array when no features stored", async () => {
|
|
242
|
+
const settingsDoc = makeSettingsDoc({ features: undefined });
|
|
243
|
+
mockDocRef.get.mockResolvedValue(makeSnap(settingsDoc, "global"));
|
|
244
|
+
const repo = makeRepo();
|
|
245
|
+
const result = await repo.getFeatures();
|
|
246
|
+
expect(result).toEqual([]);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe("SiteSettingsRepository.updateFeatures", () => {
|
|
250
|
+
it("writes features array to Firestore", async () => {
|
|
251
|
+
const repo = makeRepo();
|
|
252
|
+
const features = [{ id: "f-1", name: "F1", description: "D1", icon: "star", enabled: true }];
|
|
253
|
+
await repo.updateFeatures(features);
|
|
254
|
+
expect(mockDocRef.update).toHaveBeenCalled();
|
|
255
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
256
|
+
expect(updateArg.features).toEqual(features);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// exists
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
describe("SiteSettingsRepository.exists", () => {
|
|
263
|
+
it("returns true when singleton document exists", async () => {
|
|
264
|
+
mockDocRef.get.mockResolvedValue(makeSnap(makeSettingsDoc(), "global"));
|
|
265
|
+
const repo = makeRepo();
|
|
266
|
+
const result = await repo.exists();
|
|
267
|
+
expect(result).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
it("returns false when document does not exist", async () => {
|
|
270
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
271
|
+
const repo = makeRepo();
|
|
272
|
+
const result = await repo.exists();
|
|
273
|
+
expect(result).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
it("returns false when Firestore throws", async () => {
|
|
276
|
+
mockDocRef.get.mockRejectedValue(new Error("Firestore unavailable"));
|
|
277
|
+
const repo = makeRepo();
|
|
278
|
+
const result = await repo.exists();
|
|
279
|
+
expect(result).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// resetToDefaults
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
describe("SiteSettingsRepository.resetToDefaults", () => {
|
|
286
|
+
it("overwrites the singleton with default settings", async () => {
|
|
287
|
+
const repo = makeRepo();
|
|
288
|
+
await repo.resetToDefaults();
|
|
289
|
+
expect(mockDocRef.set).toHaveBeenCalledOnce();
|
|
290
|
+
const setArg = mockDocRef.set.mock.calls[0][0];
|
|
291
|
+
expect(setArg.id).toBe("global");
|
|
292
|
+
});
|
|
293
|
+
it("caches the reset defaults", async () => {
|
|
294
|
+
const repo = makeRepo();
|
|
295
|
+
await repo.resetToDefaults();
|
|
296
|
+
expect(mockCacheManager.set).toHaveBeenCalledWith(expect.stringContaining("site-settings"), expect.anything(), expect.objectContaining({ ttl: expect.any(Number) }));
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const ADMIN_CHECKOUT_BYPASS_FLAG_KEY: "adminCheckoutBypass";
|
|
6
6
|
export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_reveal_ready" | "prize_reveal_expired" | "prize_reveal_reminder";
|
|
7
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
7
8
|
export type NotificationPriority = "low" | "normal" | "high";
|
|
8
|
-
export interface NotificationDocument {
|
|
9
|
-
id: string;
|
|
9
|
+
export interface NotificationDocument extends BaseDocument {
|
|
10
10
|
userId: string;
|
|
11
11
|
type: NotificationType;
|
|
12
12
|
priority: NotificationPriority;
|
|
@@ -19,8 +19,6 @@ export interface NotificationDocument {
|
|
|
19
19
|
readAt?: Date;
|
|
20
20
|
relatedId?: string;
|
|
21
21
|
relatedType?: "order" | "product" | "bid" | "review" | "blog" | "user" | "offer" | "support_ticket" | "scammer";
|
|
22
|
-
createdAt: Date;
|
|
23
|
-
updatedAt: Date;
|
|
24
22
|
}
|
|
25
23
|
export declare const NOTIFICATIONS_COLLECTION: "notifications";
|
|
26
24
|
export declare const NOTIFICATIONS_INDEXED_FIELDS: readonly ["userId", "isRead", "createdAt", "type"];
|
|
@@ -81,8 +79,7 @@ export declare const notificationQueryHelpers: {
|
|
|
81
79
|
readonly unread: (userId: string) => readonly [readonly ["userId", "==", string], readonly ["isRead", "==", false]];
|
|
82
80
|
readonly byType: (type: NotificationType) => readonly ["type", "==", NotificationType];
|
|
83
81
|
};
|
|
84
|
-
export interface ChatRoomDocument {
|
|
85
|
-
id: string;
|
|
82
|
+
export interface ChatRoomDocument extends BaseDocument {
|
|
86
83
|
buyerId: string;
|
|
87
84
|
ownerId: string;
|
|
88
85
|
orderId: string;
|
|
@@ -92,8 +89,6 @@ export interface ChatRoomDocument {
|
|
|
92
89
|
ownerName: string;
|
|
93
90
|
lastMessage?: string;
|
|
94
91
|
lastMessageAt?: Date;
|
|
95
|
-
createdAt: Date;
|
|
96
|
-
updatedAt: Date;
|
|
97
92
|
/** UIDs of participants who soft-deleted this room on their side. */
|
|
98
93
|
deletedBy: string[];
|
|
99
94
|
/** True for admin-created group chats (> 2 participants). */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { makeMockDb, makeSnap, makeQuerySnap } from "../../../../../tests/helpers/mock-firestore";
|
|
3
|
+
const { db, mockDocRef, mockCollection, mockQuery, mockBatch } = makeMockDb();
|
|
4
|
+
const { mockGetFirestoreCount } = vi.hoisted(() => ({
|
|
5
|
+
mockGetFirestoreCount: vi.fn().mockResolvedValue(0),
|
|
6
|
+
}));
|
|
7
|
+
vi.mock("../../../../providers/db-firebase/admin", () => ({
|
|
8
|
+
getAdminDb: () => db,
|
|
9
|
+
}));
|
|
10
|
+
vi.mock("../../../../providers/db-firebase", async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal();
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
prepareForFirestore: (d) => d,
|
|
15
|
+
getFirestoreCount: mockGetFirestoreCount,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
vi.mock("../../../../errors/normalize", () => ({
|
|
19
|
+
normalizeError: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
vi.mock("../../../../security", () => ({
|
|
22
|
+
encryptPiiFields: (d) => d,
|
|
23
|
+
decryptPiiFields: (d) => d,
|
|
24
|
+
BID_PII_FIELDS: [],
|
|
25
|
+
}));
|
|
26
|
+
import { BidRepository } from "../bid.repository";
|
|
27
|
+
const repo = new BidRepository();
|
|
28
|
+
function makeBidDoc(overrides = {}) {
|
|
29
|
+
return {
|
|
30
|
+
id: "bid-1",
|
|
31
|
+
productId: "auction-x",
|
|
32
|
+
bidderId: "user-1",
|
|
33
|
+
userId: "user-1",
|
|
34
|
+
userName: "Ravi",
|
|
35
|
+
userEmail: "ravi@test.com",
|
|
36
|
+
bidAmount: 5000,
|
|
37
|
+
status: "active",
|
|
38
|
+
isWinning: false,
|
|
39
|
+
bidDate: new Date(),
|
|
40
|
+
createdAt: new Date(),
|
|
41
|
+
updatedAt: new Date(),
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function makeFakeRef(id = "bid-1") {
|
|
46
|
+
return { id };
|
|
47
|
+
}
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.clearAllMocks();
|
|
50
|
+
db.collection.mockReturnValue(mockCollection);
|
|
51
|
+
mockCollection.doc.mockReturnValue(mockDocRef);
|
|
52
|
+
mockCollection.where.mockReturnValue(mockQuery);
|
|
53
|
+
mockQuery.where.mockReturnValue(mockQuery);
|
|
54
|
+
mockQuery.orderBy.mockReturnValue(mockQuery);
|
|
55
|
+
mockQuery.limit.mockReturnValue(mockQuery);
|
|
56
|
+
mockQuery.get.mockResolvedValue(makeQuerySnap([]));
|
|
57
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
58
|
+
mockDocRef.set.mockResolvedValue(undefined);
|
|
59
|
+
db.batch.mockReturnValue(mockBatch);
|
|
60
|
+
mockGetFirestoreCount.mockResolvedValue(0);
|
|
61
|
+
});
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// markWon / markLost / markOutbid / markWinning (batch helpers)
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
describe("BidRepository.markWon", () => {
|
|
66
|
+
it("stages status=won, isWinning=true in caller batch", () => {
|
|
67
|
+
const ref = makeFakeRef();
|
|
68
|
+
repo.markWon(mockBatch, ref);
|
|
69
|
+
expect(mockBatch.update).toHaveBeenCalledWith(ref, expect.objectContaining({ status: "won", isWinning: true }));
|
|
70
|
+
expect(mockBatch.commit).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("BidRepository.markLost", () => {
|
|
74
|
+
it("stages status=lost, isWinning=false in caller batch", () => {
|
|
75
|
+
const ref = makeFakeRef();
|
|
76
|
+
repo.markLost(mockBatch, ref);
|
|
77
|
+
expect(mockBatch.update).toHaveBeenCalledWith(ref, expect.objectContaining({ status: "lost", isWinning: false }));
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("BidRepository.markOutbid", () => {
|
|
81
|
+
it("stages status=outbid, isWinning=false in caller batch", () => {
|
|
82
|
+
const ref = makeFakeRef();
|
|
83
|
+
repo.markOutbid(mockBatch, ref);
|
|
84
|
+
expect(mockBatch.update).toHaveBeenCalledWith(ref, expect.objectContaining({ status: "outbid", isWinning: false }));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("BidRepository.markWinning", () => {
|
|
88
|
+
it("stages isWinning=true (does not change status) in caller batch", () => {
|
|
89
|
+
const ref = makeFakeRef();
|
|
90
|
+
repo.markWinning(mockBatch, ref);
|
|
91
|
+
expect(mockBatch.update).toHaveBeenCalledWith(ref, expect.objectContaining({ isWinning: true }));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// getWinningBid
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
describe("BidRepository.getWinningBid", () => {
|
|
98
|
+
it("returns {ref, data} for the winning active bid", async () => {
|
|
99
|
+
const bid = makeBidDoc({ isWinning: true, status: "active" });
|
|
100
|
+
const snap = makeSnap(bid, bid.id);
|
|
101
|
+
mockQuery.get.mockResolvedValue({ ...makeQuerySnap([{ id: bid.id, data: bid }]), docs: [snap], empty: false });
|
|
102
|
+
const result = await repo.getWinningBid("auction-x");
|
|
103
|
+
expect(result).not.toBeNull();
|
|
104
|
+
expect(result.data).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
it("returns null when no winning bid", async () => {
|
|
107
|
+
mockQuery.get.mockResolvedValue({ ...makeQuerySnap([]), empty: true, docs: [] });
|
|
108
|
+
const result = await repo.getWinningBid("auction-x");
|
|
109
|
+
expect(result).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
it("queries by isWinning=true AND status=active", async () => {
|
|
112
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
113
|
+
await repo.getWinningBid("auction-x");
|
|
114
|
+
expect(mockQuery.where).toHaveBeenCalledWith("isWinning", "==", true);
|
|
115
|
+
expect(mockQuery.where).toHaveBeenCalledWith("status", "==", "active");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// findWinningBid
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
describe("BidRepository.findWinningBid", () => {
|
|
122
|
+
it("returns BidDocument for winning bid", async () => {
|
|
123
|
+
const bid = makeBidDoc({ isWinning: true });
|
|
124
|
+
const snap = makeSnap(bid, bid.id);
|
|
125
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
126
|
+
const result = await repo.findWinningBid("auction-x");
|
|
127
|
+
expect(result).not.toBeNull();
|
|
128
|
+
});
|
|
129
|
+
it("returns null when no winning bid", async () => {
|
|
130
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
131
|
+
const result = await repo.findWinningBid("auction-x");
|
|
132
|
+
expect(result).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
it("queries by isWinning=true (no status filter unlike getWinningBid)", async () => {
|
|
135
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
136
|
+
await repo.findWinningBid("auction-x");
|
|
137
|
+
expect(mockQuery.where).toHaveBeenCalledWith("isWinning", "==", true);
|
|
138
|
+
// findWinningBid does NOT filter by status — that's the distinction from getWinningBid
|
|
139
|
+
const statusFilterCalls = mockQuery.where.mock.calls.filter((call) => call[0] === "status");
|
|
140
|
+
expect(statusFilterCalls).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// findHighestBid
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
describe("BidRepository.findHighestBid", () => {
|
|
147
|
+
it("returns highest bidAmount from active bids", async () => {
|
|
148
|
+
const bid = makeBidDoc({ bidAmount: 8000, status: "active" });
|
|
149
|
+
const snap = makeSnap(bid, bid.id);
|
|
150
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
151
|
+
const result = await repo.findHighestBid("auction-x");
|
|
152
|
+
expect(result).toBe(8000);
|
|
153
|
+
});
|
|
154
|
+
it("returns 0 when no bids exist", async () => {
|
|
155
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
156
|
+
const result = await repo.findHighestBid("auction-x");
|
|
157
|
+
expect(result).toBe(0);
|
|
158
|
+
});
|
|
159
|
+
it("queries by status=active, ordered by bidAmount desc", async () => {
|
|
160
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
161
|
+
await repo.findHighestBid("auction-x");
|
|
162
|
+
expect(mockQuery.where).toHaveBeenCalledWith("status", "==", "active");
|
|
163
|
+
expect(mockQuery.orderBy).toHaveBeenCalledWith("bidAmount", "desc");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// setWinningBid
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
describe("BidRepository.setWinningBid", () => {
|
|
170
|
+
it("sets all other bids for product to outbid", async () => {
|
|
171
|
+
const bid1 = makeBidDoc({ id: "bid-1", isWinning: false });
|
|
172
|
+
const bid2 = makeBidDoc({ id: "bid-2", isWinning: false });
|
|
173
|
+
const snap1 = makeSnap(bid1, "bid-1");
|
|
174
|
+
const snap2 = makeSnap(bid2, "bid-2");
|
|
175
|
+
mockQuery.get.mockResolvedValue({ docs: [snap1, snap2], empty: false });
|
|
176
|
+
await repo.setWinningBid("bid-1", "auction-x");
|
|
177
|
+
expect(mockBatch.update).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ status: "outbid" }));
|
|
178
|
+
});
|
|
179
|
+
it("sets the winning bid to status=active, isWinning=true", async () => {
|
|
180
|
+
const bid = makeBidDoc({ id: "bid-winner" });
|
|
181
|
+
const snap = makeSnap(bid, "bid-winner");
|
|
182
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
183
|
+
await repo.setWinningBid("bid-winner", "auction-x");
|
|
184
|
+
// The winning bid ref is updated separately with isWinning=true
|
|
185
|
+
const updateCalls = mockBatch.update.mock.calls;
|
|
186
|
+
const winningCall = updateCalls.find((c) => c[1].isWinning === true);
|
|
187
|
+
expect(winningCall).toBeDefined();
|
|
188
|
+
expect(winningCall[1].status).toBe("active");
|
|
189
|
+
});
|
|
190
|
+
it("commits the batch", async () => {
|
|
191
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
192
|
+
await repo.setWinningBid("bid-1", "auction-x");
|
|
193
|
+
expect(mockBatch.commit).toHaveBeenCalledOnce();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// endAuction
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
describe("BidRepository.endAuction", () => {
|
|
200
|
+
it("isWinning bid → status=won", async () => {
|
|
201
|
+
const winBid = makeBidDoc({ id: "bid-win", isWinning: true });
|
|
202
|
+
const snap = makeSnap(winBid, "bid-win");
|
|
203
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
204
|
+
await repo.endAuction("auction-x");
|
|
205
|
+
expect(mockBatch.update).toHaveBeenCalledWith(snap.ref, expect.objectContaining({ status: "won" }));
|
|
206
|
+
});
|
|
207
|
+
it("non-winning bids → status=lost", async () => {
|
|
208
|
+
const loseBid = makeBidDoc({ id: "bid-lose", isWinning: false });
|
|
209
|
+
const snap = makeSnap(loseBid, "bid-lose");
|
|
210
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
211
|
+
await repo.endAuction("auction-x");
|
|
212
|
+
expect(mockBatch.update).toHaveBeenCalledWith(snap.ref, expect.objectContaining({ status: "lost" }));
|
|
213
|
+
});
|
|
214
|
+
it("commits the batch", async () => {
|
|
215
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
216
|
+
await repo.endAuction("auction-x");
|
|
217
|
+
expect(mockBatch.commit).toHaveBeenCalledOnce();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// cancelProductBids
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
describe("BidRepository.cancelProductBids", () => {
|
|
224
|
+
it("sets all bids to status=cancelled", async () => {
|
|
225
|
+
const bid = makeBidDoc({ id: "bid-1" });
|
|
226
|
+
const snap = makeSnap(bid, "bid-1");
|
|
227
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
228
|
+
await repo.cancelProductBids("auction-x");
|
|
229
|
+
expect(mockBatch.update).toHaveBeenCalledWith(snap.ref, expect.objectContaining({ status: "cancelled", isWinning: false }));
|
|
230
|
+
});
|
|
231
|
+
it("commits the batch", async () => {
|
|
232
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
233
|
+
await repo.cancelProductBids("auction-x");
|
|
234
|
+
expect(mockBatch.commit).toHaveBeenCalledOnce();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// countByProduct / countByUser
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
describe("BidRepository.countByProduct", () => {
|
|
241
|
+
it("returns count from getFirestoreCount", async () => {
|
|
242
|
+
mockGetFirestoreCount.mockResolvedValue(7);
|
|
243
|
+
const count = await repo.countByProduct("auction-x");
|
|
244
|
+
expect(count).toBe(7);
|
|
245
|
+
});
|
|
246
|
+
it("queries by productId", async () => {
|
|
247
|
+
await repo.countByProduct("auction-x");
|
|
248
|
+
expect(mockCollection.where).toHaveBeenCalledWith("productId", "==", "auction-x");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe("BidRepository.countByUser", () => {
|
|
252
|
+
it("returns count from getFirestoreCount", async () => {
|
|
253
|
+
mockGetFirestoreCount.mockResolvedValue(3);
|
|
254
|
+
const count = await repo.countByUser("user-1");
|
|
255
|
+
expect(count).toBe(3);
|
|
256
|
+
});
|
|
257
|
+
it("queries by userId", async () => {
|
|
258
|
+
await repo.countByUser("user-1");
|
|
259
|
+
expect(mockCollection.where).toHaveBeenCalledWith("userId", "==", "user-1");
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// findOneByProductAndUser
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
describe("BidRepository.findOneByProductAndUser", () => {
|
|
266
|
+
it("returns matching bid", async () => {
|
|
267
|
+
const bid = makeBidDoc({ productId: "auction-x", userId: "user-1", status: "active" });
|
|
268
|
+
const snap = makeSnap(bid, bid.id);
|
|
269
|
+
mockQuery.get.mockResolvedValue({ docs: [snap], empty: false });
|
|
270
|
+
const result = await repo.findOneByProductAndUser("auction-x", "user-1");
|
|
271
|
+
expect(result).not.toBeNull();
|
|
272
|
+
});
|
|
273
|
+
it("returns null when not found", async () => {
|
|
274
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
275
|
+
const result = await repo.findOneByProductAndUser("auction-x", "user-2");
|
|
276
|
+
expect(result).toBeNull();
|
|
277
|
+
});
|
|
278
|
+
it("defaults status to 'active'", async () => {
|
|
279
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
280
|
+
await repo.findOneByProductAndUser("auction-x", "user-1");
|
|
281
|
+
expect(mockQuery.where).toHaveBeenCalledWith("status", "==", "active");
|
|
282
|
+
});
|
|
283
|
+
it("queries by all three conditions: productId, userId, status", async () => {
|
|
284
|
+
mockQuery.get.mockResolvedValue({ docs: [], empty: true });
|
|
285
|
+
await repo.findOneByProductAndUser("auction-x", "user-1", "outbid");
|
|
286
|
+
expect(mockQuery.where).toHaveBeenCalledWith("productId", "==", "auction-x");
|
|
287
|
+
expect(mockQuery.where).toHaveBeenCalledWith("userId", "==", "user-1");
|
|
288
|
+
expect(mockQuery.where).toHaveBeenCalledWith("status", "==", "outbid");
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Bids/Auctions Firestore Document Types & Constants
|
|
3
3
|
*/
|
|
4
4
|
import { type GenerateBidIdInput } from "../../../utils/id-generators";
|
|
5
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
5
6
|
export type BidStatus = "active" | "outbid" | "won" | "lost" | "cancelled";
|
|
6
7
|
/** Runtime-accessible bid status values — use instead of bare string literals. */
|
|
7
8
|
export declare const BidStatusValues: {
|
|
@@ -11,8 +12,7 @@ export declare const BidStatusValues: {
|
|
|
11
12
|
readonly LOST: "lost";
|
|
12
13
|
readonly CANCELLED: "cancelled";
|
|
13
14
|
};
|
|
14
|
-
export interface BidDocument {
|
|
15
|
-
id: string;
|
|
15
|
+
export interface BidDocument extends BaseDocument {
|
|
16
16
|
productId: string;
|
|
17
17
|
productTitle: string;
|
|
18
18
|
userId: string;
|
|
@@ -25,8 +25,6 @@ export interface BidDocument {
|
|
|
25
25
|
previousBidAmount?: number;
|
|
26
26
|
bidDate: Date;
|
|
27
27
|
autoMaxBid?: number;
|
|
28
|
-
createdAt: Date;
|
|
29
|
-
updatedAt: Date;
|
|
30
28
|
}
|
|
31
29
|
export declare const BID_COLLECTION: "bids";
|
|
32
30
|
export declare const BID_INDEXED_FIELDS: readonly ["productId", "userId", "status", "isWinning", "bidDate", "createdAt"];
|