Files
agent-park/.planning/codebase/TESTING.md
T
2026-04-18 19:28:53 +08:00

268 lines
9.8 KiB
Markdown

# 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<typeof executeTagMaintenance>[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*