# Testing Patterns **Analysis Date:** 2026-04-18 ## Test Framework **Runner:** - `vitest` via `vitest.config.ts`. - `package.json` declares `vitest` in `devDependencies` and exposes `pnpm test`. - Verified on 2026-04-18: `pnpm test` executed with Vitest `v2.1.9` and passed all current test files. **Assertion Library:** - Vitest built-ins: `describe`, `it`, `expect`, `beforeEach`, and `vi`, as seen in `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. **Config:** - `vitest.config.ts` sets `environment: "node"`, aliases `@` to `./src`, disables watch mode, and includes only `src/**/*.test.ts`. - No separate setup file, coverage config, browser environment, or integration test project was detected in `vitest.config.ts`. **Run Commands:** ```bash pnpm test # Run all configured Vitest suites pnpm lint # Run ESLint quality checks pnpm build # Run production build, lint, and type validation ``` ## Test File Organization **Location:** - Tests are co-located beside the source they exercise. - Library tests live next to utilities, for example `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/lib/validations.tag-maintenance.test.ts`. - API tests live next to route or service modules, for example `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, and `src/app/api/tags/maintenance/service.test.ts`. **Naming:** - Use `*.test.ts`. No `*.spec.ts` files were detected under `src` on 2026-04-18. - The include pattern in `vitest.config.ts` means `*.test.tsx` and files outside `src` are not picked up by default. **Structure:** ```text src/ lib/ auth.ts auth.test.ts app/api/tags/ route.ts route.test.ts maintenance/ route.ts route.test.ts service.ts service.test.ts ``` ## Test Structure **Suite Organization:** ```typescript import { beforeEach, describe, expect, it, vi } from "vitest"; import { NextRequest } from "next/server"; import { POST } from "./route"; const { transactionMock, revalidatePathMock, txMock } = vi.hoisted(() => { // create hoisted mocks here }); vi.mock("@/lib/prisma", () => ({ prisma: { $transaction: transactionMock } })); vi.mock("next/cache", () => ({ revalidatePath: revalidatePathMock })); function buildRequest(body: unknown): NextRequest { return new NextRequest("http://localhost:3000/api/tags/maintenance", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); } describe("POST /api/tags/maintenance", () => { beforeEach(() => { process.env.WEBHOOK_API_KEY = "k".repeat(32); vi.clearAllMocks(); }); it("returns 401 for wrong API key", async () => { const response = await POST(buildRequest({ apiKey: "a".repeat(32), updates: [], merges: [] })); expect(response.status).toBe(401); }); }); ``` **Patterns:** - Use a top-level `describe(...)` per module or endpoint, with test names phrased as behavior statements. - Reset spies and mock state in `beforeEach`, as in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. - For API routes, call the exported `GET` or `POST` function directly and assert on both `response.status` and `await response.json()`. - For pure helpers and schemas, call the function directly and assert return values or thrown errors, as in `src/lib/auth.test.ts` and `src/lib/validations.test.ts`. ## Mocking **Framework:** - Vitest mocking via `vi.mock`, `vi.fn`, `vi.hoisted`, `mockResolvedValue`, and `mockRejectedValue`. **Patterns:** ```typescript const { findManyMock } = vi.hoisted(() => ({ findManyMock: vi.fn(), })); vi.mock("@/lib/prisma", () => ({ prisma: { tag: { findMany: findManyMock, }, }, })); findManyMock.mockResolvedValue([ { id: "tag-1", name: "机器学习", slug: "machine-learning", _count: { projects: 4 }, }, ]); ``` ```typescript vi.mock("next/cache", () => ({ revalidatePath: revalidatePathMock, })); ``` **What to Mock:** - Mock Prisma client calls for route and service tests instead of hitting a real database. This is the established pattern in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. - Mock Next.js side effects such as `revalidatePath` when testing mutation endpoints, as in `src/app/api/tags/reset-projects/route.test.ts` and `src/app/api/tags/maintenance/route.test.ts`. - Set environment variables inline per suite when auth behavior depends on them, as in `process.env.WEBHOOK_API_KEY` usage in `src/app/api/tags/reset-projects/route.test.ts` and `src/app/api/tags/maintenance/route.test.ts`. **What NOT to Mock:** - Do not mock pure validation or auth utilities when they can be tested directly. `src/lib/auth.test.ts` and `src/lib/validations.tag-maintenance.test.ts` exercise real implementation logic without mocks. - There is no current pattern for browser, React component, or DOM mocking because no component tests are checked in. ## Fixtures and Helpers **Test Data:** ```typescript function buildValidPayload(apiKey: string) { return { apiKey, projects: [ { projectSlug: "project-one", selectedTagSlugsByCategory: { FIXED_PROJECT_TYPE: ["agent-tooling"], TECH_STACK: ["typescript"], AI_PARADIGM: ["ai-agents"], PRODUCT_FORM: ["web-application"], DOMAIN_SCENARIO: ["code-dev"], }, }, ], }; } ``` ```typescript const baseProjectInput = { name: "Agent Park", description: "A curated list of practical AI agent tools.", tags: [{ name: "ai-agent" }], links: [{ type: "GITHUB" as const, url: "https://github.com/example/repo" }], }; ``` **Location:** - Builders and fixtures are defined inline in each test file. No shared `test-utils`, factory module, or fixture directory was detected under `src` or repository root. - Reuse small local helpers such as `buildRequest`, `buildValidPayload`, `createTxMock`, and `baseProjectInput` instead of creating cross-suite abstractions. ## Coverage **Requirements:** - No coverage thresholds or coverage command are configured in `package.json` or `vitest.config.ts`. - No CI coverage upload or report configuration was detected. **View Coverage:** ```bash Not configured ``` ## Test Types **Unit Tests:** - Pure utility tests cover security and schema logic in `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/lib/validations.tag-maintenance.test.ts`. - Service-level unit tests cover tag maintenance merge behavior in `src/app/api/tags/maintenance/service.test.ts`. **Route-Level Tests:** - Route handler tests call Next.js App Router handlers directly with mocked dependencies in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. - These are closer to isolated handler tests than full integration tests because Prisma and cache modules are mocked. **Integration Tests:** - Not detected. No test currently exercises a real Prisma client, live database, or full Next.js server. **E2E Tests:** - Not detected in the repository. No `e2e/` directory and no `playwright.config.*` file were found on 2026-04-18. - Repository guidance mentions `pnpm test:e2e`, but `package.json` does not define that script. Treat E2E support as undocumented or not yet checked in. ## Common Patterns **Async Testing:** ```typescript const response = await POST(buildRequest(payload)); const json = await response.json(); expect(response.status).toBe(200); expect(json.success).toBe(true); ``` **Error Testing:** ```typescript findManyMock.mockRejectedValue(new Error("db unavailable")); const response = await GET(); const json = await response.json(); expect(response.status).toBe(500); expect(json.error).toBe("Internal server error"); expect(json.details).toContain("db unavailable"); ``` ```typescript await expect( executeTagMaintenance(tx as Parameters[0], { updates: [{ tagId: "missing-tag", nameEn: "Missing" }], merges: [], }) ).rejects.toMatchObject({ status: 400, message: "Validation error", }); ``` ## Current Gaps **Untested Server Modules:** - No tests were detected for `src/app/api/projects/route.ts`, `src/app/api/projects/[slug]/route.ts`, `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, or `src/app/api/webhook/signals/route.ts`. - No tests were detected for `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, `src/lib/tag-taxonomy.ts`, or `src/lib/github/badges.ts`. **Untested UI:** - No component tests were detected for large client components such as `src/app/[locale]/projects/ProjectsResultsClient.tsx` (784 lines), `src/components/project/TagFilterPanel.tsx` (698 lines), or `src/components/signals/SignalFeedClient.tsx` (603 lines). - No tests were detected for route pages under `src/app/[locale]`. **Test Infrastructure Gaps:** - No shared test helpers, factories, or fixture libraries are present. - No browser or DOM test environment is configured. - No E2E harness or Playwright configuration is present. - No CI workflow files were detected under `.github/`. ## Verification Signals **Current Quality Checks:** - `pnpm test` passed on 2026-04-18 with 7 test files and 24 tests passing. - `pnpm lint` passed on 2026-04-18 with no warnings or errors. - `pnpm build` passed on 2026-04-18. The build completed static generation and route analysis successfully. **Observed Test Output Notes:** - `pnpm test` prints expected stderr from the error-path test in `src/app/api/tags/route.test.ts` because `src/app/api/tags/route.ts` logs with `console.error`. - Vitest emitted a Vite deprecation notice about the CJS Node API during the run. This is a tooling signal, not a failing test. --- *Testing analysis: 2026-04-18*