@mohasinac/appkit 4.42.1 → 4.42.2
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/server/features/offers/adapters.d.ts +29 -7
- package/dist/_internal/server/features/offers/adapters.js +14 -3
- package/dist/_internal/server/features/orders/adapters.js +54 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.js +1 -0
- package/dist/features/account/components/UserOrdersView.js +30 -2
- package/dist/features/account/hooks/useAddresses.d.ts +21 -6
- package/dist/features/account/hooks/useAddresses.js +1 -1
- package/dist/features/addresses/hooks/usePostalLookup.js +84 -12
- package/dist/features/admin/components/AdminCategoriesView.js +37 -2
- package/dist/features/admin/components/AdminOffersView.js +2 -2
- package/dist/features/admin/constants/filter-tabs.d.ts +25 -0
- package/dist/features/admin/constants/filter-tabs.js +30 -0
- package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
- package/dist/features/admin/hooks/useAdminListingData.js +45 -9
- package/dist/features/cart/schemas/index.d.ts +15 -0
- package/dist/features/cart/schemas/index.js +2 -0
- package/dist/features/cart/types/index.d.ts +22 -0
- package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
- package/dist/features/events/actions/event-actions.d.ts +13 -0
- package/dist/features/events/actions/event-actions.js +75 -1
- package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
- package/dist/features/events/repository/event-entry.repository.js +36 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +21 -3
- package/dist/features/orders/components/OrdersList.js +2 -1
- package/dist/features/orders/index.d.ts +1 -0
- package/dist/features/orders/index.js +1 -0
- package/dist/features/orders/types/index.d.ts +2 -0
- package/dist/features/orders/utils/order-ref.d.ts +45 -0
- package/dist/features/orders/utils/order-ref.js +58 -0
- package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
- package/dist/features/products/components/MakeOfferButton.js +23 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
- package/dist/features/products/components/ProductDetailPageView.js +3 -2
- package/dist/features/products/repository/products.repository.js +29 -2
- package/dist/features/seller/actions/offer-actions.js +32 -0
- package/dist/features/seller/components/QuickProductForm.js +15 -1
- package/dist/features/seller/components/SellerBidsView.js +3 -1
- package/dist/features/seller/components/SellerOffersView.js +2 -2
- package/dist/features/seller/components/SellerOrdersView.js +49 -6
- package/dist/features/seller/components/SellerProductsCards.js +23 -1
- package/dist/features/seller/components/SellerProductsView.js +27 -2
- package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
- package/dist/features/seller/hooks/useSellerListingData.js +17 -19
- package/dist/features/shell/SectionForm.js +33 -2
- package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
- package/dist/features/tester/seed-data/_happy-path.js +710 -0
- package/dist/features/tester/seed-data/_money-flows.js +34 -23
- package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
- package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
- package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
- package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
- package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
- package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
- package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
- package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
- package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
- package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
- package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
- package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
- package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
- package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
- package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
- package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
- package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
- package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
- package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
- package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
- package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
- package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
- package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
- package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
- package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
- package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
- package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
- package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
- package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
- package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
- package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
- package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
- package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
- package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +2 -1
- package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
- package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
- package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
- package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
- package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
- package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
- package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
- package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
- package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
- package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
- package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
- package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
- package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
- package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
- package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
- package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
- package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
- package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
- package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
- package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +1 -1
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
- package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
- package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
- package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
- package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
- package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
- package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
- package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
- package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
- package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
- package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
- package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
- package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
- package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
- package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
- package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
- package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
- package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
- package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
- package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
- package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
- package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
- package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
- package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
- package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
- package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
- package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
- package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
- package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
- package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
- package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
- package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +4 -4
- package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
- package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
- package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
- package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +123 -21
- package/dist/features/tester/utils/phases.d.ts +17 -0
- package/dist/features/tester/utils/phases.js +29 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/seo/json-ld.d.ts +23 -0
- package/dist/seo/json-ld.js +3 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/package.json +1 -1
|
@@ -32,7 +32,7 @@ export const authored = {
|
|
|
32
32
|
"Open /admin/sections and open a section for edit.",
|
|
33
33
|
"Change its heading to 'QA Section Heading Probe' and save.",
|
|
34
34
|
"Reload the admin page and read the heading.",
|
|
35
|
-
"
|
|
35
|
+
"Sign out, open /, and find that section on the homepage.",
|
|
36
36
|
"Read its heading there.",
|
|
37
37
|
"Restore the original heading.",
|
|
38
38
|
],
|
|
@@ -79,7 +79,7 @@ export const authored = {
|
|
|
79
79
|
steps: [
|
|
80
80
|
SIGN_IN_ADMIN,
|
|
81
81
|
"Open /admin/sections and disable a section rather than deleting it.",
|
|
82
|
-
"
|
|
82
|
+
"Sign out, open /, and check the section is gone from the homepage.",
|
|
83
83
|
"Return to the admin page and check the section is still listed, marked as disabled.",
|
|
84
84
|
"Re-enable it and check it returns publicly.",
|
|
85
85
|
"Read the two controls side by side and check which is which is obvious without trying them.",
|
|
@@ -128,27 +128,30 @@ export const authored = {
|
|
|
128
128
|
startPage: "/admin/carousel",
|
|
129
129
|
steps: [
|
|
130
130
|
SIGN_IN_ADMIN,
|
|
131
|
-
"Open /admin/carousel and
|
|
132
|
-
"Read whether the page states a maximum.",
|
|
133
|
-
"Activate
|
|
134
|
-
"Read what happens
|
|
135
|
-
"
|
|
136
|
-
"
|
|
131
|
+
"Open /admin/carousel and write down which slides are active — five of the six seeded slides are, and one is inactive.",
|
|
132
|
+
"Read whether the page states a maximum anywhere.",
|
|
133
|
+
"Activate the sixth slide and save.",
|
|
134
|
+
"Read what happens: a refusal naming the maximum, or a successful save.",
|
|
135
|
+
"Reload /admin/carousel and count the active slides again.",
|
|
136
|
+
"Sign out, open /, and count the slides actually in the homepage rotation.",
|
|
137
|
+
"Sign back in and restore the original active set from step 2.",
|
|
137
138
|
],
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
inputs: { maxActiveSlides: 5, seededSlides: 6 },
|
|
140
|
+
expectedBehaviour: "Exceeding the active-slide maximum should be refused with a reason. Silently accepting the extra slide and then rendering only the first few is worse than refusing — the admin sees their slide saved and never learns it is not being shown.",
|
|
141
|
+
expectedUiState: "🛑 EXPECT THE SAVE TO SUCCEED, AND RECORD IT AS A FAILURE. The limit is enforced in the appkit handlers mounted at /api/carousel, and the admin editor posts to /api/admin/carousel, whose `active` field is a bare optional boolean with no count check. So expect six active slides in the admin list and FIVE in the homepage rotation — the public read slices to five. Report both numbers; the gap between them is the finding, and it is exactly the saved-but-not-rendered slide this case names.",
|
|
142
|
+
expectedData: { activeSlidesInAdmin: 6, slidesOnHomepage: 5 },
|
|
143
|
+
endResult: "The original active set is restored, so the homepage rotates the same five slides as before.",
|
|
141
144
|
},
|
|
142
145
|
"checklist-admin-content-deletes-blog-edit-persists": {
|
|
143
146
|
roles: ["admin"],
|
|
144
147
|
startPage: "/admin/blog",
|
|
145
148
|
steps: [
|
|
146
149
|
SIGN_IN_ADMIN,
|
|
147
|
-
"Open /admin/blog and open
|
|
150
|
+
"Open /admin/blog and open blog-spot-genuine-takara-tomy-beyblade for edit, writing down its Slug.",
|
|
148
151
|
"Change its title to 'QA Blog Title Probe', edit a sentence of its body, and replace its cover image with /test-media/sample-image.png.",
|
|
149
152
|
"Save.",
|
|
150
153
|
"Reload the admin editor and check all three changes are there.",
|
|
151
|
-
"
|
|
154
|
+
"Sign out, open /blog/blog-spot-genuine-takara-tomy-beyblade, and check all three again.",
|
|
152
155
|
"Restore the original title, sentence and cover image.",
|
|
153
156
|
],
|
|
154
157
|
inputs: { title: "QA Blog Title Probe", coverImage: "/test-media/sample-image.png" },
|
|
@@ -162,7 +165,7 @@ export const authored = {
|
|
|
162
165
|
steps: [
|
|
163
166
|
SIGN_IN_ADMIN,
|
|
164
167
|
"Open /admin/blog and create a post titled 'QA Disposable Post', publish it.",
|
|
165
|
-
"Open
|
|
168
|
+
"Open /blog/qa-disposable-post — the slug is the title slugified — and note that it renders.",
|
|
166
169
|
"Return to /admin/blog and delete it, confirming.",
|
|
167
170
|
"Reload the public URL with the network panel open.",
|
|
168
171
|
"Read the response status.",
|
|
@@ -200,7 +203,7 @@ export const authored = {
|
|
|
200
203
|
"Return to /admin/products and delete it, confirming.",
|
|
201
204
|
"Reload the public URL with the network panel open and read the status.",
|
|
202
205
|
"Open /products and search for it.",
|
|
203
|
-
"Open
|
|
206
|
+
"Open /search and search for it there too.",
|
|
204
207
|
],
|
|
205
208
|
inputs: { title: "QA Disposable Listing", price: 299 },
|
|
206
209
|
expectedBehaviour: "A deleted listing leaves every grid it appeared in, not only its own page. Search is the one most likely to lag, because its index is written on save — a listing removed from the collection but left in the index renders a result card that 404s when clicked.",
|
|
@@ -213,18 +216,21 @@ export const authored = {
|
|
|
213
216
|
startPage: "/admin/products",
|
|
214
217
|
steps: [
|
|
215
218
|
SIGN_IN_ADMIN,
|
|
216
|
-
"Open /admin/orders
|
|
217
|
-
"Open /admin/products
|
|
218
|
-
"Read whether the interface refuses, warns, or deletes.",
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
"
|
|
219
|
+
"Open /admin/orders, open any order, and write down its id and the exact title of one product it contains.",
|
|
220
|
+
"Open /admin/products, find that product, and delete it.",
|
|
221
|
+
"Read whether the interface refuses, warns about the order, or simply deletes.",
|
|
222
|
+
"Reload /admin/products and confirm whether the product is gone.",
|
|
223
|
+
"Reopen the order from step 2 and read whether its rows still render with that title and price.",
|
|
224
|
+
"Click the product link on the order's row and read exactly where it lands — a product page, a 404, or nothing.",
|
|
225
|
+
"Repeat the link check as the BUYER on /user/orders for the same order, since that is where a real person would meet it.",
|
|
222
226
|
],
|
|
227
|
+
inputs: { deleteIsHard: true },
|
|
223
228
|
expectedBehaviour: "An order's rows denormalise the title, price and image they display, so an existing order survives the product's deletion — but the buyer's link into that product breaks. Either outcome can be defensible; what matters is that it is deliberate and stated rather than discovered by a buyer opening an old order.",
|
|
224
|
-
expectedUiState: "
|
|
225
|
-
|
|
229
|
+
expectedUiState: "🛑 EXPECT A HARD DELETE. `adminDeleteProduct` checks only that a prize draw is not locked and then deletes the document; there is no order check anywhere, and no archive path. So expect no refusal and no warning. The order should still render its rows from the denormalised title and price — if it does NOT, that is a far more serious finding than the broken link. Record where the product link lands, on both the admin and the buyer surfaces.",
|
|
230
|
+
expectedData: { deleteRefused: false, orderRowsStillRender: true },
|
|
231
|
+
endResult: "A seeded product has been destroyed. `products` is seed-owned and is re-seeded around every run, so this is recoverable — but re-seed rather than attempting a manual repair, and do not run this case against a product another case in the same batch depends on.",
|
|
226
232
|
needsReview: true,
|
|
227
|
-
reviewNote: "
|
|
233
|
+
reviewNote: "What the code does is now settled — a hard delete with no order guard. What is NOT settled is whether that is the intended behaviour, so the case records the consequence for a real buyer rather than asserting a verdict on the design.",
|
|
228
234
|
},
|
|
229
235
|
"checklist-admin-content-deletes-delete-confirmations-name-the-record": {
|
|
230
236
|
roles: ["admin"],
|
|
@@ -13,6 +13,36 @@
|
|
|
13
13
|
* checking the interface. Two cases here read the PUBLIC response rather than the
|
|
14
14
|
* admin form.
|
|
15
15
|
*
|
|
16
|
+
* VERIFIED FIXED 2026-09-29 before asserting it: `adSettings` is now in
|
|
17
|
+
* `PRIVATE_SITE_SETTINGS_FIELDS`, and `/api/ads` serves the public view behind
|
|
18
|
+
* `if (String(item.status || "") !== "active") return false`. So the ads case
|
|
19
|
+
* should PASS; it is kept because the leak was invisible from the admin seat.
|
|
20
|
+
*
|
|
21
|
+
* 🛑 THE NAVIGATION EDITOR REACHES NO PUBLIC SURFACE.
|
|
22
|
+
*
|
|
23
|
+
* `AdminNavigationView` reads and writes `siteSettings.navbarConfig.navItems`
|
|
24
|
+
* through /api/admin/navigation. The public navbar renders `MAIN_NAV_ITEMS`
|
|
25
|
+
* from `src/constants/navigation.tsx` — a static array, filtered only by the
|
|
26
|
+
* listing-type toggles. A repo-wide grep for `navbarConfig` finds the two admin
|
|
27
|
+
* routes, the `/api/admin/site` field list, the seed and the projection, and
|
|
28
|
+
* **no renderer at all**. So it is a complete CRUD surface over data nothing
|
|
29
|
+
* displays, and the projection lists `navbarConfig` as private with the reason
|
|
30
|
+
* "rendered server-side" — which is not true of anything. Recorded in
|
|
31
|
+
* OUTOFSCOPE; the case below now expects the public half to fail.
|
|
32
|
+
*
|
|
33
|
+
* 🛑 THERE IS NO FEATURE-FLAGS SURFACE. The `featureFlags` group was DELETED on
|
|
34
|
+
* 2026-08-29 — "11 of its 14 keys had zero readers, and the survivors were not
|
|
35
|
+
* flags at all". The survivors moved: listing and category types to the
|
|
36
|
+
* `listings` tab (a PRODUCT CONTROL, per that schema comment), and
|
|
37
|
+
* `smsVerification` / `adminCheckoutBypass` to the `payment` group. The last
|
|
38
|
+
* case is rewritten onto the control that actually exists and actually reaches
|
|
39
|
+
* public behaviour.
|
|
40
|
+
*
|
|
41
|
+
* /admin/site has TWENTY tabs, not nineteen: about, branding, appearance,
|
|
42
|
+
* themes, announcement, seo, contact, watermark, fees, procurement, emi, gst,
|
|
43
|
+
* listings, integrations, shipping, auction, limits, whatsapp, notifications,
|
|
44
|
+
* legal.
|
|
45
|
+
*
|
|
16
46
|
* @tag domain:tester
|
|
17
47
|
* @tag layer:seed
|
|
18
48
|
* @tag pattern:none
|
|
@@ -26,18 +56,21 @@ export const authored = {
|
|
|
26
56
|
startPage: "/admin/ads",
|
|
27
57
|
steps: [
|
|
28
58
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
29
|
-
"Open
|
|
30
|
-
"
|
|
31
|
-
"
|
|
59
|
+
"Open /admin/ads and read every existing ad, its status and its placement.",
|
|
60
|
+
"In the provider settings, read how 'AdSense client id' and 'Third-party script URL' are presented — whether a stored value is shown or only a masked placeholder.",
|
|
61
|
+
"Create an ad named 'QA Ad crud-preview' in a DRAFT state with a placement selected.",
|
|
62
|
+
"Save, RELOAD, and read every field.",
|
|
32
63
|
"Use the preview and read what it renders.",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
64
|
+
"Sign out, open /, and check the draft ad is NOT rendered anywhere.",
|
|
65
|
+
"View that page's source and search it for 'QA Ad crud-preview' and for any AdSense client id.",
|
|
66
|
+
"Open /api/ads signed out and check the draft is absent from the response.",
|
|
67
|
+
"Sign back in and delete the ad.",
|
|
36
68
|
],
|
|
37
|
-
|
|
69
|
+
// audit-hardcoded-api-routes-ok: a URL the tester opens by hand, not a call site.
|
|
70
|
+
inputs: { name: "QA Ad crud-preview", status: "draft", publicEndpoint: "/api/ads" },
|
|
38
71
|
expectedBehaviour: "A draft ad is invisible publicly and its provider credentials never leave the server. The ads group was written by the admin form while undeclared on the settings type, so it was published wholesale by an unauthenticated endpoint — including drafts and unmasked credentials — and nothing type-driven could see it.",
|
|
39
|
-
expectedUiState: "The ad round-trips
|
|
40
|
-
expectedData: { credentialInPublicSource: 0 },
|
|
72
|
+
expectedUiState: "The ad round-trips. Provider credentials appear only as masked placeholders in the admin form, never as editable stored values. The public page does not render the draft, its source contains neither the ad's name nor an AdSense id, and /api/ads omits it — that endpoint keeps only `status === \"active\"`. A draft appearing publicly, or a credential in the source, is a leak rather than a display bug.",
|
|
73
|
+
expectedData: { credentialInPublicSource: 0, draftInPublicAdsResponse: false },
|
|
41
74
|
endResult: "The ad is deleted by the final step.",
|
|
42
75
|
},
|
|
43
76
|
"checklist-admin-content-marketing-newsletter-export-admin": {
|
|
@@ -45,13 +78,14 @@ export const authored = {
|
|
|
45
78
|
startPage: "/admin/newsletter",
|
|
46
79
|
steps: [
|
|
47
80
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
48
|
-
"Open
|
|
81
|
+
"Open /admin/newsletter and read the subscriber count and the rows.",
|
|
49
82
|
"Read whether subscriber email addresses are shown in full or masked.",
|
|
50
|
-
"
|
|
51
|
-
"Navigate
|
|
83
|
+
"Click 'Export CSV' and read what the page does IMMEDIATELY — whether it returns at once with a job accepted, or blocks.",
|
|
84
|
+
"Navigate to /admin and back to /admin/newsletter, then read whether the export's progress is still reported.",
|
|
52
85
|
"Wait for it to finish and open the exported file.",
|
|
53
86
|
"Compare the file's row count against the subscriber count shown.",
|
|
54
87
|
],
|
|
88
|
+
inputs: { action: "Export CSV" },
|
|
55
89
|
expectedBehaviour: "The export runs as a background job rather than inside the request — an unbounded scan of every subscriber is exactly the shape that dies at the ten-second function ceiling in production while a small local list passes. The page subscribes to the job's progress instead of waiting on a response.",
|
|
56
90
|
expectedUiState: "The export returns immediately with a job accepted rather than freezing the page. Progress survives navigating away and back. The finished file's row count matches the subscriber count. A request that hangs and then times out is the failure.",
|
|
57
91
|
endResult: "An export file exists. Note whether subscriber emails are exposed in the LIST as well as in the file — the file is expected to carry them, the list is a judgement.",
|
|
@@ -60,12 +94,12 @@ export const authored = {
|
|
|
60
94
|
roles: ["admin", "guest"],
|
|
61
95
|
startPage: "/contact",
|
|
62
96
|
steps: [
|
|
63
|
-
"
|
|
64
|
-
"Sign in as admin@letitrip.in / TempPass123
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"Use
|
|
97
|
+
"Signed out, open /contact and submit a message: name 'QA Contact submissions-admin', email qa-contact-admin@mailnull.com, subject 'Checklist submission', message 'Sent by the tester checklist.'.",
|
|
98
|
+
"Sign in as admin@letitrip.in / TempPass123! and open /admin/contact.",
|
|
99
|
+
"Find that message and read its status — a new submission reads 'new'.",
|
|
100
|
+
"Open the row menu and use its View action, then read every field in the drawer and confirm the full message body is there rather than a truncated preview.",
|
|
101
|
+
"Use the row menu's mark-read action, RELOAD, and read the status.",
|
|
102
|
+
"Use the row menu's archive action, RELOAD, and read the status again.",
|
|
69
103
|
"Search zzzznope and read the result count.",
|
|
70
104
|
],
|
|
71
105
|
inputs: {
|
|
@@ -74,7 +108,7 @@ export const authored = {
|
|
|
74
108
|
message: "Sent by the tester checklist.",
|
|
75
109
|
},
|
|
76
110
|
expectedBehaviour: "A submitted message arrives in the admin surface with its full body readable. This is the round trip the contact form's own case cannot prove — a form that shows a confirmation and delivers nothing looks identical to one that works, from the sender's side.",
|
|
77
|
-
expectedUiState: "The submission appears with its name, email, subject and full body.
|
|
111
|
+
expectedUiState: "The submission appears with its name, email, subject and full body. The View action opens a drawer rather than only offering mutations — a row you can act on but not read is the acting-blind shape. Status moves new → read → resolved and each survives a reload. 'zzzznope' returns zero rows rather than the full list.",
|
|
78
112
|
expectedData: { nonsenseResultCount: 0 },
|
|
79
113
|
endResult: "The submission remains, marked handled. Its arrival is the assertion — a missing message means the contact form delivers nowhere.",
|
|
80
114
|
},
|
|
@@ -83,7 +117,7 @@ export const authored = {
|
|
|
83
117
|
startPage: "/admin/media",
|
|
84
118
|
steps: [
|
|
85
119
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
86
|
-
"Open
|
|
120
|
+
"Open /admin/media and read the grid.",
|
|
87
121
|
"Check every tile renders its image rather than an empty bordered box.",
|
|
88
122
|
"Read the filter and sort controls and use each, confirming the results change.",
|
|
89
123
|
"Search for a known filename, then search zzzznope and read the count.",
|
|
@@ -101,33 +135,36 @@ export const authored = {
|
|
|
101
135
|
startPage: "/admin/navigation",
|
|
102
136
|
steps: [
|
|
103
137
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
104
|
-
"Open /admin/navigation and
|
|
138
|
+
"Open /admin/navigation and write down every nav item, its label, its destination and its order.",
|
|
105
139
|
"Create one labelled 'QA Nav navigation-editor' pointing at /about, and save.",
|
|
106
|
-
"RELOAD and read every field of it.",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
140
|
+
"RELOAD the editor and read every field of it — this half is the editor's own round trip.",
|
|
141
|
+
"Sign out and open /. Read the public navbar and record every item it shows.",
|
|
142
|
+
"Compare that list against the editor's: is 'QA Nav navigation-editor' there, and do the two lists agree at all?",
|
|
143
|
+
"Sign back in, reorder two items, save, reload, then sign out and check whether the public navbar order followed.",
|
|
144
|
+
"Sign back in, delete the item, restore the original order, and confirm the editor matches what was written down in step 2.",
|
|
111
145
|
],
|
|
112
146
|
inputs: { label: "QA Nav navigation-editor", href: "/about" },
|
|
113
|
-
expectedBehaviour: "
|
|
114
|
-
expectedUiState: "
|
|
115
|
-
|
|
147
|
+
expectedBehaviour: "An editor that saves is not an editor that reaches anything. The claim under test is that /admin/navigation is the source of the public navbar — a create, a reorder and a delete should all show up there.",
|
|
148
|
+
expectedUiState: "🛑 EXPECT THE PUBLIC HALF TO FAIL, AND RECORD IT AS A FAILURE. The editor round trip should work: the item saves and survives a reload. But the public navbar is `MAIN_NAV_ITEMS`, a static array in src/constants/navigation.tsx filtered only by the listing-type toggles, and nothing anywhere reads `navbarConfig.navItems` — so expect the new item to be absent from /, the reorder to change nothing publicly, and the two lists to disagree. Record both lists side by side; that comparison is the finding.",
|
|
149
|
+
expectedData: { savedInEditor: true, appearsInPublicNavbar: false },
|
|
150
|
+
endResult: "The item is deleted and the original order restored, so the editor matches step 2 again. Keep the halves apart: the editor persisting correctly and reaching nothing is a different defect from an editor that fails to save.",
|
|
116
151
|
},
|
|
117
152
|
"checklist-admin-content-marketing-settings-navigation-actions": {
|
|
118
153
|
roles: ["admin"],
|
|
119
154
|
startPage: "/admin/site",
|
|
120
155
|
steps: [
|
|
121
156
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
122
|
-
"Open /admin/site and read every tab in the strip.",
|
|
123
|
-
"Open the header search and type
|
|
157
|
+
"Open /admin/site and read every tab in the strip — there should be twenty: about, branding, appearance, themes, announcement, seo, contact, watermark, fees, procurement, emi, gst, listings, integrations, shipping, auction, limits, whatsapp, notifications, legal.",
|
|
158
|
+
"Open the header search and type 'fees'.",
|
|
124
159
|
"Click the result and read which tab opens.",
|
|
125
|
-
"Read the URL and check it names the tab.",
|
|
126
|
-
"Copy that URL, open it in a new tab, and confirm it opens on the same tab.",
|
|
127
|
-
"Open /admin/site
|
|
160
|
+
"Read the URL and check it names the tab rather than being bare /admin/site.",
|
|
161
|
+
"Copy that URL, open it in a new browser tab, and confirm it opens on the same settings tab.",
|
|
162
|
+
"Open /admin/site?tab=zzzznope and read what happens.",
|
|
128
163
|
],
|
|
129
|
-
|
|
164
|
+
inputs: { searchTerm: "fees", expectedTab: "fees", unknownTab: "zzzznope", tabCount: 20 },
|
|
165
|
+
expectedBehaviour: "Settings are reachable by deep link and by search, and search reaches individual tabs rather than only the page. Landing on the first of twenty tabs and leaving the admin to hunt is a fail rather than a partial pass, and an unknown tab value falls back quietly because the realistic cause is a stale bookmark.",
|
|
130
166
|
expectedUiState: "The search result opens the named tab, the URL carries it, and the copied URL reopens the same tab. An unknown value opens the default with no error banner and no blank panel.",
|
|
167
|
+
expectedData: { tabCount: 20 },
|
|
131
168
|
endResult: "Read-only; nothing is saved.",
|
|
132
169
|
},
|
|
133
170
|
"checklist-admin-content-marketing-features-feature-flags-admin": {
|
|
@@ -135,17 +172,17 @@ export const authored = {
|
|
|
135
172
|
startPage: "/admin/site",
|
|
136
173
|
steps: [
|
|
137
174
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
138
|
-
"Open
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"Open /api/site-settings in a private window and read whether any flag is present in the response.",
|
|
175
|
+
"Open /admin/site, then its 'Listings' tab, and write down the state of every listing-type and category-type toggle.",
|
|
176
|
+
"Turn the auction listing type OFF and save.",
|
|
177
|
+
"RELOAD the tab and check that ONLY that toggle changed.",
|
|
178
|
+
"Sign out and open /: the Auctions item must be gone from the main navbar. Then open /auctions and read what is shown.",
|
|
179
|
+
"Sign back in, turn the auction type back ON, save, sign out, and confirm the navbar item and /auctions both return.",
|
|
180
|
+
"Signed out, open /api/site-settings and search the response for 'adminCheckoutBypass', 'smsVerification' and 'commissions'.",
|
|
145
181
|
],
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
182
|
+
inputs: { tab: "listings", toggled: "auction" },
|
|
183
|
+
expectedBehaviour: "A product control reaches the behaviour it names, in both directions, and saving one leaves the others alone — a disabled listing type is hidden from every listing surface and rejected on create and add-to-cart. The public settings response is an ALLOW-LIST projection: an operational setting such as the admin checkout bypass has no business being readable by an anonymous caller, and such settings were published for a long time because the response was built by spreading the document and deleting three keys.",
|
|
184
|
+
expectedUiState: "Only the toggled type changes after the reload. With auctions off the navbar drops its Auctions item; with them on it returns. The public settings response contains no operational setting — nothing naming a checkout bypass, an SMS-verification switch or the commission economics.",
|
|
185
|
+
expectedData: { operationalSettingsInPublicResponse: 0 },
|
|
186
|
+
endResult: "Every toggle is back in its original state and the navbar shows Auctions again. A setting visible in the public response is a projection leak rather than a settings bug. Note this page has NO 'feature flags' surface: that group was deleted in favour of this one, because 11 of its 14 keys had no readers at all.",
|
|
150
187
|
},
|
|
151
188
|
};
|
|
@@ -63,9 +63,9 @@ export const authored = {
|
|
|
63
63
|
steps: [
|
|
64
64
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
65
65
|
"Open /admin/coupons, find ARENAVIP, and read its per-user limit (1), total limit (50) and current usage (12).",
|
|
66
|
-
"
|
|
66
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as vivaan.kapoor@gmail.com / TempPass123! and place an order of 2 × product-beyblade-original-dranzer-s (₹2,998, above ARENAVIP's ₹2,000 minimum) applying ARENAVIP.",
|
|
67
67
|
"Attempt a second order applying it again and read the refusal.",
|
|
68
|
-
"
|
|
68
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as rehan.sheikh@gmail.com / TempPass123! and apply ARENAVIP at checkout on the same two items.",
|
|
69
69
|
"Sign back in as admin and read the usage count.",
|
|
70
70
|
],
|
|
71
71
|
inputs: { code: "ARENAVIP", perUserLimit: 1 },
|
|
@@ -79,7 +79,7 @@ export const authored = {
|
|
|
79
79
|
startPage: "/admin/coupons",
|
|
80
80
|
steps: [
|
|
81
81
|
"Sign in as admin@letitrip.in / TempPass123! and confirm QAADMIN15 has a minimum purchase of 500 and a maximum discount of 200.",
|
|
82
|
-
"
|
|
82
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as vivaan.kapoor@gmail.com / TempPass123!.",
|
|
83
83
|
"Add product-beyblade-metal-storm-pegasus (₹99) to the cart alone and apply QAADMIN15 at checkout.",
|
|
84
84
|
"Read the refusal and whether it names the required amount.",
|
|
85
85
|
"Add product-beyblade-original-driger-v (₹1,799) so the subtotal clears the minimum, and apply again.",
|
|
@@ -88,7 +88,7 @@ export const authored = {
|
|
|
88
88
|
],
|
|
89
89
|
inputs: { code: "QAADMIN15", minPurchase: 500, maxDiscount: 200 },
|
|
90
90
|
expectedBehaviour: "The minimum gates whether the coupon applies at all and the maximum caps what it gives. The minimum is measured against the items the coupon could actually discount rather than the whole cart, and money is decimal rupees throughout — a validator rejecting 799.50 as a non-integer is a leftover from the old whole-number storage.",
|
|
91
|
-
expectedUiState: "Below the minimum the coupon is refused with a message naming the
|
|
91
|
+
expectedUiState: "Below the minimum the coupon is refused with a message naming the REASON — today 'Minimum purchase requirement not met', which names the reason but NOT the amount, and the missing amount is a known gap recorded against content-discovery/coupons rather than a fresh finding here. What must not appear is a bare 'Invalid coupon', which would tell the buyer nothing about how much more to add. Above it the discount is capped at ₹200.00. A decimal amount is accepted in the coupon form.",
|
|
92
92
|
endResult: "Nothing is ordered; leave checkout without placing.",
|
|
93
93
|
},
|
|
94
94
|
"checklist-admin-coupons-coupon-scope-admin-vs-seller": {
|
|
@@ -113,7 +113,7 @@ export const authored = {
|
|
|
113
113
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
114
114
|
"Open /admin/coupons, open SEALED20, and write down its end date.",
|
|
115
115
|
"Set its end date to yesterday and LEAVE the Active toggle ON, then save — it is now past its end date while still flagged active.",
|
|
116
|
-
"
|
|
116
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as vivaan.kapoor@gmail.com / TempPass123! and apply SEALED20 at checkout.",
|
|
117
117
|
"Read the exact wording of the refusal.",
|
|
118
118
|
"Sign back in as admin, deactivate BLADER50 without touching its dates, and apply that one as the buyer.",
|
|
119
119
|
"Compare the two refusal messages.",
|
|
@@ -129,7 +129,7 @@ export const authored = {
|
|
|
129
129
|
startPage: "/admin/coupons",
|
|
130
130
|
steps: [
|
|
131
131
|
"Sign in as admin@letitrip.in / TempPass123! and confirm QAADMIN15 is admin-scoped with no store.",
|
|
132
|
-
"
|
|
132
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as vivaan.kapoor@gmail.com / TempPass123!.",
|
|
133
133
|
"Add product-beyblade-burst-regalia-genesis (₹1,399, Beyblade Arena) to the cart.",
|
|
134
134
|
"Add prizedraw-beyblade-original-vintage-vault (₹299, LetItRip Official) and set its quantity to 2.",
|
|
135
135
|
"Apply QAADMIN15 at checkout and read the discount.",
|
|
@@ -163,7 +163,7 @@ export const authored = {
|
|
|
163
163
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
164
164
|
"Create a coupon coded QACATADMIN20 at 20%, restricted to the Beyblade Burst category, no minimum.",
|
|
165
165
|
"Save, RELOAD, and confirm the restriction persisted.",
|
|
166
|
-
"
|
|
166
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as vivaan.kapoor@gmail.com / TempPass123!.",
|
|
167
167
|
"Add product-beyblade-metal-storm-pegasus (₹1,299, Metal Fight) alone and apply QACATADMIN20 — read the refusal.",
|
|
168
168
|
"Add product-beyblade-burst-regalia-genesis (₹1,399, Burst) and apply again.",
|
|
169
169
|
"Read the discount and compare it against 20% of ₹1,399.",
|
|
@@ -201,7 +201,7 @@ export const authored = {
|
|
|
201
201
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
202
202
|
"Open /admin/coupons and read the usage figure on every row.",
|
|
203
203
|
"Note the figure for ARENA25.",
|
|
204
|
-
"
|
|
204
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads that file only at context creation. An admin can apply a coupon perfectly well, so without this the case passes while testing the wrong person. Then sign in as vivaan.kapoor@gmail.com / TempPass123! and place an order applying ARENA25.",
|
|
205
205
|
"Sign back in as admin and read ARENA25's usage figure again.",
|
|
206
206
|
"Open the coupon's detail and read whether it shows total usage, per-user usage, or both.",
|
|
207
207
|
"Reload and confirm the figure persists.",
|
|
@@ -30,7 +30,7 @@ export const authored = {
|
|
|
30
30
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
31
31
|
"Open /admin/events/new and read every option in the event type picker.",
|
|
32
32
|
"Write the list down and compare it against the real event types the catalogue supports.",
|
|
33
|
-
"Create an event titled 'QA Event raffle-open' with
|
|
33
|
+
"Create an event titled 'QA Event raffle-open' with a raffle of type 'Open raffle (all confirmed entries)' — the full dropdown label, a prize, a start date of today and an end date a week out.",
|
|
34
34
|
"Save, RELOAD, and read the raffle type, the prize and the dates.",
|
|
35
35
|
"Sign out, sign in as rehan.sheikh@gmail.com / TempPass123!, and enter the raffle from its public page.",
|
|
36
36
|
"Sign back in as admin and read the entry count on the event.",
|
|
@@ -45,10 +45,10 @@ export const authored = {
|
|
|
45
45
|
startPage: "/admin/events/new",
|
|
46
46
|
steps: [
|
|
47
47
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
48
|
-
"Create an event titled 'QA Event raffle-scorers' with
|
|
48
|
+
"Create an event titled 'QA Event raffle-scorers' with the raffle type 'Top N scorers' — that exact dropdown label — N set to 3, and a prize.",
|
|
49
49
|
"Save, RELOAD, and read the raffle type and the N value.",
|
|
50
|
-
"Open the public page and read how the Overview tab describes who can win.",
|
|
51
|
-
"
|
|
50
|
+
"Open the event's public page at /events/{its id} and read how the Overview tab describes who can win.",
|
|
51
|
+
"Swap identity before this step: this case lists admin among its roles, so the harness resolves the whole batch to the ADMIN session. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER — the MCP reads it only at context creation. Then sign in as rehan.sheikh@gmail.com / TempPass123!, enter the event, and read the Leaderboard tab. Entering as the ADMIN would still populate a leaderboard, so this would pass while measuring the wrong account.",
|
|
52
52
|
"Read whether the leaderboard is ordered by score and whether the qualifying cut-off is stated.",
|
|
53
53
|
],
|
|
54
54
|
inputs: { title: "QA Event raffle-scorers", raffleType: "top_n_scorers", topN: 3 },
|
|
@@ -61,9 +61,9 @@ export const authored = {
|
|
|
61
61
|
startPage: "/admin/events/new",
|
|
62
62
|
steps: [
|
|
63
63
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
64
|
-
"Create an event titled 'QA Event raffle-participants' with a
|
|
64
|
+
"Create an event titled 'QA Event raffle-participants' with the raffle type 'Top N earliest participants' — note the word 'earliest', which a tester scanning for 'Top N participants' would not match, N set to 3, and a prize.",
|
|
65
65
|
"Save, RELOAD, and read the raffle type and N.",
|
|
66
|
-
"Open the public page and read how the Overview tab describes who can win.",
|
|
66
|
+
"Open the event's public page at /events/{its id} and read how the Overview tab describes who can win.",
|
|
67
67
|
"Compare that description against the scorers event's.",
|
|
68
68
|
"Enter as rehan.sheikh@gmail.com / TempPass123! and compare the two leaderboards' ordering.",
|
|
69
69
|
],
|
|
@@ -97,7 +97,7 @@ export const authored = {
|
|
|
97
97
|
"Create an event titled 'QA Event spin-create' of the spin-wheel type.",
|
|
98
98
|
"Define three prizes with labels and weights, set a maximum of 2 spins per user, and set a spin window covering today.",
|
|
99
99
|
"Save, RELOAD, and read every prize, weight, the maximum and the window.",
|
|
100
|
-
"Open the public page and read the wheel and its segments.",
|
|
100
|
+
"Open the event's public page at /events/{its id} and read the wheel and its segments.",
|
|
101
101
|
"Check the wheel shows the prize labels and does NOT disclose their weights.",
|
|
102
102
|
],
|
|
103
103
|
inputs: { title: "QA Event spin-create", prizeCount: 3, maxPerUser: 2 },
|
|
@@ -129,7 +129,7 @@ export const authored = {
|
|
|
129
129
|
startPage: "/admin/events",
|
|
130
130
|
steps: [
|
|
131
131
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
132
|
-
"Open an
|
|
132
|
+
"Open /admin/events/event-favourite-blader-poll/entries — an ACTIVE poll carrying 3 seeded entries, so the table is not empty — and read every column.",
|
|
133
133
|
"Read every status filter offered and select each, noting any that returns nothing.",
|
|
134
134
|
"For each empty filter, check the unfiltered list for rows of that status.",
|
|
135
135
|
"Read the row actions offered and check at least one lets you VIEW an entry.",
|
|
@@ -147,7 +147,7 @@ export const authored = {
|
|
|
147
147
|
startPage: "/admin/events",
|
|
148
148
|
steps: [
|
|
149
149
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
150
|
-
"Open the entries
|
|
150
|
+
"Open /admin/events/event-favourite-blader-poll/entries. That is the poll, and it has 3 seeded entries; picking an event with none would leave nothing to read and is indistinguishable from a broken table.",
|
|
151
151
|
"Find a row offering Confirm, Waitlist or Cancel.",
|
|
152
152
|
"WITHOUT using any of those, look for a way to read the entry's actual content first.",
|
|
153
153
|
"Open the entry and read the participant's response in full.",
|
|
@@ -4,12 +4,23 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 🛑 EVERY CASE HERE TESTS AN EFFECT, NEVER A FUNCTION.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* 53 Firebase functions are defined — 27 scheduled, 20 Firestore triggers, 6
|
|
8
8
|
* HTTPS — and none had a checklist case. The reason is that the obvious way to
|
|
9
9
|
* write one is untestable: "trigger the cron and confirm it ran" cannot be done
|
|
10
10
|
* from a browser in a session, so every such case would be answered null
|
|
11
11
|
* forever, which reads as coverage while providing none.
|
|
12
12
|
*
|
|
13
|
+
* 🛑 THOSE FOUR NUMBERS WERE 55 / 28 / 20 / 7 UNTIL 2026-09-29, and three of them
|
|
14
|
+
* were wrong. Recounted from the registry itself:
|
|
15
|
+
*
|
|
16
|
+
* grep -c "defineFunction(" appkit/src/_internal/server/functions/scheduled.ts
|
|
17
|
+
* firestore.ts
|
|
18
|
+
* https.ts
|
|
19
|
+
*
|
|
20
|
+
* CLAUDE.md warns that the scheduled figure has drifted four separate times and
|
|
21
|
+
* says to recount rather than quote it; this file quoted it and drifted a fifth.
|
|
22
|
+
* Recount before trusting any of them — including these.
|
|
23
|
+
*
|
|
13
24
|
* So each case instead reads the thing the function is RESPONSIBLE FOR. A nightly
|
|
14
25
|
* reconciler is tested by recounting what it reconciles; a settlement job by
|
|
15
26
|
* whether the winner can pay; a self-triggering write-back by whether its
|
|
@@ -44,6 +55,8 @@ export const authored = {
|
|
|
44
55
|
expectedUiState: "The per-function breakdown is on screen, sorted. The top function's count is of the same order as the rest of the active ones. A ratio in the thousands is the finding, and the function's name is quoted.",
|
|
45
56
|
expectedData: { runawayFunctions: 0 },
|
|
46
57
|
endResult: "Read-only; nothing persists. 🛑 Read the PER-FUNCTION breakdown, never the aggregate — the aggregate never names the culprit, which is exactly why #92 was found from a billing page rather than from monitoring.",
|
|
58
|
+
requiresHumanChannel: true,
|
|
59
|
+
humanChannelReason: "The per-function invocation breakdown lives in the Firebase console, which needs an interactive Google sign-in the automated browser does not have. There is no in-product surface for it — /admin/maintenance/cloud-logs shows raw log lines, not per-function counts. A human runs this one; the automated answer is null, naming the console as the missing channel.",
|
|
47
60
|
},
|
|
48
61
|
"checklist-admin-firebase-function-effects-function-errors-page-has-no-producer": {
|
|
49
62
|
roles: ["admin"],
|
|
@@ -54,7 +67,7 @@ export const authored = {
|
|
|
54
67
|
"Open /admin/maintenance/server-errors in another tab and read what it shows.",
|
|
55
68
|
"Compare the two: note whether server-errors has rows while function-errors does not.",
|
|
56
69
|
],
|
|
57
|
-
expectedBehaviour: "BEFORE: the page is expected to list Cloud Function failures. AFTER: it is empty, and it will remain empty no matter what breaks, because NOTHING IN PRODUCTION WRITES a serverErrors row with source \"function\".
|
|
70
|
+
expectedBehaviour: "BEFORE: the page is expected to list Cloud Function failures. AFTER: it is empty, and it will remain empty no matter what breaks, because NOTHING IN PRODUCTION WRITES a serverErrors row with source \"function\". `wrapJobHandler` is the only thing that would, and as of 2026-09-29 it has NO CALL SITES AT ALL — it is defined, re-exported from the server barrel, and invoked nowhere. (This case used to say \"referenced solely by its own test\", which stopped being true when both test suites were deleted; the claim is now stronger, not weaker.) The sibling server-errors page has a real producer and should show rows, which is what makes the contrast legible.",
|
|
58
71
|
expectedUiState: "function-errors renders an empty list or empty state. server-errors renders actual rows. Screenshot both, side by side if possible.",
|
|
59
72
|
expectedData: { functionErrorRows: 0 },
|
|
60
73
|
endResult: "🛑 ANSWER NO. This case is authored to FAIL against today's product, and that is deliberate: a case reading \"check that errors appear here\" would pass vacuously against a surface that can never populate. A red case with a screenshot is a tracked defect; a note in a document is not.",
|
|
@@ -194,7 +207,7 @@ export const authored = {
|
|
|
194
207
|
steps: [
|
|
195
208
|
ADMIN,
|
|
196
209
|
"Open /admin/orders and note the most recent order's id and the time it was created.",
|
|
197
|
-
"Open the admin
|
|
210
|
+
"Open /admin/admin-notifications — the staff inbox, which is a different page from /admin/notifications, the per-user notification list — and look for a signal corresponding to that order.",
|
|
198
211
|
"Note whether the signal exists, and whether there is exactly one rather than one per employee.",
|
|
199
212
|
],
|
|
200
213
|
expectedBehaviour: "A new order leaves something an operator can notice, as ONE durable staff-visible record rather than a personal notification fanned out to every employee. The fan-out shape is what nearly exhausted a 100/day email allowance from a single event, so one row read by the admin inbox and counted by the daily digest is the correct shape.",
|
|
@@ -229,6 +242,8 @@ export const authored = {
|
|
|
229
242
|
],
|
|
230
243
|
expectedBehaviour: "The two counts match. Scheduler bills per REGISTERED job rather than per invocation, so a job with no matching function is paid for and dead, and a scheduled function with no job simply never runs — and nothing in the product reports either.",
|
|
231
244
|
expectedUiState: "Two counts are written down and are equal. If they differ, name the jobs or functions that appear on only one side.",
|
|
232
|
-
endResult: "Read-only; nothing persists. 🛑 RECOUNT rather than quoting a remembered number — this figure has drifted
|
|
245
|
+
endResult: "Read-only; nothing persists. 🛑 RECOUNT rather than quoting a remembered number — this figure has drifted five separate times now, including once inside this very file, which is exactly why the case asks for a count instead of stating one. As of 2026-09-29 the registry defines 27 scheduled functions; if Scheduler shows a different number, the difference is the finding.",
|
|
246
|
+
requiresHumanChannel: true,
|
|
247
|
+
humanChannelReason: "Both counts are read from Google consoles — Cloud Scheduler and the Firebase Functions list — which need an interactive Google sign-in the automated browser does not have. The registry side can be counted from source, but the billed side cannot, and the whole case is the comparison. A human runs this one.",
|
|
233
248
|
},
|
|
234
249
|
};
|