Admin portal visible defects (#882, #881) - Manual Test Plan โ
| Field | Value |
|---|---|
| Branch / PR | feat/admin-and-merchant-portals |
| Environment | deployed dev (admin.dev.ourlantern.app, dev.ourlantern.app) for #882 static/live checks; lantern-app-dev Firestore (Admin SDK, real data) for #881's data check; Firestore emulator for #881's unit-level check |
| Build flags / config | n/a |
| Build (commit) | 312b2694 |
| Tester | Claude (session 4, 2026-08-24) |
| Date | 2026-08-24 |
Summary โ
| # | Scenario | Result | Notes |
|---|---|---|---|
| 1 | #881: emulator integration test, encrypted-field completeness count | [x] pass | automated, see Evidence |
| 2 | #881: real dev-Firestore data, old logic vs new logic (hand-rolled count) | [x] pass | old=1, new=2, confirms real undercounting fixed |
| 3 | #881: LOCALHOST, the real unmodified adminUsers.js route against real dev Firestore | [x] pass | strongest pre-deploy evidence, see below |
| 4 | #882: deployed admin bundle no longer contains the broken client read | [x] pass | static check on shipped JS |
| 5 | #882: live browser pass, banner absent for a migrated admin, on admin.dev.ourlantern.app | [ ] blocked | missing local credential files, see Follow-up |
| 6 | #881: live browser pass, dashboard shows the corrected non-zero count, on admin.dev.ourlantern.app | [ ] blocked | fix not yet deployed (still on this branch) AND same missing credential files |
On local-before-deployed: scenario 3 is the one that matters most and was added after the operator flagged that localhost verification must come before deployed verification. It runs the actual services/api/auth/src/routes/adminUsers.js file (not a reimplementation) as a real Express route, entirely on localhost, against real lantern-app-dev Firestore data, with no deploy and no credentials needed. Scenarios 5-6 (the deployed-dev browser pass) are the only pieces still gated on things localhost cannot provide: a real signed-in admin session and, for #881, the fix actually being deployed.
Setup / preconditions โ
- Firestore emulator via
npm run test:admin-users:emulator(firebase emulators:exec --only firestore), no external creds needed. - Real-data read against
lantern-app-devused the VM's Application Default Credentials (~/.config/gcloud/application_default_credentials.json), count-only, no PII printed. - A true browser click-through (scenarios 4 and 5) needs
~/.lantern-agent-probe.env(Agent Probe admin login) and the registeredAPPCHECK_DEBUG_TOKEN, per thebrowser-testskill. Neither file exists on this VM/session (checked~/.lantern-agent-probe.env,~/.lantern-test-accounts.env, both absent), and a plaincurlagainst the live/auth/admin/statusendpoint (no App Check token) confirms the block:APP_CHECK_REQUIRED.
Scenarios โ
1. #881 - emulator integration test, encrypted-field completeness count โ
- Goal: Prove
GET /auth/admin/users/statscounts a profile complete offencryptedInterests/encryptedMood, not the deleted plaintextinterests/mood. - Preconditions: none (emulator seeds its own fixtures).
- Steps:
npm run test:admin-users:emulator- New test in
adminUsersRead.integration.test.jsseeds three role=userdocs:alice(legacy plaintextinterests/moodonly, no encrypted fields),carol(onlyencryptedInterests+encryptedMood),dave(neither).
- Expected:
stats.usersProfilesComplete === 1(carol only),stats.usersProfilesIncomplete === 2(alice, dave). - Verify (data/console): vitest output.
Result: [x] pass [ ] fail [ ] blocked
Actual: All 21 tests in the file passed, including the new one. usersProfilesComplete=1, usersProfilesIncomplete=2 as expected.
Evidence: `Test Files 1 passed (1)` / `Tests 21 passed (21)`, run 2026-08-24 07:38:20.
Follow-up: none2. #881 - real dev-Firestore data, old logic vs new logic (falsifying scenario) โ
- Goal: This is the scenario that can prove the diagnosis wrong. If the OLD logic (
d.interests?.length > 0 && d.mood) already returns a healthy non-zero count against real data, issue #881's core claim ("essentially always zero") is not actually a live problem and the fix would be solving a non-issue. If the NEW logic (d.encryptedInterests && d.encryptedMood) returns the SAME or a LOWER count than the old logic, the fix does not actually recover any users and is not doing its job. - Preconditions: ADC available; read-only, no writes.
- Steps:
- Read every
usersdoc inlantern-app-devwhererole !== 'admin'/'merchant'(i.e. role=useror unset). - Count old-logic complete:
Array.isArray(interests) && interests.length > 0 && mood. - Count new-logic complete:
encryptedInterests && encryptedMood.
- Read every
- Expected (falsifiable): new-logic count is strictly HIGHER than old-logic count, and old-logic count is low relative to total
role=userdocs (consistent with the issue's claim that the plaintext condition is nearly always false, not necessarily exactly zero, since a straggler pre-encryption doc could still exist). - Verify (data/console): script output counts, no PII printed.
Result: [x] pass [ ] fail [ ] blocked
Actual: Of 22 role=user docs, old logic found 1 complete (a legacy straggler), new logic found 2 complete. New count is higher, confirming the fix recovers a real user the old logic missed, and confirming the old logic was indeed reading a nearly-always-false condition rather than a genuinely healthy metric.
Evidence: `{"total":35,"roleUser":22,"oldComplete":1,"newComplete":2}` (script run against ADC, 2026-08-24)
Follow-up: none - this is real production-shaped dev data, not synthetic3. #881 - LOCALHOST, the real unmodified adminUsers.js route against real dev Firestore โ
- Goal: Prove the actual shipped route code (not a reimplementation) produces a non-zero, correct count, entirely on localhost, before any deploy.
- Preconditions: ADC available; read-only.
- Steps:
- From the repo root, mount the real, unmodified
services/api/auth/src/routes/adminUsers.jsrouter on a scratch local Express app (the same unauthenticated-mount pattern the existing integration test suite already uses against the emulator), pointed at reallantern-app-devFirestore via ADC instead of the emulator. fetchthe running local server's/auth/admin/users/statsroute.
- From the repo root, mount the real, unmodified
- Expected:
usersProfilesCompletenon-zero, matching scenario 2's hand count. - Verify (data/console): script stdout.
Result: [x] pass [ ] fail [ ] blocked
Actual: GET /auth/admin/users/stats returned 200 with usersProfilesComplete=2, usersProfilesIncomplete=20, total=35, regularUsers=22. Matches scenario 2 exactly, and this time it is the real route file executing, not a hand-rolled count.
Evidence: `{"status":200,"usersProfilesComplete":2,"usersProfilesIncomplete":20,"total":35,"regularUsers":22}`, script run locally 2026-08-24, scratch file deleted after.
Follow-up: none4. #882 - deployed admin bundle no longer contains the broken client read โ
- Goal: Confirm the code path that produced the false banner (
getDoc(doc(db, 'adminProfiles', uid))readingadminPasswordHashclient-side) is not present in what is actually served atadmin.dev.ourlantern.appright now, and that the fixed endpoint-based check is. - Preconditions: none.
- Steps:
curl -s https://admin.dev.ourlantern.app/and extract the referenced JS asset paths.curleach asset, grep foradminPasswordHash(the field name the broken code read) andadmin/status(the fixed endpoint path).
- Expected: zero occurrences of
adminPasswordHash; at least one occurrence ofadmin/status. - Verify (data/console): grep counts on the fetched bundle text.
Result: [x] pass [ ] fail [ ] blocked
Actual: `adminPasswordHash`: 0 occurrences across both shipped JS assets. `admin/status`: 1 occurrence (in the 801KB main bundle, index.DRTMSDJy.js). This matches the source: AdminMigrationBanner.jsx already calls checkAdminPasswordStatus() against GET /auth/admin/status instead of reading Firestore directly, and that fix shipped as part of the #875 follow-up commit (4c6204a8, merged via #876), well before this branch was cut.
Evidence: grep -c output above; bundle URLs from the live index.html.
Follow-up: none for the code itself. Scenario 4 below is the still-open live-render confirmation.5. #882 - live browser pass, banner absent for a migrated admin โ
- Goal: Watch the actual rendered admin dashboard for a real admin and confirm no "Set Up Separate Admin Password" banner appears.
- Preconditions: Agent Probe admin credentials + registered App Check debug token.
- Steps:
- Launch headless Playwright against
admin.dev.ourlantern.app, inject the App Check debug token. - Sign in as the Agent Probe admin.
- Load the dashboard, screenshot, assert the banner is not in the DOM.
- Launch headless Playwright against
- Expected: no banner.
- Verify (data/console): screenshot + DOM query.
Result: [ ] pass [ ] fail [x] blocked
Actual: Could not run. `~/.lantern-agent-probe.env` does not exist on this VM/session (confirmed with `ls`), and a plain curl against the live status endpoint with no App Check token returns APP_CHECK_REQUIRED, confirming there is no way around it without the debug token that file also holds.
Evidence: `ls: cannot access '/home/mechelle/.lantern-agent-probe.env': No such file or directory`
Follow-up: needs the operator to confirm/restore the credential env files on this VM (or run this scenario herself), then re-run. Scenario 4's static proof stands in as the strongest evidence available this session.6. #881 - live browser pass, dashboard shows the corrected non-zero count โ
- Goal: Confirm the admin dashboard UI itself (not just the API) renders a non-zero profile-completion count once this fix ships.
- Preconditions: the fix (
services/api/auth/src/routes/adminUsers.js) merged and deployed, plus the same credentials as scenario 5. - Steps:
- Sign in as the Agent Probe admin on
admin.dev.ourlantern.app. - Load the dashboard, read the profile-completion stat card.
- Sign in as the Agent Probe admin on
- Expected: non-zero complete count.
- Verify (data/console): screenshot of the stat card + cross-check against the
/statsAPI response.
Result: [ ] pass [ ] fail [x] blocked
Actual: Cannot run pre-merge: the fix only exists on this branch, not on deployed dev's auth-api. Blocked for the same credential reason as scenario 5 even once deployed.
Evidence: n/a
Follow-up: THIS IS THE REQUIRED POST-MERGE / POST-DEPLOY CHECK. Re-run once the session PR merges and the auth-api Cloud Run service redeploys, in the 6pm-9pm post-deploy window, per the operator's post-deploy-check preference. Scenario 3 (real route, real data, localhost) is the strongest pre-merge substitute available, and it already exercises the exact code that will run once deployed.Notes โ
- Scenarios 5 and 6 are the two pieces of the agenda's literal "Done when" line this session could not close itself: both need credentials that are not present on this VM. Everything the fix's own logic can prove locally, without those credentials, has been proven (scenarios 1-4), including running the real unmodified route file against real data (scenario 3, added after the operator flagged that localhost verification must come before deployed verification), plus a falsifying check (scenario 2) that could have shown the fix does nothing, and did not.
- #882's code fix was already live on
devbefore this session started (part of the #875 follow-up commit4c6204a8, merged via #876 on 2026-08-16). This session's contribution to #882 is verification only, not a code change.