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".
In this repository33 other files it gives its agents.
- AGENTS.mdAGENTS.md
- add-authSkill
- add-capabilitiesSkill
- add-contentSkill
- add-dbSkill
- add-i18nSkill
- add-imagesSkill
- add-observabilitySkill
- add-openapiSkill
- audit-a11ySkill
- audit-agent-surfaceSkill
- audit-authSkill
- audit-bundlesSkill
- audit-csrfSkill
- audit-depsSkill
- audit-headersSkill
- audit-islandsSkill
- audit-loadersSkill
- audit-redirectsSkill
- audit-secretsSkill
- audit-seoSkill
- audit-shellsSkill
- configure-isgSkill
- migrate-nextjsSkill
- pracht-debugSkill
- pracht-deploySkill
- pracht-scaffoldSkill
- pre-deploySkill
- scaffold-e2eSkill
- scaffold-testsSkill
- tune-render-modeSkill
- typed-routesSkill
- upgrade-prachtSkill
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
- Very specific: line 29
If the project has no vitest dependency, run scaffold-tests first (or prompt the user to).
- Very specific: line 48
Ask the user which subset to scaffold or accept paths via $ARGUMENTS.
- Very specific: line 52
Place tests next to the handler with .test.ts suffix (src/api/users/[id].test.ts).
- Very specific: line 130
If the handler exports default (one function dispatching on request.method), generate a test per HTTP method the handler appears to support …
- Very specific: line 146
Use pracht inspect api --json as the inventory — do not glob.
- Distinctive: line 147
Only import methods the handler actually exports.
- Very specific: line 151
Test files live next to handlers with .test.ts suffix unless the project already uses a __tests__/ convention (detect and match).
- Very specific: line 153
Never overwrite existing test files.
≈ “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.
Files
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 --jsonFor 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:
| Method | Default case |
GET | Plain GET → expect(res.status).toBeLessThan(400) |
POST | POST with empty FormData → assert validation behavior |
PUT | PUT with JSON body → assert 200 or auth-required (401/403) |
PATCH | PATCH with partial body → assert 200 or 422 |
DELETE | DELETE 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 --jsonReport passes/failures. Mark generated assertions as TODO so the user knows to tighten them.
Rules
- Use
pracht inspect api --jsonas the inventory — do not glob. - Only import methods the handler actually exports; otherwise the test fails to load.
- Direct-handler invocation skips middleware AND the default-on
requireSameOriginCSRF check. Be explicit in the report. - Test files live next to handlers with
.test.tssuffix unless the project already uses a__tests__/convention (detect and match). - Never overwrite existing test files; emit a
.next.test.tsand tell the user to merge.
$ARGUMENTS
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
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.

