Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-1862Done

11.1.6 End-to-end conformance suite — drive `/api/v1` as an EXTERNAL HTTP client with a real PAT against the real route + real Postgres

Repo: motir-core. One PR. The story's mandatory end-to-end test subtask — the one that proves the API works for the client it is built for: something outside the process, holding only a token and a URL.

blocked_by 11.1.2 + 11.1.3 + 11.1.4.

⚠️ Playwright is the WRONG tool here — a justified deviation, stated on the card

The canonical story composition asks for a Playwright E2E. This Story has no browser surface (the whole story is headless HTTP; the API reference page is 11.4's and carries its own E2E). Driving an HTTP API through a browser would test Playwright's request fixture, not the product, and would pay the E2E lane's cost for nothing.

The honest analogue — and the shipped precedent — is an external-client suite over real HTTP: boot the real server, mint a real PAT, and call it across a real socket. tests/cli/cli-story.test.ts already does exactly this for the CLI (built binary → real /api/mcp → real Postgres) using tests/helpers/mcpHttpServer.ts. Reuse that harness. This is a deviation in TOOL, not in rigour: the end-to-end user flow is still driven end to end, against the real thing.

What this covers that 11.1.5 cannot

The vitest gate exercises handlers and their composition IN-PROCESS. It cannot see anything that only exists once a request crosses a socket:

  • The route is actually mounted and reachable at the /api/v1/… path — a handler that exists but is wired at the wrong path passes every in-process test.
  • Real header transport — that Authorization: Bearer … survives the framework's parsing, and that the X-RateLimit-* and request-id headers reach a client rather than being set on an object nobody serialises.
  • Real status codes and real bodies as an HTTP client sees them, including that a 500 does not leak a stack over the wire.
  • The documented client journey works verbatim — the exact sequence the getting-started docs will promise.

The journey to drive (the Story's verification_recipe, automated)

  1. Mint a PAT with read (via the shipped token service, as a user would via settings).
  2. GET /api/v1/me → 200; the identity and granted scopes are correct.
  3. GET /api/v1/workspaces → page through a collection larger than one page using ONLY the returned cursor, as an external client must.
  4. Call with no token, a garbage token, and a revoked token → 401 each time, indistinguishable bodies.
  5. Call an endpoint whose scope the token lacks → 403 naming no internal detail.
  6. Exhaust the budget → 429 with usable X-RateLimit-Reset; confirm the headers were present on the preceding 200s too.
  7. Request a resource in ANOTHER workspace → 404, not 403.
  8. Send a malformed cursor → 422 with a code.

Scope BOUNDARY

Ends at the HTTP conformance of THIS story's endpoints (/me, /workspaces) and the envelope behaviours. It does NOT test resource endpoints that do not exist yet (11.2 / 11.3 carry their own, and pulling their cases forward would premise this card on unbuilt code) — but the harness it builds MUST be extensible so those stories add cases rather than a second harness. It does NOT use Playwright, does NOT add a browser test, does NOT test the API reference page, and does NOT re-cover what 11.1.5 asserts in-process. It changes no production code.

No acceptance video — this Story has no user-observable surface, so it is exempt under the acceptance-video rule's non-UI carve-out.

Acceptance criteria

  • The suite boots the REAL server and calls /api/v1 over a real socket with a real PAT against real Postgres — no in-process handler invocation, no mocked fetch.
  • Every step of the journey above is asserted, in order, as a single coherent flow rather than eight unrelated cases.
  • Pagination is walked using ONLY the cursor the previous response returned — never a hand-constructed one, since that is precisely what an external client cannot do.
  • The three unauthenticated cases produce byte-identical bodies (the non-disclosure property, verified at the wire, not just at the handler).
  • X-RateLimit-* headers are asserted present on a SUCCESSFUL response, not only on the 429.
  • A forced 500 returns no stack, no Prisma text and no code over the wire.
  • The harness is reusable: adding a case for a new v1 endpoint requires no new server-boot or auth plumbing — demonstrated by the file's structure.
  • The suite is deterministic under CI load: every assertion waits on the response it depends on, and no step relies on a fixed sleep.

Context refs

  • tests/cli/cli-story.test.ts — the shipped built-binary-over-real-HTTP suite; the pattern and rigour bar to mirror.
  • tests/helpers/mcpHttpServer.tsstartMcpHttpServer, the real-server harness to reuse/extend for /api/v1.
  • tests/helpers/db.ts — the real-Postgres truncate harness.
  • lib/services/apiTokensService.tscreate(...), how the suite mints a real scoped PAT.
  • lib/mcp/scopes.ts — the scopes the suite grants and withholds.
  • Blockers: 11.1.2 · 11.1.3 · 11.1.4. Sibling gate: 11.1.5. Parent story: 11.1.