feat(web): self-contained local test mode for the dashboard#331
Conversation
Adds VITE_TEST_MODE (build-time) and NERPYBOT_TEST_MODE (runtime) flags that together enable a fully functional dashboard with synthetic data and no external dependencies. - Backend: /auth/test-login endpoint issues a JWT for a synthetic operator; /auth/me returns hardcoded test guilds; dependencies bypass Valkey/DB checks for the test user; app.py uses ValkeyClient.create_fake() in test mode - Frontend: src/dev/ directory with fixture data, a URL-pattern mock resolver, and a TestModeIndicator sidebar badge; api/client.ts intercepts matching paths before they hit the network (tree-shaken from production builds) - docker-compose.test.yml: single-service stack — docker compose up --build, navigate to http://localhost:8080, click "Test Login" Co-Authored-By: Claude <noreply@anthropic.com>
…ecks
- Merge mutableLeaveMessages/autoKicker/language into main `stores` Record,
eliminating three parallel Maps keyed by guild ID
- Widen stores type to Record<string,Record<string,unknown>> so object-valued
entries (language, leaveMessages, autoKicker) don't require `as unknown` casts
- Cache `import("@/dev")` in module-level `_devMod` variable to avoid
re-resolving the dynamic import on every request
- Move _TEST_MODE constant to `web/dependencies.py` and import it from there
in app.py and auth.py, removing redundant os.environ.get() call-site lookups
- Remove duplicate _TEST_USER_ID definition from auth.py
Co-Authored-By: Claude <noreply@anthropic.com>
…routes
All resolver patterns were using camelCase path segments (modRoles,
autoKicker, applicationForms, etc.) but the real API uses kebab-case
(moderator-roles, auto-kicker, application-forms, etc.), causing every
tab to get no data or an unmatched path in test mode.
- Fix all guild resource paths to kebab-case
- Fix /discord/roles path (was /roles)
- Merge /wow endpoint: returns { guild_news, crafting_boards } to serve
both WowGuildNewsTab and WowCraftingTab from a single GET /guilds/{id}/wow
- Add /wow/news-configs CRUD routes (was incorrectly named wowGuildNews)
- Add /wow/news-configs/{id}/roster route (returns empty array)
- Fix /wow/crafting-role-mappings and /wow/crafting-orders paths
- Add PUT support for crafting-role-mappings/{id}
- Fix /operator/premium-users (was /operator/premium)
- Fix /operator/modules/{name}/load|unload (was single-segment pattern)
- Add application-templates question CRUD routes
- Strip query string before pattern matching (fixes submissions ?form_id=)
Co-Authored-By: Claude <noreply@anthropic.com>
Add fixture data for guild3 (External Server) so all tabs show content when navigating to it in support mode. Handle support-mode concerns once in resolveTestRequest rather than per-route: - All GET responses for guild3 are automatically elevated to supportMode: true, so GuildDetailView shows the support banner - All write methods (POST/PUT/PATCH/DELETE) to guild3 throw a 403 error, mirroring the real backend behaviour - Remove the one-off guild3 language and /wow special-case handlers (now covered by the generic routes + central elevation) Co-Authored-By: Claude <noreply@anthropic.com>
When an operator views a support-mode guild (not in their managed guilds), guild.current is null so effectiveLevel fell back to "member", hiding all moderation/roles/application/wow tabs. Now falls back to "admin" when supportMode is active, matching operator read-access intent. Co-Authored-By: Claude <noreply@anthropic.com>
Guild3 (External Server) had empty stores for reaction roles, role mappings, application forms/templates/submissions, and WoW tabs, making it impossible to test support-mode redaction or write-denial. Added full fixture data for all missing tabs so every section has visible content to verify read-only support-mode behaviour against. Co-Authored-By: Claude <noreply@anthropic.com>
…solver Mirror backend _redact() from guilds.py in the test-mode resolver. When returning data for the support-mode guild (999...0003), replace PII fields with "[redacted]" for: - reminders: author - application-submissions: user_id, user_name, decision_reason, answers[].answer_text, votes[].voter_id/voter_name - wow/crafting-orders: notes, creator_id/name, crafter_id/name Fixture data stays as raw DB-like values; redaction is applied dynamically so the real frontend display path is exercised. Co-Authored-By: Claude <noreply@anthropic.com>
Previously the indicator was an inline badge in the footer row, causing "Test Mode" to wrap to two lines due to space competition with the username, language switcher, and logout button. Now it sits in its own full-width strip between the nav and footer row with a subtle amber top border, keeping the footer controls uncluttered. Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds an opt-in "test mode" across frontend and backend: build flag and compose file, fake Valkey client, test-user authentication endpoint and dependency branches, frontend request interception with in-memory fixtures/resolver, UI test login/indicator, and many synthetic frontend fixtures for three test guilds. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as User/Browser
participant Client as Frontend API Client
participant DevModule as "@/dev" (index/resolver/fixtures)
participant Resolver as Test Resolver
participant Backend as Real API / Valkey
Browser->>Client: Send API request (method, path, body)
Client->>Client: Check VITE_TEST_MODE
alt VITE_TEST_MODE == "true"
Client->>DevModule: Lazy-import "@/dev"
Client->>DevModule: isTestRequest(path)?
alt isTestRequest == true
Client->>Resolver: resolveTestRequest(method,path,body)
Resolver->>Resolver: Read/modify in-memory fixtures (supportMode/redaction)
Resolver-->>Client: { data, supportMode }
Client-->>Browser: Return mocked response
else
Client->>Backend: Perform real fetch (Authorization included)
Backend-->>Client: Real response
Client-->>Browser: Return real response
end
else
Client->>Backend: Perform real fetch
Backend-->>Client: Real response
Client-->>Browser: Return real response
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/dependencies.py`:
- Line 17: _CURRENT: _TEST_MODE is set via
bool(os.environ.get("NERPYBOT_TEST_MODE")) which treats any non-empty string
(including "false" or "0") as True; change the logic that sets _TEST_MODE to
parse the string value explicitly (e.g., normalize to lower-case and check
membership in a set of truthy values like {"1","true","yes","y","on"} or use a
utility like distutils.util.strtobool) so that "false", "0", empty or unset
values produce False; update the assignment for the _TEST_MODE constant to use
this explicit parsing approach.
In `@web/frontend/src/dev/resolver.ts`:
- Around line 585-602: The mock resolver branch that handles
"/application-submissions" incorrectly redacts decision_reason by calling
ni(s.decision_reason); update the mapping in the block (the map callback that
spreads s and transforms fields) to stop applying ni to decision_reason and
return it as-is (preserve nullable string), i.e., remove the ni(...) call for
decision_reason so the frontend mock matches backend _submission_to_schema
behavior.
In `@web/routes/auth.py`:
- Around line 24-53: Add the missing support-mode guild to the _TEST_GUILDS
list: insert a GuildSummary entry with id "999000000000000003", name matching
the other test entries (e.g., "Support Guild" or consistent naming), icon=None,
permission_level="admin", bot_present=True, and invite_url=None so the backend
fixture matches the frontend and the permission system (update the _TEST_GUILDS
definition where GuildSummary entries are declared).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: d0ea2cff-6acb-44a0-b451-6cfd25302ea4
📒 Files selected for processing (12)
Dockerfiledocker-compose.test.ymlweb/app.pyweb/dependencies.pyweb/frontend/src/api/client.tsweb/frontend/src/dev/TestModeIndicator.vueweb/frontend/src/dev/fixtures.tsweb/frontend/src/dev/index.tsweb/frontend/src/dev/resolver.tsweb/frontend/src/views/LoginView.vueweb/frontend/src/views/guild/GuildDetailView.vueweb/routes/auth.py
- dependencies.py: bool(os.environ.get(...)) treats any non-empty string
(including "false" and "0") as True. Use explicit lowercase set membership
{"1","true","yes","y","on"} so NERPYBOT_TEST_MODE=false correctly disables
test mode.
- resolver.ts: decision_reason was incorrectly passed through ni() (redacted).
The main-branch backend _submission_to_schema does not redact this field;
only fix/support-mode-dashboard adds that. Mock now matches actual behaviour.
The suggestion to add guild3 to _TEST_GUILDS in auth.py was not applied —
it is intentionally absent so the frontend treats it as an external (support
mode) guild. Adding it would filter it out of OperatorGuildsTab and break
the support-mode test scenario.
Co-Authored-By: Claude <noreply@anthropic.com>
Reverts the previous removal. decision_reason redaction is intentionally pre-applied here to align with fix/support-mode-dashboard (follow-up PR) which adds _redact(s.DecisionReason, user) to the backend, so the mock already reflects the target behaviour of the full merged system. Co-Authored-By: Claude <noreply@anthropic.com>
Summary
docker compose -f docker-compose.test.yml upas a zero-dependency way to run the full dashboard UI with synthetic data — no Discord bot, Valkey, or real guild data requiredVITE_TEST_MODE=true(build arg) bakes a mock API interceptor into the frontend bundle; tree-shaken from normal production buildsNERPYBOT_TEST_MODE=true(runtime env) enables aGET /auth/test-loginendpoint and makes/auth/mereturn a synthetic operator user, bypassing Discord OAuth and Valkey entirely[redacted]PII masking as the real backend (_redact()inguilds.py), and all write operations return 403 — same as production behaviourTest plan
docker compose -f docker-compose.test.yml up --buildstarts cleanly, no external services neededhttp://localhost:8080→ Login page shows "Test Login" button[redacted]; any save/edit attempt returns an errornpm run build) contains no references to test guild IDs (tree-shaken)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores