@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,261 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { makeMockDb, makeSnap, makeQuerySnap } from "../../../../../tests/helpers/mock-firestore";
|
|
3
|
+
import { DatabaseError } from "../../../../errors";
|
|
4
|
+
const { db, mockDocRef, mockCollection, mockQuery, mockBatch } = makeMockDb();
|
|
5
|
+
vi.mock("../../../../providers/db-firebase/admin", () => ({
|
|
6
|
+
getAdminDb: () => db,
|
|
7
|
+
}));
|
|
8
|
+
vi.mock("../../../../providers/db-firebase", async (importOriginal) => {
|
|
9
|
+
const actual = await importOriginal();
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
prepareForFirestore: (d) => d,
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
vi.mock("../../../../security/settings-encryption", () => ({
|
|
16
|
+
encryptSecret: (v) => `enc:${v}`,
|
|
17
|
+
decryptSecret: (v) => v.replace(/^enc:/, ""),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("../../../../errors/normalize", () => ({
|
|
20
|
+
normalizeError: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
import { StoreRepository } from "../store.repository";
|
|
23
|
+
const repo = new StoreRepository();
|
|
24
|
+
function makeStoreDoc(overrides = {}) {
|
|
25
|
+
return {
|
|
26
|
+
id: "store-test",
|
|
27
|
+
storeSlug: "store-test",
|
|
28
|
+
ownerId: "uid-seller-1",
|
|
29
|
+
storeName: "Test Store",
|
|
30
|
+
status: "active",
|
|
31
|
+
isPublic: true,
|
|
32
|
+
isVerified: false,
|
|
33
|
+
createdAt: new Date(),
|
|
34
|
+
updatedAt: new Date(),
|
|
35
|
+
stats: { totalProducts: 0, itemsSold: 0, totalReviews: 0, averageRating: 0 },
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function makeCreateInput(overrides = {}) {
|
|
40
|
+
return {
|
|
41
|
+
storeSlug: "store-test",
|
|
42
|
+
ownerId: "uid-seller-1",
|
|
43
|
+
storeName: "Test Store",
|
|
44
|
+
status: "pending",
|
|
45
|
+
isPublic: false,
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.clearAllMocks();
|
|
51
|
+
db.collection.mockReturnValue(mockCollection);
|
|
52
|
+
mockCollection.doc.mockReturnValue(mockDocRef);
|
|
53
|
+
mockCollection.where.mockReturnValue(mockQuery);
|
|
54
|
+
mockQuery.where.mockReturnValue(mockQuery);
|
|
55
|
+
mockQuery.orderBy.mockReturnValue(mockQuery);
|
|
56
|
+
mockQuery.limit.mockReturnValue(mockQuery);
|
|
57
|
+
mockQuery.get.mockResolvedValue(makeQuerySnap([]));
|
|
58
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
59
|
+
mockDocRef.create.mockResolvedValue(undefined);
|
|
60
|
+
mockDocRef.set.mockResolvedValue(undefined);
|
|
61
|
+
mockDocRef.update.mockResolvedValue(undefined);
|
|
62
|
+
mockDocRef.delete.mockResolvedValue(undefined);
|
|
63
|
+
db.batch.mockReturnValue(mockBatch);
|
|
64
|
+
});
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// create
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
describe("StoreRepository.create", () => {
|
|
69
|
+
it("throws when storeSlug equals ownerId", async () => {
|
|
70
|
+
const input = makeCreateInput({ storeSlug: "uid-same", ownerId: "uid-same" });
|
|
71
|
+
await expect(repo.create(input)).rejects.toThrow(DatabaseError);
|
|
72
|
+
});
|
|
73
|
+
it("creates document using .create() semantics (not .set())", async () => {
|
|
74
|
+
await repo.create(makeCreateInput());
|
|
75
|
+
expect(mockDocRef.create).toHaveBeenCalledOnce();
|
|
76
|
+
expect(mockDocRef.set).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
it("sets document ID to storeSlug", async () => {
|
|
79
|
+
await repo.create(makeCreateInput({ storeSlug: "store-pokemon-palace" }));
|
|
80
|
+
expect(mockCollection.doc).toHaveBeenCalledWith("store-pokemon-palace");
|
|
81
|
+
});
|
|
82
|
+
it("throws DatabaseError(slug already taken) when .create() fails with gRPC code 6", async () => {
|
|
83
|
+
const grpcAlreadyExists = Object.assign(new Error("ALREADY_EXISTS"), { code: 6 });
|
|
84
|
+
mockDocRef.create.mockRejectedValue(grpcAlreadyExists);
|
|
85
|
+
await expect(repo.create(makeCreateInput({ storeSlug: "store-taken" }))).rejects.toThrow(DatabaseError);
|
|
86
|
+
});
|
|
87
|
+
it("re-throws non-ALREADY_EXISTS errors as-is", async () => {
|
|
88
|
+
const networkErr = new Error("NETWORK_FAILURE");
|
|
89
|
+
mockDocRef.create.mockRejectedValue(networkErr);
|
|
90
|
+
await expect(repo.create(makeCreateInput())).rejects.toThrow("NETWORK_FAILURE");
|
|
91
|
+
});
|
|
92
|
+
it("returns the store document with id=storeSlug", async () => {
|
|
93
|
+
const result = await repo.create(makeCreateInput({ storeSlug: "store-abc" }));
|
|
94
|
+
expect(result.id).toBe("store-abc");
|
|
95
|
+
expect(result.storeSlug).toBe("store-abc");
|
|
96
|
+
});
|
|
97
|
+
it("encrypts whatsappConfig.accessToken before writing", async () => {
|
|
98
|
+
const input = makeCreateInput({
|
|
99
|
+
whatsappConfig: { accessToken: "secret-token", phoneNumberId: "ph-1", businessId: "biz-1" },
|
|
100
|
+
});
|
|
101
|
+
await repo.create(input);
|
|
102
|
+
const callArg = mockDocRef.create.mock.calls[0][0];
|
|
103
|
+
const config = callArg.whatsappConfig;
|
|
104
|
+
expect(config.accessToken).toBe("enc:secret-token");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// isSlugAvailable
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
describe("StoreRepository.isSlugAvailable", () => {
|
|
111
|
+
it("returns true when no document exists for that slug", async () => {
|
|
112
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
113
|
+
const result = await repo.isSlugAvailable("store-new");
|
|
114
|
+
expect(result).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it("returns false when a document exists for that slug", async () => {
|
|
117
|
+
const store = makeStoreDoc();
|
|
118
|
+
mockDocRef.get.mockResolvedValue(makeSnap(store, "store-test"));
|
|
119
|
+
const result = await repo.isSlugAvailable("store-test");
|
|
120
|
+
expect(result).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// findByOwnerId
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
describe("StoreRepository.findByOwnerId", () => {
|
|
127
|
+
it("returns store when found", async () => {
|
|
128
|
+
const store = makeStoreDoc();
|
|
129
|
+
mockQuery.get.mockResolvedValue(makeQuerySnap([{ id: store.id, data: store }]));
|
|
130
|
+
const result = await repo.findByOwnerId("uid-seller-1");
|
|
131
|
+
expect(result).not.toBeNull();
|
|
132
|
+
});
|
|
133
|
+
it("queries by ownerId field", async () => {
|
|
134
|
+
mockQuery.get.mockResolvedValue(makeQuerySnap([]));
|
|
135
|
+
await repo.findByOwnerId("uid-seller-1");
|
|
136
|
+
expect(mockCollection.where).toHaveBeenCalledWith("ownerId", "==", "uid-seller-1");
|
|
137
|
+
});
|
|
138
|
+
it("returns null when no store matches", async () => {
|
|
139
|
+
mockQuery.get.mockResolvedValue(makeQuerySnap([]));
|
|
140
|
+
const result = await repo.findByOwnerId("uid-nobody");
|
|
141
|
+
expect(result).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// setStatus
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
describe("StoreRepository.setStatus", () => {
|
|
148
|
+
it("status=active → sets isPublic: true", async () => {
|
|
149
|
+
const store = makeStoreDoc({ status: "pending", isPublic: false });
|
|
150
|
+
mockDocRef.get.mockResolvedValue(makeSnap(store, "store-test"));
|
|
151
|
+
await repo.setStatus("store-test", "active");
|
|
152
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "active", isPublic: true }));
|
|
153
|
+
});
|
|
154
|
+
it("status=suspended → sets isPublic: false", async () => {
|
|
155
|
+
const store = makeStoreDoc({ status: "active", isPublic: true });
|
|
156
|
+
mockDocRef.get.mockResolvedValue(makeSnap(store, "store-test"));
|
|
157
|
+
await repo.setStatus("store-test", "suspended");
|
|
158
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "suspended", isPublic: false }));
|
|
159
|
+
});
|
|
160
|
+
it("status=pending → sets isPublic: false", async () => {
|
|
161
|
+
const store = makeStoreDoc({ status: "active", isPublic: true });
|
|
162
|
+
mockDocRef.get.mockResolvedValue(makeSnap(store, "store-test"));
|
|
163
|
+
await repo.setStatus("store-test", "pending");
|
|
164
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "pending", isPublic: false }));
|
|
165
|
+
});
|
|
166
|
+
it("status=rejected → sets isPublic: false", async () => {
|
|
167
|
+
const store = makeStoreDoc({ status: "active", isPublic: true });
|
|
168
|
+
mockDocRef.get.mockResolvedValue(makeSnap(store, "store-test"));
|
|
169
|
+
await repo.setStatus("store-test", "rejected");
|
|
170
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ isPublic: false }));
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// incrementTotalProducts
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
describe("StoreRepository.incrementTotalProducts", () => {
|
|
177
|
+
it("calls update with increment on stats.totalProducts", async () => {
|
|
178
|
+
await repo.incrementTotalProducts("store-1", 1);
|
|
179
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ "stats.totalProducts": expect.anything() }));
|
|
180
|
+
});
|
|
181
|
+
it("no-op when storeId is empty string", async () => {
|
|
182
|
+
await repo.incrementTotalProducts("", 1);
|
|
183
|
+
expect(mockDocRef.update).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
it("accepts negative delta (decrement)", async () => {
|
|
186
|
+
await repo.incrementTotalProducts("store-1", -1);
|
|
187
|
+
expect(mockDocRef.update).toHaveBeenCalledOnce();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// updateReviewStats
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
describe("StoreRepository.updateReviewStats", () => {
|
|
194
|
+
it("updates totalReviews and averageRating on the store doc", async () => {
|
|
195
|
+
await repo.updateReviewStats("store-1", 10, 4.5);
|
|
196
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({
|
|
197
|
+
"stats.totalReviews": 10,
|
|
198
|
+
"stats.averageRating": 4.5,
|
|
199
|
+
}));
|
|
200
|
+
});
|
|
201
|
+
it("no-op when storeId is empty string", async () => {
|
|
202
|
+
await repo.updateReviewStats("", 10, 4.5);
|
|
203
|
+
expect(mockDocRef.update).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// changeSlug
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
describe("StoreRepository.changeSlug", () => {
|
|
210
|
+
it("throws when currentSlug === newSlug", async () => {
|
|
211
|
+
await expect(repo.changeSlug("store-same", "store-same")).rejects.toThrow(DatabaseError);
|
|
212
|
+
});
|
|
213
|
+
it("throws when newSlug is empty", async () => {
|
|
214
|
+
await expect(repo.changeSlug("store-old", "")).rejects.toThrow(DatabaseError);
|
|
215
|
+
});
|
|
216
|
+
it("throws when newSlug contains invalid characters (uppercase)", async () => {
|
|
217
|
+
await expect(repo.changeSlug("store-old", "Store-BAD")).rejects.toThrow(DatabaseError);
|
|
218
|
+
});
|
|
219
|
+
it("throws when current store is not found", async () => {
|
|
220
|
+
mockDocRef.get.mockResolvedValue(makeSnap(null));
|
|
221
|
+
await expect(repo.changeSlug("store-ghost", "store-new")).rejects.toThrow(DatabaseError);
|
|
222
|
+
});
|
|
223
|
+
it("throws when newSlug is already taken", async () => {
|
|
224
|
+
const currentStore = makeStoreDoc({ storeSlug: "store-old" });
|
|
225
|
+
const takenStore = makeStoreDoc({ storeSlug: "store-taken", id: "store-taken" });
|
|
226
|
+
mockDocRef.get
|
|
227
|
+
.mockResolvedValueOnce(makeSnap(currentStore, "store-old")) // findById(currentSlug)
|
|
228
|
+
.mockResolvedValueOnce(makeSnap(takenStore, "store-taken")); // isSlugAvailable check
|
|
229
|
+
await expect(repo.changeSlug("store-old", "store-taken")).rejects.toThrow(DatabaseError);
|
|
230
|
+
});
|
|
231
|
+
it("atomically creates new doc and deletes old doc in one batch", async () => {
|
|
232
|
+
const currentStore = makeStoreDoc({ storeSlug: "store-old" });
|
|
233
|
+
mockDocRef.get
|
|
234
|
+
.mockResolvedValueOnce(makeSnap(currentStore, "store-old")) // findById(currentSlug)
|
|
235
|
+
.mockResolvedValueOnce(makeSnap(null)); // isSlugAvailable(newSlug) → available
|
|
236
|
+
await repo.changeSlug("store-old", "store-new");
|
|
237
|
+
expect(mockBatch.create).toHaveBeenCalledOnce();
|
|
238
|
+
expect(mockBatch.delete).toHaveBeenCalledOnce();
|
|
239
|
+
expect(mockBatch.commit).toHaveBeenCalledOnce();
|
|
240
|
+
});
|
|
241
|
+
it("returns result with id=newSlug and storeSlug=newSlug", async () => {
|
|
242
|
+
const currentStore = makeStoreDoc({ storeSlug: "store-old" });
|
|
243
|
+
mockDocRef.get
|
|
244
|
+
.mockResolvedValueOnce(makeSnap(currentStore, "store-old"))
|
|
245
|
+
.mockResolvedValueOnce(makeSnap(null));
|
|
246
|
+
const result = await repo.changeSlug("store-old", "store-new");
|
|
247
|
+
expect(result.id).toBe("store-new");
|
|
248
|
+
expect(result.storeSlug).toBe("store-new");
|
|
249
|
+
});
|
|
250
|
+
it("creates new doc at newSlug path, deletes old doc at currentSlug path", async () => {
|
|
251
|
+
const currentStore = makeStoreDoc({ storeSlug: "store-old" });
|
|
252
|
+
mockDocRef.get
|
|
253
|
+
.mockResolvedValueOnce(makeSnap(currentStore, "store-old"))
|
|
254
|
+
.mockResolvedValueOnce(makeSnap(null));
|
|
255
|
+
await repo.changeSlug("store-old", "store-new");
|
|
256
|
+
// doc() was called for newSlug and currentSlug
|
|
257
|
+
const docCalls = mockCollection.doc.mock.calls.map((c) => c[0]);
|
|
258
|
+
expect(docCalls).toContain("store-new");
|
|
259
|
+
expect(docCalls).toContain("store-old");
|
|
260
|
+
});
|
|
261
|
+
});
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* and helpers for the stores feature.
|
|
6
6
|
*/
|
|
7
7
|
import type { StoreCapability } from "../../auth/permissions/constants";
|
|
8
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
8
9
|
export type ShippingProviderType = "shiprocket" | "self-courier" | "store-pickup" | "custom";
|
|
9
10
|
export interface ShippingProviderConfig {
|
|
10
11
|
/** Stable slug — never changes after creation. */
|
|
@@ -46,8 +47,7 @@ export declare const StoreStatusValues: {
|
|
|
46
47
|
readonly REJECTED: "rejected";
|
|
47
48
|
};
|
|
48
49
|
export type StoreStatus = (typeof StoreStatusValues)[keyof typeof StoreStatusValues];
|
|
49
|
-
export interface StoreDocument {
|
|
50
|
-
id: string;
|
|
50
|
+
export interface StoreDocument extends BaseDocument {
|
|
51
51
|
storeSlug: string;
|
|
52
52
|
ownerId: string;
|
|
53
53
|
storeName: string;
|
|
@@ -123,8 +123,6 @@ export interface StoreDocument {
|
|
|
123
123
|
/** When the seller first connected their account */
|
|
124
124
|
connectedAt?: Date;
|
|
125
125
|
};
|
|
126
|
-
createdAt: Date;
|
|
127
|
-
updatedAt: Date;
|
|
128
126
|
}
|
|
129
127
|
export type { StoreCapability } from "../../auth/permissions/constants";
|
|
130
128
|
export declare const STORE_COLLECTION: "stores";
|
|
@@ -405,8 +405,8 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
405
405
|
createdAt: string | Date | z.objectOutputType<{
|
|
406
406
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
407
407
|
}, z.ZodTypeAny, "passthrough">;
|
|
408
|
-
id: string;
|
|
409
408
|
storeName: string;
|
|
409
|
+
id: string;
|
|
410
410
|
updatedAt: string | Date | z.objectOutputType<{
|
|
411
411
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
412
412
|
}, z.ZodTypeAny, "passthrough">;
|
|
@@ -489,8 +489,8 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
489
489
|
createdAt: string | Date | z.objectInputType<{
|
|
490
490
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
491
491
|
}, z.ZodTypeAny, "passthrough">;
|
|
492
|
-
id: string;
|
|
493
492
|
storeName: string;
|
|
493
|
+
id: string;
|
|
494
494
|
updatedAt: string | Date | z.objectInputType<{
|
|
495
495
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
496
496
|
}, z.ZodTypeAny, "passthrough">;
|
|
@@ -603,8 +603,8 @@ export declare const storeListItemSchema: z.ZodObject<{
|
|
|
603
603
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
604
604
|
}, "strip", z.ZodTypeAny, {
|
|
605
605
|
status: string;
|
|
606
|
-
id: string;
|
|
607
606
|
storeName: string;
|
|
607
|
+
id: string;
|
|
608
608
|
isPublic: boolean;
|
|
609
609
|
storeSlug: string;
|
|
610
610
|
ownerId: string;
|
|
@@ -619,8 +619,8 @@ export declare const storeListItemSchema: z.ZodObject<{
|
|
|
619
619
|
averageRating?: number | undefined;
|
|
620
620
|
}, {
|
|
621
621
|
status: string;
|
|
622
|
-
id: string;
|
|
623
622
|
storeName: string;
|
|
623
|
+
id: string;
|
|
624
624
|
isPublic: boolean;
|
|
625
625
|
storeSlug: string;
|
|
626
626
|
ownerId: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,312 @@
|
|
|
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 } = 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
|
+
firebaseFieldOps: {
|
|
13
|
+
arrayUnion: (...args) => args,
|
|
14
|
+
arrayRemove: (...args) => args,
|
|
15
|
+
increment: (n) => n,
|
|
16
|
+
serverTimestamp: () => new Date(),
|
|
17
|
+
delete: () => null,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
vi.mock("../../../../errors/normalize", () => ({
|
|
22
|
+
normalizeError: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
vi.mock("../../../../monitoring", () => ({
|
|
25
|
+
serverLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
26
|
+
}));
|
|
27
|
+
import { SupportRepository } from "../support.repository";
|
|
28
|
+
import { ACTIVE_TICKET_STATUSES } from "../../schemas/firestore";
|
|
29
|
+
const repo = new SupportRepository();
|
|
30
|
+
function makeTicketDoc(overrides = {}) {
|
|
31
|
+
return {
|
|
32
|
+
id: "ticket-order-issue-ravi-20260627",
|
|
33
|
+
userId: "user-ravi",
|
|
34
|
+
userEmail: "ravi@test.com",
|
|
35
|
+
userDisplayName: "Ravi Kumar",
|
|
36
|
+
category: "order_issue",
|
|
37
|
+
subject: "My order is missing",
|
|
38
|
+
description: "I placed an order but never received it.",
|
|
39
|
+
orderId: "order-3-20260601-abc123",
|
|
40
|
+
status: "open",
|
|
41
|
+
priority: "normal",
|
|
42
|
+
messages: [],
|
|
43
|
+
createdAt: new Date(),
|
|
44
|
+
updatedAt: new Date(),
|
|
45
|
+
...overrides,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function makeMessage(overrides = {}) {
|
|
49
|
+
return {
|
|
50
|
+
id: "msg-1",
|
|
51
|
+
authorId: "user-ravi",
|
|
52
|
+
authorRole: "user",
|
|
53
|
+
body: "I need help with my order.",
|
|
54
|
+
createdAt: new Date(),
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
vi.clearAllMocks();
|
|
60
|
+
db.collection.mockReturnValue(mockCollection);
|
|
61
|
+
mockCollection.doc.mockReturnValue(mockDocRef);
|
|
62
|
+
mockCollection.where.mockReturnValue(mockQuery);
|
|
63
|
+
mockCollection.add = vi.fn().mockResolvedValue({ id: "new-ticket-id", get: vi.fn().mockResolvedValue(makeSnap(makeTicketDoc(), "new-ticket-id")) });
|
|
64
|
+
mockQuery.where.mockReturnValue(mockQuery);
|
|
65
|
+
mockQuery.orderBy.mockReturnValue(mockQuery);
|
|
66
|
+
mockQuery.limit.mockReturnValue(mockQuery);
|
|
67
|
+
mockQuery.select = vi.fn().mockReturnValue(mockQuery);
|
|
68
|
+
mockQuery.get.mockResolvedValue(makeQuerySnap([]));
|
|
69
|
+
mockDocRef.get.mockResolvedValue(makeSnap(makeTicketDoc(), "ticket-1"));
|
|
70
|
+
mockDocRef.set.mockResolvedValue(undefined);
|
|
71
|
+
mockDocRef.update.mockResolvedValue(undefined);
|
|
72
|
+
});
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// createTicket
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
describe("SupportRepository.createTicket", () => {
|
|
77
|
+
it("sets status: open on new tickets", async () => {
|
|
78
|
+
const input = {
|
|
79
|
+
userId: "user-ravi",
|
|
80
|
+
userEmail: "ravi@test.com",
|
|
81
|
+
userDisplayName: "Ravi Kumar",
|
|
82
|
+
category: "order_issue",
|
|
83
|
+
subject: "Missing order",
|
|
84
|
+
description: "Order never arrived.",
|
|
85
|
+
};
|
|
86
|
+
await repo.createTicket(input);
|
|
87
|
+
// create() calls collection.add (BaseRepository.create uses add for auto-ID)
|
|
88
|
+
const addArg = mockCollection.add.mock.calls[0][0];
|
|
89
|
+
expect(addArg.status).toBe("open");
|
|
90
|
+
});
|
|
91
|
+
it("sets priority: normal by default", async () => {
|
|
92
|
+
await repo.createTicket({
|
|
93
|
+
userId: "user-ravi",
|
|
94
|
+
userEmail: "ravi@test.com",
|
|
95
|
+
userDisplayName: "Ravi Kumar",
|
|
96
|
+
category: "general",
|
|
97
|
+
subject: "Question",
|
|
98
|
+
description: "I have a question.",
|
|
99
|
+
});
|
|
100
|
+
const addArg = mockCollection.add.mock.calls[0][0];
|
|
101
|
+
expect(addArg.priority).toBe("normal");
|
|
102
|
+
});
|
|
103
|
+
it("initialises messages: [] on new ticket", async () => {
|
|
104
|
+
await repo.createTicket({
|
|
105
|
+
userId: "user-ravi",
|
|
106
|
+
userEmail: "ravi@test.com",
|
|
107
|
+
userDisplayName: "Ravi Kumar",
|
|
108
|
+
category: "general",
|
|
109
|
+
subject: "Question",
|
|
110
|
+
description: "I have a question.",
|
|
111
|
+
});
|
|
112
|
+
const addArg = mockCollection.add.mock.calls[0][0];
|
|
113
|
+
expect(addArg.messages).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
it("stores orderId when provided", async () => {
|
|
116
|
+
await repo.createTicket({
|
|
117
|
+
userId: "user-ravi",
|
|
118
|
+
userEmail: "ravi@test.com",
|
|
119
|
+
userDisplayName: "Ravi Kumar",
|
|
120
|
+
category: "order_issue",
|
|
121
|
+
subject: "Order missing",
|
|
122
|
+
description: "My order is gone.",
|
|
123
|
+
orderId: "order-3-20260601-abc123",
|
|
124
|
+
});
|
|
125
|
+
const addArg = mockCollection.add.mock.calls[0][0];
|
|
126
|
+
expect(addArg.orderId).toBe("order-3-20260601-abc123");
|
|
127
|
+
});
|
|
128
|
+
it("sets createdAt and updatedAt timestamps", async () => {
|
|
129
|
+
await repo.createTicket({
|
|
130
|
+
userId: "user-ravi",
|
|
131
|
+
userEmail: "ravi@test.com",
|
|
132
|
+
userDisplayName: "Ravi Kumar",
|
|
133
|
+
category: "general",
|
|
134
|
+
subject: "Q",
|
|
135
|
+
description: "D",
|
|
136
|
+
});
|
|
137
|
+
const addArg = mockCollection.add.mock.calls[0][0];
|
|
138
|
+
expect(addArg.createdAt).toBeInstanceOf(Date);
|
|
139
|
+
expect(addArg.updatedAt).toBeInstanceOf(Date);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// countActiveTickets
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
describe("SupportRepository.countActiveTickets", () => {
|
|
146
|
+
it("runs a query per ACTIVE_TICKET_STATUS in parallel", async () => {
|
|
147
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
148
|
+
await repo.countActiveTickets("user-ravi");
|
|
149
|
+
// One query per active status
|
|
150
|
+
expect(mockQuery.get).toHaveBeenCalledTimes(ACTIVE_TICKET_STATUSES.length);
|
|
151
|
+
});
|
|
152
|
+
it("returns sum of sizes across all active status queries", async () => {
|
|
153
|
+
// Each of the 3 queries returns size=2 → total should be 6
|
|
154
|
+
mockQuery.get.mockResolvedValue({ empty: false, size: 2, docs: [] });
|
|
155
|
+
const count = await repo.countActiveTickets("user-ravi");
|
|
156
|
+
expect(count).toBe(ACTIVE_TICKET_STATUSES.length * 2);
|
|
157
|
+
});
|
|
158
|
+
it("returns 0 when no active tickets", async () => {
|
|
159
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
160
|
+
const count = await repo.countActiveTickets("user-nobody");
|
|
161
|
+
expect(count).toBe(0);
|
|
162
|
+
});
|
|
163
|
+
it("queries by userId on each sub-query", async () => {
|
|
164
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
165
|
+
await repo.countActiveTickets("user-specific");
|
|
166
|
+
expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("userId"), "==", "user-specific");
|
|
167
|
+
});
|
|
168
|
+
it("uses select() stub reads (minimal cost)", async () => {
|
|
169
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
170
|
+
await repo.countActiveTickets("user-ravi");
|
|
171
|
+
expect(mockQuery.select).toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// getActiveCategoryTicket
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
describe("SupportRepository.getActiveCategoryTicket", () => {
|
|
178
|
+
it("returns existing ticket for userId + category with status waiting_on_user", async () => {
|
|
179
|
+
const ticket = makeTicketDoc({ status: "waiting_on_user", category: "order_issue" });
|
|
180
|
+
mockQuery.get.mockResolvedValue({
|
|
181
|
+
empty: false,
|
|
182
|
+
size: 1,
|
|
183
|
+
docs: [makeSnap(ticket, ticket.id)],
|
|
184
|
+
});
|
|
185
|
+
const result = await repo.getActiveCategoryTicket("user-ravi", "order_issue");
|
|
186
|
+
expect(result).not.toBeNull();
|
|
187
|
+
expect(result?.category).toBe("order_issue");
|
|
188
|
+
});
|
|
189
|
+
it("returns null when no matching ticket found", async () => {
|
|
190
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
191
|
+
const result = await repo.getActiveCategoryTicket("user-ravi", "general");
|
|
192
|
+
expect(result).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
it("queries specifically for status == waiting_on_user", async () => {
|
|
195
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
196
|
+
await repo.getActiveCategoryTicket("user-ravi", "billing_payment");
|
|
197
|
+
expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("status"), "==", "waiting_on_user");
|
|
198
|
+
});
|
|
199
|
+
it("queries by userId and category", async () => {
|
|
200
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
201
|
+
await repo.getActiveCategoryTicket("user-xyz", "account");
|
|
202
|
+
expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("userId"), "==", "user-xyz");
|
|
203
|
+
expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("category"), "==", "account");
|
|
204
|
+
});
|
|
205
|
+
it("uses limit(1) to avoid scanning all tickets", async () => {
|
|
206
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
207
|
+
await repo.getActiveCategoryTicket("user-ravi", "general");
|
|
208
|
+
expect(mockQuery.limit).toHaveBeenCalledWith(1);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// addMessage
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
describe("SupportRepository.addMessage", () => {
|
|
215
|
+
it("appends message using arrayUnion (does not overwrite messages array)", async () => {
|
|
216
|
+
const message = makeMessage();
|
|
217
|
+
await repo.addMessage("ticket-1", message);
|
|
218
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
219
|
+
// arrayUnion mock returns its arguments as an array
|
|
220
|
+
expect(updateArg.messages).toBeDefined();
|
|
221
|
+
});
|
|
222
|
+
it("updates updatedAt timestamp on message add", async () => {
|
|
223
|
+
await repo.addMessage("ticket-1", makeMessage());
|
|
224
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
225
|
+
expect(updateArg.updatedAt).toBeInstanceOf(Date);
|
|
226
|
+
});
|
|
227
|
+
it("sets newStatus when provided", async () => {
|
|
228
|
+
await repo.addMessage("ticket-1", makeMessage(), "in_progress");
|
|
229
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
230
|
+
expect(updateArg.status).toBe("in_progress");
|
|
231
|
+
});
|
|
232
|
+
it("does NOT set status field when newStatus not provided", async () => {
|
|
233
|
+
await repo.addMessage("ticket-1", makeMessage());
|
|
234
|
+
const updateArg = mockDocRef.update.mock.calls[0][0];
|
|
235
|
+
expect(Object.keys(updateArg)).not.toContain("status");
|
|
236
|
+
});
|
|
237
|
+
it("calls doc(ticketId).update — not collection.add", async () => {
|
|
238
|
+
await repo.addMessage("ticket-1", makeMessage());
|
|
239
|
+
expect(mockCollection.doc).toHaveBeenCalledWith("ticket-1");
|
|
240
|
+
expect(mockDocRef.update).toHaveBeenCalledOnce();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// assignTicket
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
describe("SupportRepository.assignTicket", () => {
|
|
247
|
+
it("sets assignedTo field", async () => {
|
|
248
|
+
await repo.assignTicket("ticket-1", "employee-uid-1", "Support Agent");
|
|
249
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ assignedTo: "employee-uid-1" }));
|
|
250
|
+
});
|
|
251
|
+
it("sets assignedToName field", async () => {
|
|
252
|
+
await repo.assignTicket("ticket-1", "emp-1", "Alice Support");
|
|
253
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ assignedToName: "Alice Support" }));
|
|
254
|
+
});
|
|
255
|
+
it("sets updatedAt timestamp", async () => {
|
|
256
|
+
await repo.assignTicket("ticket-1", "emp-1", "Agent");
|
|
257
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ updatedAt: expect.any(Date) }));
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// updateTicketStatus
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
describe("SupportRepository.updateTicketStatus", () => {
|
|
264
|
+
it("applies provided status update", async () => {
|
|
265
|
+
await repo.updateTicketStatus("ticket-1", { status: "resolved" });
|
|
266
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "resolved" }));
|
|
267
|
+
});
|
|
268
|
+
it("sets updatedAt on every status change", async () => {
|
|
269
|
+
await repo.updateTicketStatus("ticket-1", { status: "closed" });
|
|
270
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ updatedAt: expect.any(Date) }));
|
|
271
|
+
});
|
|
272
|
+
it("stores resolvedAt when provided", async () => {
|
|
273
|
+
const resolvedAt = new Date();
|
|
274
|
+
await repo.updateTicketStatus("ticket-1", { status: "resolved", resolvedAt });
|
|
275
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ resolvedAt }));
|
|
276
|
+
});
|
|
277
|
+
it("stores priority update when provided", async () => {
|
|
278
|
+
await repo.updateTicketStatus("ticket-1", { priority: "high" });
|
|
279
|
+
expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ priority: "high" }));
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// getActiveOrderTicket
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
describe("SupportRepository.getActiveOrderTicket", () => {
|
|
286
|
+
it("returns ticket matching userId + orderId with active status", async () => {
|
|
287
|
+
const ticket = makeTicketDoc({ status: "open", orderId: "order-1" });
|
|
288
|
+
mockQuery.get.mockResolvedValue({
|
|
289
|
+
empty: false,
|
|
290
|
+
size: 1,
|
|
291
|
+
docs: [{ id: ticket.id, data: () => ticket }],
|
|
292
|
+
});
|
|
293
|
+
const result = await repo.getActiveOrderTicket("user-ravi", "order-1");
|
|
294
|
+
expect(result).not.toBeNull();
|
|
295
|
+
expect(result?.orderId).toBe("order-1");
|
|
296
|
+
});
|
|
297
|
+
it("returns null when no active order ticket exists", async () => {
|
|
298
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
299
|
+
const result = await repo.getActiveOrderTicket("user-ravi", "order-nonexistent");
|
|
300
|
+
expect(result).toBeNull();
|
|
301
|
+
});
|
|
302
|
+
it("queries status in ACTIVE_TICKET_STATUSES", async () => {
|
|
303
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
304
|
+
await repo.getActiveOrderTicket("user-ravi", "order-1");
|
|
305
|
+
expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("status"), "in", ACTIVE_TICKET_STATUSES);
|
|
306
|
+
});
|
|
307
|
+
it("uses limit(1)", async () => {
|
|
308
|
+
mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
|
|
309
|
+
await repo.getActiveOrderTicket("user-ravi", "order-1");
|
|
310
|
+
expect(mockQuery.limit).toHaveBeenCalledWith(1);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - No creation if user has create_support_tickets soft ban
|
|
15
15
|
* - No new ticket in the same category while existing one is waiting_on_user
|
|
16
16
|
*/
|
|
17
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
17
18
|
export declare const TicketCategoryValues: {
|
|
18
19
|
readonly ORDER_ISSUE: "order_issue";
|
|
19
20
|
readonly BILLING_PAYMENT: "billing_payment";
|
|
@@ -70,8 +71,7 @@ export interface TicketRelatedParties {
|
|
|
70
71
|
productId?: string;
|
|
71
72
|
bidId?: string;
|
|
72
73
|
}
|
|
73
|
-
export interface SupportTicketDocument {
|
|
74
|
-
id: string;
|
|
74
|
+
export interface SupportTicketDocument extends BaseDocument {
|
|
75
75
|
userId: string;
|
|
76
76
|
/** Denormalized — for admin table display. */
|
|
77
77
|
userEmail: string;
|
|
@@ -103,8 +103,6 @@ export interface SupportTicketDocument {
|
|
|
103
103
|
messages: TicketMessage[];
|
|
104
104
|
resolvedAt?: Date;
|
|
105
105
|
closedAt?: Date;
|
|
106
|
-
createdAt: Date;
|
|
107
|
-
updatedAt: Date;
|
|
108
106
|
}
|
|
109
107
|
export type SupportTicketCreateInput = Pick<SupportTicketDocument, "userId" | "userEmail" | "userDisplayName" | "category" | "subject" | "description" | "orderId">;
|
|
110
108
|
export type SupportTicketUpdateInput = Partial<Pick<SupportTicketDocument, "status" | "priority" | "assignedTo" | "assignedToName" | "internalNotes" | "resolvedAt" | "closedAt" | "relatedParties">>;
|