Skip to content
This repository was archived by the owner on Apr 6, 2026. It is now read-only.

feat(web): self-contained local test mode for the dashboard#331

Merged
karaktaka merged 10 commits into
mainfrom
feat/test-mode
Mar 14, 2026
Merged

feat(web): self-contained local test mode for the dashboard#331
karaktaka merged 10 commits into
mainfrom
feat/test-mode

Conversation

@karaktaka
Copy link
Copy Markdown
Contributor

@karaktaka karaktaka commented Mar 14, 2026

Summary

  • Adds docker compose -f docker-compose.test.yml up as a zero-dependency way to run the full dashboard UI with synthetic data — no Discord bot, Valkey, or real guild data required
  • VITE_TEST_MODE=true (build arg) bakes a mock API interceptor into the frontend bundle; tree-shaken from normal production builds
  • NERPYBOT_TEST_MODE=true (runtime env) enables a GET /auth/test-login endpoint and makes /auth/me return a synthetic operator user, bypassing Discord OAuth and Valkey entirely
  • Four test guilds cover all major dashboard scenarios: full-featured guild, minimal guild, bot-not-present (Add to Server), and support-mode (operator read-only view with PII redacted)
  • Support-mode guild applies the same [redacted] PII masking as the real backend (_redact() in guilds.py), and all write operations return 403 — same as production behaviour

Test plan

  • docker compose -f docker-compose.test.yml up --build starts cleanly, no external services needed
  • Navigate to http://localhost:8080 → Login page shows "Test Login" button
  • Click Test Login → redirected to dashboard with test guilds in the sidebar
  • Nerdcraft Central — all tabs populated (moderator roles, reminders, reaction roles, applications with submissions, WoW guild news + crafting board)
  • Quiet Corner — most tabs show correct empty states; language set to DE
  • Cool Guild — appears in "Add to Server" section on Server Overview
  • External Server — support mode banner visible (amber); all tabs show data with PII fields replaced by [redacted]; any save/edit attempt returns an error
  • Sidebar shows amber "Test mode — synthetic data" strip when expanded
  • Normal production build (npm run build) contains no references to test guild IDs (tree-shaken)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Test mode for the app, including a "Test Login" option and a visible "Test mode — synthetic data" banner.
    • Frontend can operate with comprehensive synthetic data and a mock API to exercise UI flows without external services.
  • Chores

    • Added a containerized test compose configuration and build-time toggle to run the app in test mode.

karaktaka and others added 8 commits March 14, 2026 15:33
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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Docker & Compose
Dockerfile, docker-compose.test.yml
Adds VITE_TEST_MODE build arg/env and new docker-compose.test.yml to run the web service in test mode with test env vars and port mapping.
Backend init & auth
web/app.py, web/routes/auth.py
Creates fake ValkeyClient when test mode enabled; adds /auth/test-login JWT endpoint and short-circuits /me for synthetic test operator.
Backend dependencies & access checks
web/dependencies.py
Introduces test-mode flags and constants; bypasses operator/premium checks for the test user and adds support-mode branches in require_guild_access.
Frontend API client
web/frontend/src/api/client.ts
Lazily loads @/dev in test mode; when isTestRequest(path) true, short-circuits network fetches to return resolver-provided mocked responses.
Frontend test harness
web/frontend/src/dev/index.ts, web/frontend/src/dev/resolver.ts, web/frontend/src/dev/fixtures.ts
Adds test-mode entrypoint, a large set of synthetic fixtures (~600 lines), and a route-driven in-memory resolver with read/write semantics and support-mode redaction (resolveTestRequest).
Frontend UI
web/frontend/src/views/LoginView.vue, web/frontend/src/views/guild/GuildDetailView.vue, web/frontend/src/dev/TestModeIndicator.vue
Adds test login button/handler, conditionally renders a test-mode indicator in guild sidebar, and adjusts permission computation for support mode.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

review

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: adding self-contained local test mode for the dashboard with synthetic data.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/test-mode
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 86b9832 and 546def9.

📒 Files selected for processing (12)
  • Dockerfile
  • docker-compose.test.yml
  • web/app.py
  • web/dependencies.py
  • web/frontend/src/api/client.ts
  • web/frontend/src/dev/TestModeIndicator.vue
  • web/frontend/src/dev/fixtures.ts
  • web/frontend/src/dev/index.ts
  • web/frontend/src/dev/resolver.ts
  • web/frontend/src/views/LoginView.vue
  • web/frontend/src/views/guild/GuildDetailView.vue
  • web/routes/auth.py

Comment thread web/dependencies.py Outdated
Comment thread web/frontend/src/dev/resolver.ts
Comment thread web/routes/auth.py
karaktaka and others added 2 commits March 14, 2026 17:41
- 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>
@karaktaka karaktaka merged commit b6c9b44 into main Mar 14, 2026
10 checks passed
@karaktaka karaktaka deleted the feat/test-mode branch March 14, 2026 16:50
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant