Files
civitai__civitai/tests
Zachary Lowden 86eab5d1d2 perf(api): SSR-inject user.checkNotifications to cut ~21 req/s off api-primary (#2690)
* perf(api): SSR-inject user.checkNotifications to cut ~21 req/s off api-primary

SSR-seed the header notification-bell unread count so the ambient
`user.checkNotifications` client query never fires on bootstrap, removing
~21 req/s of full non-batched tRPC middleware + context-build + superjson
cycles from `civitai-dp-prod-api-primary`. This is Tier-1 #6 in the
datapacket-talos analysis
`claudedocs/api-primary-trpc-cost-analysis-2026-06-21.md`; mirrors the
just-merged #2683 (system.getLiveNow) and #2471 (following/getSettings) pattern.

The cost is the per-request fixed middleware cycle x21/s (the CPU lever), not
the already-redis-cached resolver. Cutting the bootstrap call removes one full
fixed-cost cycle per logged-in page load.

How (the #2471 server-endpoint pattern, so NO server-only module leaks into the
client bundle):
- Extract the resolver's reduce into a shared `getUserNotificationCounts`
  (notification.service.ts) so BOTH `user.checkNotifications` and the SSR-seed
  path produce a byte-identical `{ all, <category>: count }` object. The
  controller now calls it directly (public tRPC type unchanged).
- Compute the count in the `/api/user/settings` self-fetch `_app` already makes
  (alongside the existing `following` seed) — authed-only, `.catch(() =>
  undefined)` so a redis/DB blip degrades to no-seed (client self-heals) and can
  never reject the Promise.all and drop the critical settings/session payload.
- Thread `notificationCounts` through `_app` pageProps -> AppProvider, which
  seeds `trpc.user.checkNotifications.useQuery(undefined, { initialData, enabled:
  !!notificationCounts, staleTime: Infinity })` on the fixed `undefined` key.

Live-count freshness preserved: the seed REPLACES the one-shot bootstrap fetch
only. The consumer hook already uses `staleTime: Infinity` (no time-poll today
either); the count stays live via the `NotificationNew` SignalR push + the
mark-read optimistic `setData`, both of which apply on top of the cache (seed or
fetched) exactly as before — not made stickier than today.

Byte-equality: payload is a plain object of numbers (no Date/array/undefined
field), so the JSON seed and the live `result.data.json` compare directly with
no superjson undefined->null divergence; an absent category is absent on both
sides. The shared reduce is the single source of truth backing both.

Tests: extends `tests/preview-ssr-inject.spec.ts` with an authed describe block
asserting (a) the seed is present in `__NEXT_DATA__.props.pageProps`, (b) no
`user.checkNotifications` request fires on bootstrap, and (c) the seed
deep-equals a live authed fetch (fetched from within the page, not page.request).
Authed-only (protectedProcedure → never fires anon, like getFollowingUsers).
These run under playwright.preview.config.ts (need a live preview).

Verification:
- tsc: zero type errors in any of the changed lines (the repo's pre-existing
  repo-wide implicit-any / Prisma.sql errors are untouched and unrelated).
- Full `next build` (bundle-leak check): INCONCLUSIVE locally — Turbopack rejects
  the worktree's symlinked node_modules ("points out of the filesystem root") and
  the --webpack fallback OOMs the Node heap (same blockers #2683 hit). No
  module-resolution/leak error surfaced before those infra failures. The two new
  imports into client-bundled files are `import type` only (erased at compile);
  the runtime value flows through the existing `/api/user/settings` self-fetch, so
  the client-bundle surface is structurally identical to the in-production
  `following` seed (#2471). Preview-build CI is the gate.

Behavior-preserving; dark-safe (cache seed only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(notifications): type checkNotifications setData updaters to UserNotificationCounts

The SSR-seed PR narrowed the `user.checkNotifications` resolver to return the
shared `getUserNotificationCounts` (explicitly `UserNotificationCounts =
Record<Lowercase<NotificationCategory> | 'all', number>`). That made the query's
`setData` `Updater` data type strictly `UserNotificationCounts | undefined`, so
the two optimistic updaters' `Record<string, number>` returns no longer
type-checked (TS2345 at the old 111/210).

Keep the `Record<string, number>` working buffer (needed for the dynamic
string-key indexing the body does) and return it typed as `UserNotificationCounts`
— the buffer is `...old` plus a guaranteed `all`, only mutated numerically, so it
satisfies the shape. No runtime behavior change. notifications.utils.ts is now
free of TS2345 (only the pre-existing TS7006 implicit-any baseline in the
untouched announcements helper remains).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:01:48 -05:00
..
2025-08-12 13:20:21 -04:00
2025-05-23 14:00:07 -04:00

Video: https://discord.com/channels/955572167662260295/1062092338698145812/1338591521401733192

# Playwright Testing

### Goal
E2E testing for the main app.
Catch potential issues when changing code, or evaluate edge cases.

### Anti-Goal
For this to be a frustrating pain-in-the-ass time vampire.
It's better to have 1 decent test than try to do 10 perfect ones and give up because it's too time consuming.

### What to Do

- Write tests for common user flows (fill a form, click a button, get a result)
- Handle what-if scenarios (errors, browsers, etc)
- A good rule of thumb is: if it's been reported as a bug, we should have a test in place for it (and things like it)

### What Not to Do

- Write "1==1" tests (dopamine hit, but pointless)
- Mandate coverage percentages (leads to annoyance and features not being done)

---

### How

Testing is intended to work on local development (docker) for consistency with users/data and easy tear down.

1) Run local services (`make init` or devcontainers)
2) Create a file in the `tests/` directory, or use an existing one. Doesn't really matter. Open to directory structure, so something like `tests/generator/gen-queue.spec.ts` would be reasonable.
3) Start writing tests.
    (a) can be done by hand if you know what you're looking to do
    (b) easier approach: `npm run test:gen -- --load-storage tests/auth/{user}.json --viewport-size 1920,1080 http://localhost:3000/{url}`
        - This allows you to create tests by interacting with the page and picking locators
    (c) we'll need better locators, especially for icons. add `data-testid=` to the places you need them (they'll be stripped from production)
    (d) use the various authed users to test different scenarios (mod, full access, muted, etc)
    (e) feel free to mock responses from any of the APIs, but in general it's best to only do this for external services
4) Run with either `npm run test` or `npm run test:ui` to do it interactively with screenshots
5) If you need to reset the db after each test, you can either:
    (a) clean up the mutations as part of the test (delete an object you just made)
    (b) `make boostrap-db` to reset the whole database back to normal
6) We'll eventually set up the github action to run this before a deploy

### Test Failures

There are 4 types of test failures:

1) A bad test (always fail)
    - these might have bad selectors or inaccurate logic
    - **solution**: fix them
2) A flaky test (sometimes fail)
    - frustrating tests which seem to pass most of the time, but not always
        - this is usually a result of race conditions, mismatched timing, or not properly awaiting events like animations
    - **solution**: narrow down which part of the test fails, and catch the flaky issue
3) Intended code change
    - we might have changed the verbiage on a button, which makes certain locators no longer work
        - this is fine, although locators should try to be as agnostic as possible
    - alternatively, we might have simply changed the business logic
    - **solution**: in either case, simply update the test itself
4) Unintended code change
    - you've changed something in the app, and a test breaks due to the introduction of a bug
        - this is the major reason we have tests
    - **solution**: leave the tests alone, they're doing their job. fix the code.