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

9.8 KiB

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:

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:

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:

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:

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 },
  },
]);
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:

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"],
        },
      },
    ],
  };
}
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:

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:

const response = await POST(buildRequest(payload));
const json = await response.json();

expect(response.status).toBe(200);
expect(json.success).toBe(true);

Error Testing:

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");
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