Skip to content

Admin portal visible defects (#882, #881) - Manual Test Plan โ€‹

FieldValue
Branch / PRfeat/admin-and-merchant-portals
Environmentdeployed 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 / confign/a
Build (commit)312b2694
TesterClaude (session 4, 2026-08-24)
Date2026-08-24

Summary โ€‹

#ScenarioResultNotes
1#881: emulator integration test, encrypted-field completeness count[x] passautomated, see Evidence
2#881: real dev-Firestore data, old logic vs new logic (hand-rolled count)[x] passold=1, new=2, confirms real undercounting fixed
3#881: LOCALHOST, the real unmodified adminUsers.js route against real dev Firestore[x] passstrongest pre-deploy evidence, see below
4#882: deployed admin bundle no longer contains the broken client read[x] passstatic check on shipped JS
5#882: live browser pass, banner absent for a migrated admin, on admin.dev.ourlantern.app[ ] blockedmissing local credential files, see Follow-up
6#881: live browser pass, dashboard shows the corrected non-zero count, on admin.dev.ourlantern.app[ ] blockedfix 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-dev used 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 registered APPCHECK_DEBUG_TOKEN, per the browser-test skill. Neither file exists on this VM/session (checked ~/.lantern-agent-probe.env, ~/.lantern-test-accounts.env, both absent), and a plain curl against the live /auth/admin/status endpoint (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/stats counts a profile complete off encryptedInterests/encryptedMood, not the deleted plaintext interests/mood.
  • Preconditions: none (emulator seeds its own fixtures).
  • Steps:
    1. npm run test:admin-users:emulator
    2. New test in adminUsersRead.integration.test.js seeds three role=user docs: alice (legacy plaintext interests/mood only, no encrypted fields), carol (only encryptedInterests+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: none

2. #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:
    1. Read every users doc in lantern-app-dev where role !== 'admin'/'merchant' (i.e. role=user or unset).
    2. Count old-logic complete: Array.isArray(interests) && interests.length > 0 && mood.
    3. Count new-logic complete: encryptedInterests && encryptedMood.
  • Expected (falsifiable): new-logic count is strictly HIGHER than old-logic count, and old-logic count is low relative to total role=user docs (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 synthetic

3. #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:
    1. From the repo root, mount the real, unmodified services/api/auth/src/routes/adminUsers.js router on a scratch local Express app (the same unauthenticated-mount pattern the existing integration test suite already uses against the emulator), pointed at real lantern-app-dev Firestore via ADC instead of the emulator.
    2. fetch the running local server's /auth/admin/users/stats route.
  • Expected: usersProfilesComplete non-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: none

4. #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)) reading adminPasswordHash client-side) is not present in what is actually served at admin.dev.ourlantern.app right now, and that the fixed endpoint-based check is.
  • Preconditions: none.
  • Steps:
    1. curl -s https://admin.dev.ourlantern.app/ and extract the referenced JS asset paths.
    2. curl each asset, grep for adminPasswordHash (the field name the broken code read) and admin/status (the fixed endpoint path).
  • Expected: zero occurrences of adminPasswordHash; at least one occurrence of admin/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:
    1. Launch headless Playwright against admin.dev.ourlantern.app, inject the App Check debug token.
    2. Sign in as the Agent Probe admin.
    3. Load the dashboard, screenshot, assert the banner is not in the DOM.
  • 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:
    1. Sign in as the Agent Probe admin on admin.dev.ourlantern.app.
    2. Load the dashboard, read the profile-completion stat card.
  • Expected: non-zero complete count.
  • Verify (data/console): screenshot of the stat card + cross-check against the /stats API 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 dev before this session started (part of the #875 follow-up commit 4c6204a8, merged via #876 on 2026-08-16). This session's contribution to #882 is verification only, not a code change.

Built with VitePress