task/34: Testkit self-serve plan + report endpoints

## Routes added to artifacts/api-server/src/routes/testkit.ts

### GET /api/testkit/plan
- Returns TIMMY_TEST_PLAN.md verbatim as text/markdown; charset=utf-8
- Reads file at request time (not on startup) so edits to the plan are picked
  up without server restart
- Path resolves via import.meta.url + dirname() → 4 levels up to project root
  (handles both dev/tsx and compiled dist/routes/ directories)

### GET /api/testkit/report
- Returns only the content from "## Report template" heading to end-of-file
- Content-Type: text/plain; charset=utf-8 — ready to copy and fill in
- Slice is found with indexOf("## Report template"); 500 if marker absent
- Uses the same PLAN_PATH as /api/testkit/plan (single source of truth)

## Deviation: __dirname → import.meta.url
Original plan said "resolve relative to project root regardless of cwd".
The codebase runs as ESM (tsx / ts-node with ESM), so __dirname is not
defined. Fixed by using dirname(fileURLToPath(import.meta.url)) instead —
equivalent semantics, correct in both dev and compiled output.

## AGENTS.md — Testing section added
Three-step workflow documented between "Branch and PR conventions" and
"Stub mode" sections:
  1. curl <BASE>/api/testkit/plan — fetch plan before starting
  2. curl -s <BASE>/api/testkit | bash — run suite after implementing
  3. curl <BASE>/api/testkit/report — fetch report template to fill in

## Unchanged
- GET /api/testkit bash script generation: untouched
- No new test cases or script modifications

## TypeScript: 0 errors. Smoke tests all pass:
  - /api/testkit/plan → 200 text/markdown, full TIMMY_TEST_PLAN.md content
  - /api/testkit/report → 200 text/plain, starts at "## Report template"
  - /api/testkit → 200 bash script, unchanged
This commit is contained in:
alexpaynex
2026-03-19 21:02:43 +00:00
parent 66eb8ed394
commit 56eb7bc56e
2 changed files with 73 additions and 0 deletions

View File

@@ -39,6 +39,30 @@ Set `GITEA_TOKEN` or write the token to `.gitea-credentials` (gitignored). Never
- Open a PR on Gitea and squash-merge after review
- CI runs `pnpm typecheck && pnpm lint` on every PR automatically
## Testing
Executor agents should follow this three-step workflow when implementing or verifying any change:
**1. Fetch the test plan before starting**
```bash
curl <BASE>/api/testkit/plan
```
Returns `TIMMY_TEST_PLAN.md` — full architecture notes, route descriptions, and expected behaviour for all 24 tests. Read this first so you understand what each endpoint is supposed to do before touching the code.
**2. Run the full test suite after implementing**
```bash
curl -s <BASE>/api/testkit | bash
```
The server returns a self-contained bash script with the base URL already baked in. Requirements: `curl`, `bash`, `jq` — nothing else. All 24 tests must pass (FAIL=0) before submitting.
**3. Fill in and submit the report**
```bash
curl <BASE>/api/testkit/report
```
Returns just the report template section ready to copy and fill in. Attach the completed report to your PR or task output.
Where `<BASE>` is the running server URL, e.g. `http://localhost:8080` locally or the Replit dev URL in CI.
## Stub mode
The API server starts without Lightning or AI credentials:

View File

@@ -1,4 +1,7 @@
import { Router, type Request, type Response } from "express";
import { readFileSync } from "fs";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
const router = Router();
@@ -757,4 +760,50 @@ if [[ "\$FAIL" -gt 0 ]]; then exit 1; fi
res.send(script);
});
/**
* GET /api/testkit/plan
*
* Returns TIMMY_TEST_PLAN.md verbatim as text/markdown.
* Path resolves from the project root regardless of cwd.
*/
const _dirname = dirname(fileURLToPath(import.meta.url));
const PLAN_PATH = resolve(_dirname, "../../../../TIMMY_TEST_PLAN.md");
router.get("/testkit/plan", (_req: Request, res: Response) => {
let content: string;
try {
content = readFileSync(PLAN_PATH, "utf-8");
} catch {
res.status(500).json({ error: "TIMMY_TEST_PLAN.md not found on server" });
return;
}
res.setHeader("Content-Type", "text/markdown; charset=utf-8");
res.send(content);
});
/**
* GET /api/testkit/report
*
* Returns only the report template section from TIMMY_TEST_PLAN.md —
* everything from the "## Report template" heading to end-of-file.
* Returned as text/plain so agents can copy and fill in directly.
*/
router.get("/testkit/report", (_req: Request, res: Response) => {
let content: string;
try {
content = readFileSync(PLAN_PATH, "utf-8");
} catch {
res.status(500).json({ error: "TIMMY_TEST_PLAN.md not found on server" });
return;
}
const marker = "## Report template";
const idx = content.indexOf(marker);
if (idx === -1) {
res.status(500).json({ error: "Report template section not found in plan" });
return;
}
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.send(content.slice(idx));
});
export default router;