--- kind: task slug: bootstrap-vitest-for-apps-web title: Bootstrap Vitest for the Next.js app (apps/web) with alias + env wiring plan_slug: multitenant-saas-hardening epic_slug: test-foundation status: draft priority: P2 tenant_id: global owner: unassigned cursor_todo_id: null updated_at: "2026-06-02" --- # Task summary Vitest is configured for `packages/shared`, `packages/database`, and `packages/ai` (see `Task-bootstrap-vitest-and-ci`). It is NOT configured for `apps/web`. This means callback logic, tRPC procedures, and React hooks in the web app have no unit-test home — only the manual smoke-test path covers them. This task adds the harness so future work can put real assertions next to the code they verify. ## Description ### Vitest config Add `apps/web/vitest.config.ts`. The Next.js app uses a `@/*` path alias and a mix of server-only modules (DB clients, NextAuth callbacks) and client modules (React components). The config has to: - Resolve `@/*` to `apps/web/*` (matches `tsconfig.json`). - Run server-side modules under `environment: "node"`. - Run React-component modules under `environment: "jsdom"` with `@testing-library/react` available. (Optional in v1 — start with node tests only; React testing is its own incremental step.) - Mock `next/headers`, `next/navigation`, and `next-auth` for any test that imports a Next-specific module without booting the full framework. Reference: [Next.js + Vitest docs](https://nextjs.org/docs/app/building-your-application/testing/vitest). ### Add `test` + `test:watch` scripts to `apps/web/package.json` and verify `pnpm test` (turbo) picks them up ### Initial tests (high-ROI) The point of bootstrapping the harness is to immediately backfill the assertions that were deferred from `Task-multi-email-identity`. Specifically: 1. **`resolveOAuthEmailVerified`** (currently a non-exported helper in `apps/web/lib/auth.ts`). Either extract to its own module or export it. Test: - Google + Authentik branches return `profile.email_verified` directly. - GitHub branch makes the right `fetch` call and reads `verified` from the matching entry. Mock `globalThis.fetch`. - Unknown provider returns `false`. 2. **`resolveOAuthUser`** — harder, hits the DB. Either: - Set up a test-DB harness (transaction-per-test pattern with `pg-test-transactions` or hand-rolled BEGIN/ROLLBACK). - OR mock `getSql` and assert the SQL templates. I'd vote for the test-DB harness eventually — mocking SQL strings is brittle — but it's a meaningful chunk of infrastructure. Filed as a follow-up to this task if it bloats. 3. **`userOwnsEmail` + `findUserIdByVerifiedEmail`** in `apps/web/server/lib/identity.ts`. Same DB-harness question. ### Anti-goals (defer) - E2E tests via Playwright. That's a separate plan. - Full coverage of every tRPC procedure. Add tests where regressions would hurt; don't manufacture coverage. ## Subtasks - [ ] Add `apps/web/vitest.config.ts` with alias + env wiring. - [ ] Add `test` / `test:watch` scripts to `apps/web/package.json`. - [ ] Decide on test-DB strategy (real Postgres via a `_test` suffix DB + per-test BEGIN/ROLLBACK is recommended). Document it in `AGENTS.md`. - [ ] Backfill the `resolveOAuthEmailVerified` test. - [ ] Backfill the `userOwnsEmail` test against the chosen DB harness. ## Owner or assignee Unassigned ## Status draft ## Estimation M ## Acceptance criteria - [ ] `pnpm --filter @tasks/web test` runs and passes from a clean clone. - [ ] `pnpm test` (turbo, root) includes the apps/web suite. - [ ] At least one real test per planned target (`resolveOAuthEmailVerified`, `userOwnsEmail`). - [ ] CI gates the merged result. ## Links - Epic: `./Epic-test-foundation.md` - Plan: `../Plan-multitenant-saas-hardening.md` - Unblocks proper test coverage for: `Task-multi-email-identity.md`, all subsequent `Plan-multitenant-saas-hardening` work that lives in `apps/web`.