Skip to content

Latest commit

 

History

History
105 lines (84 loc) · 6.02 KB

File metadata and controls

105 lines (84 loc) · 6.02 KB

Testing

Four gates, run in this order by pnpm verify, plus an end-to-end suite.

pnpm typecheck      # tsc --noEmit
pnpm lint           # eslint .
pnpm check:design   # design-token discipline
pnpm check:actions  # server-action boundary rules
pnpm test           # vitest
pnpm build          # next build
pnpm test:e2e       # playwright, against a production build

Static gates beyond the compiler

Two checks exist because the failure they catch is invisible to tsc and expensive to catch any other way.

pnpm check:design (scripts/check-design-tokens.mjs) fails on a raw Tailwind palette colour, a gray step outside the 100–1000 ramp, font-bold, transition-all, hover:opacity-*, a hard-coded hex, :focus styling where :focus-visible is meant, and neutral text on a tinted -subtle surface. A design system that is only written down erodes; this one is enforced.

pnpm check:actions (scripts/check-server-actions.mjs) fails on an inline callback inside an exported action in a "use server" module — an inline .refine() or errorMap on a Zod schema. SWC rejects it with "Server Actions must be async functions", tsc says nothing, and the only other way to find out is a five-minute build. It caught four occurrences during the rebuild.

Unit tests — Vitest

tests/**/*.test.ts. jsdom environment, @/ path alias, tests/setup.ts polyfills the browser APIs Radix expects (matchMedia, ResizeObserver, pointer capture).

The suite deliberately targets logic that can be wrong in a way types cannot catch:

  • tests/lib/utils.test.ts — query-string building, list/int param parsing, truncation on word boundaries, HTML stripping.
  • tests/lib/format.test.ts — currency and minor-unit conversion, compact notation, UTC-pinned dates (so server and client agree), relative time with an injectable now, open-ended range formatting.
  • tests/lib/slug.test.ts — accent folding, collision suffixing, and that a reserved route segment can never be claimed as a slug.
  • tests/lib/plans.test.ts — the pricing invariants: exactly one free tier, yearly cheaper than 12× monthly, limits relaxing as tiers rise, per-day listing cost falling with duration, bigger credit packs being better value, the subscription always beating every pack, and fee + net === amount for every amount.
  • tests/lib/enums.test.ts — parses prisma/schema.prisma and asserts every enum value has a label and every label maps to a value that still exists. Adding a database enum value without a label fails the build rather than rendering undefined in production.
  • tests/server/permissions.test.ts — the capability matrix: an interviewer cannot advance a candidate, a recruiter cannot touch billing, a billing member cannot read candidates, only an owner can hand out ownership.

Two of these earned their keep during the build. The pricing test caught a real inversion where the 150-credit pack undercut the Talent Pro subscription per credit. The formatting test caught a file-size branch that rendered "2 KB" where "2.0 KB" was intended.

End-to-end — Playwright

e2e/*.spec.ts, run against a production build (next start), in Chromium and on a Pixel 7 viewport.

The webServer config forces DEV_AUTH_BYPASS=0 regardless of what is in .env.local, because the point of the suite is to prove the real guards work.

Spec Covers
auth-protection.spec.ts The security contract — see below
marketplace.spec.ts Job/project/talent/company listing, filtering, detail pages, 404s
workspaces.spec.ts Every talent, organization, admin and account route renders for the right persona; role boundaries inside an organization
accessibility.spec.ts One <h1> per page, a <main> landmark, alt text, accessible names on every control, skip link, keyboard reachability, no horizontal overflow at 375/768/1280, dark mode

The auth-protection contract

e2e/auth-protection.spec.ts is the file that proves the bypass is off and the guards do the work. It asserts:

  • No page advertises the development bypass.
  • Every route in PROTECTED_ROUTES — talent, organization, admin, messages, settings — either redirects to /login or 404s when signed out, and never renders its own path.
  • The callbackUrl survives the redirect, so signing in returns you where you were going.
  • Every route in PUBLIC_ROUTES still renders for anybody.
  • A signed-in talent reaches /t but not /admin, and not an organization they do not belong to.
  • An organization owner reaches their own workspace but not a different organization.
  • An admin reaches /admin.
  • Clearing the session closes the doors again.
  • A wrong password and an unknown email fail identically, so the form cannot be used to enumerate which accounts exist.

Cross-organization and cross-role denials return 404, not 403, so probing slugs cannot enumerate which organizations exist. The tests accept either a 404 or a redirect and assert only that the protected content is not reached.

What is not covered

  • No visual regression suite. The design system is enforced by token discipline and review rather than by screenshot diffing.
  • No load testing.
  • Stripe webhooks are exercised through their handlers, not against Stripe's test fixtures; the signature check is not stubbed out, so an end-to-end webhook test would need the Stripe CLI.