Grilling the random-prompt endpoint
Designing a one-shot endpoint before writing the first test.
This was meant to be the “trivial” demo — endpoint, returns one prompt, done. The grilling exposed three decisions I would have hand-waved past and regretted later. Cheap insurance.
The session
Agent: Random across what, exactly? Every prompt in the collection? Only the ones tagged as demo prompts? Only published?
Me: Every prompt in the collection. If it’s bundled, it’s eligible. No tag filter.
Agent: Uniform random, or weighted? Recent prompts first? Tag-weighted? Different prompts surfaced to different visitors?
Me: Uniform. Anything fancier is a v2 problem and probably never becomes a v1 problem.
Agent: Should the same visitor get the same prompt if they hit refresh within a second, or always a fresh one?
Me: Always fresh. cache-control: no-store. The whole point is the
refresh-to-discover behaviour.
Agent: What does the response actually contain? Just an id and let the client fetch the body separately, or the full prompt inline?
Me: Full body inline. Self-sufficient response. One request, one prompt, render it.
Agent: What’s the failure mode if the prompts collection is empty —
say someone deletes all the markdown files? 200 with null, 404, 503?
Me: Good catch. 503 with a JSON error body. It’s a server-state problem, not a “not found” — the route exists, the data doesn’t.
Agent: How do we test that the randomness is actually working? 50 calls, count distinct results, assert some minimum?
Me: Yeah. 50 calls, at least 5 distinct ids when the corpus has 5+ prompts. Cheap test, catches the “I forgot to call Math.random” bug.
What we decided
GET /api/random-prompt→ 200,application/json, uniform random over all prompts in the collection.- Response shape:
{ prompt: { id, title, description, tags, demo, body } }. cache-control: no-storeon every response.- Empty corpus → 503 with
{ error: "no prompts indexed" }. - Randomness test: 50 sequential calls, assert ≥ 5 distinct ids when the corpus has ≥ 5 prompts.
- No UI in this slice. Backend only. The copy-button widget comes later.
Three tracer bullets fell out of this naturally: constant-prompt stub
to nail the shape, random selection to nail the behaviour,
empty-corpus 503 to nail the edge case. PRD at /prd/random-prompt.