agentleFS
JoviDeCroockJoviDeCroock/pracht/skills/pracht-test-api/SKILL.md

pracht-test-api

Skill106 starsChanged 7 months agoMITView on GitHub

Generate Vitest tests for every handler in `src/api/` that build a `Request`, call the handler directly, and assert on the `Response` without starting a server. Use for "test my API routes", "scaffold API tests", "add tests for this endpoint".

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 1 is distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If the project has no vitest dependency, run scaffold-tests first (or prompt the user to).

    line 29
  2. Very specific:

    Ask the user which subset to scaffold or accept paths via $ARGUMENTS.

    line 48
  3. Very specific:

    Place tests next to the handler with .test.ts suffix (src/api/users/[id].test.ts).

    line 52
  4. Very specific:

    If the handler exports default (one function dispatching on request.method), generate a test per HTTP method the handler appears to support …

    line 130
  5. Very specific:

    Use pracht inspect api --json as the inventory — do not glob.

    line 146
  6. Distinctive:

    Only import methods the handler actually exports.

    line 147
  7. Very specific:

    Test files live next to handlers with .test.ts suffix unless the project already uses a __tests__/ convention (detect and match).

    line 151
  8. Very specific:

    Never overwrite existing test files.

    line 153

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Pracht Test API

pracht API handlers are plain functions: (args: ApiRouteArgs) => Response | Promise<Response> (ApiRouteArgs is BaseRouteArgs with route narrowed to ResolvedApiRoute). They test cleanly in Vitest without booting the framework.

Step 1: Confirm Vitest is installed

If the project has no vitest dependency, run scaffold-tests first (or prompt the user to). This skill does not handle Vitest setup.

Step 2: Enumerate API handlers

MCP: if pracht dev-mcp is registered, prefer its inspect_*, generate_*, doctor, and verify tools to the CLI.

pracht inspect api --json

For each entry, capture: path (URL), file (source path), and the exported methods (e.g., ["GET", "POST"]). Note: methods only lists named HTTP method exports — a default-export dispatcher yields methods: [], never ["default"]. Current @pracht/cli versions also report a hasDefaultHandler boolean; use it when present. If the field is absent (older CLI) and methods is empty, fall back to the grep in Step 7 to detect a default dispatcher.

Ask the user which subset to scaffold or accept paths via $ARGUMENTS.

Step 3: Generate one test per handler file

Place tests next to the handler with .test.ts suffix (src/api/users/[id].test.ts). Use createApiArgs() from @pracht/test (add it as a dev dependency if missing) to construct ApiRouteArgs:

import { describe, it, expect } from "vitest";
import { createApiArgs, readJson } from "@pracht/test";
import { GET, POST /* import only what the handler exports */ } from "./<file>";

describe("<METHOD> <api-path>", () => {
  it("returns 200 on a valid request", async () => {
    const res = await GET(createApiArgs({ url: "<api-path>" }));
    expect(res).toBeInstanceOf(Response);
    expect(res.status).toBe(200);
  });
});

Pass params for dynamic segments, body for JSON payloads (plain objects are JSON-encoded with the right Content-Type), and use submitForm() for form-encoded/multipart POSTs — it drives the same FormData parsing path defineApi() uses.

Step 4: Generate method-specific cases

For each exported method, emit the smallest realistic case:

MethodDefault case
GETPlain GET → expect(res.status).toBeLessThan(400)
POSTPOST with empty FormData → assert validation behavior
PUTPUT with JSON body → assert 200 or auth-required (401/403)
PATCHPATCH with partial body → assert 200 or 422
DELETEDELETE on a real-shaped path → assert 200/204 or 401

For dynamic segments ([id].ts → /api/users/:id), pick a placeholder param (e.g., id: "test-1") and pass it via params. Surface in the report that the user may need to provide a real fixture.

Step 5: Detect auth-gated APIs

API routes do NOT appear in pracht inspect routes --json (that report covers page routes only) and pracht inspect api --json has no middleware field. API middleware is the single global list configured as defineApp({ api: { middleware: [...] } }) — read the manifest source (src/routes.ts or wherever defineApp lives) to see whether an auth middleware is in that list. If it is, scaffold an extra test:

it("rejects unauthenticated requests", async () => {
  const res = await POST(createApiArgs({ url: "<api-path>", method: "POST" }));
  expect([401, 403, 302]).toContain(res.status);
});

Note: middleware does NOT run when calling the handler directly — this test verifies the handler's own defense if it has one. If the only defense is middleware, mention that in the report and recommend an integration test that goes through the framework's request pipeline (out of scope for this skill). The same applies to the framework's built-in CSRF check: requireSameOrigin (on by default) rejects cross-origin state-changing API requests in the real pipeline but never runs in direct-invocation tests.

Step 6: Validate JSON shape

For handlers that return Response.json(...), generate:

it("returns JSON with the expected keys", async () => {
  const res = await GET(createApiArgs({ url: "<api-path>" }));
  expect(res.headers.get("content-type")).toMatch(/application\/json/);
  const body = await readJson(res);
  expect(body).toEqual(expect.objectContaining({ /* fill in */ }));
});

Step 7: Default-handler dispatchers

If the handler exports default (one function dispatching on request.method), generate a test per HTTP method the handler appears to support (grep for request.method === patterns inside the file).

Step 8: Run

pnpm test
pracht verify --json

Report passes/failures. Mark generated assertions as TODO so the user knows to tighten them.

Rules

  1. Use pracht inspect api --json as the inventory — do not glob.
  2. Only import methods the handler actually exports; otherwise the test fails to load.
  3. Direct-handler invocation skips middleware AND the default-on requireSameOrigin CSRF check. Be explicit in the report.
  4. Test files live next to handlers with .test.ts suffix unless the project already uses a __tests__/ convention (detect and match).
  5. Never overwrite existing test files; emit a .next.test.ts and tell the user to merge.

$ARGUMENTS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.