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-2067Done

11.3.10 Story vitest gate — coverage floor, the write→read seams, and the contract guards the planning endpoints owe

The story-level vitest gate for 11.3, run AFTER its feature subtasks merge — it measures their real, merged result, so it cannot run before they land. Real Postgres, not mocks.

It is a gate, not a list of cases. Three jobs, resolved at run time:

1. Coverage floor

Run coverage over the story's changed surface — lib/api/v1/pagination.ts's new surface, the project / sprint / ready schema modules, sprintsService.getById, and every new route file — and wherever it sits below the project's ≥90% per-file branch/fn/line gate, WRITE the missing unit and branch tests to reach it. Each subtask's own units are the floor; this tops up the seams between them. "Already covered by the per-subtask floor" is the EXPECTED normal here, not a reason the card has nothing to do.

2. Integration seams — the writer→consumer paths the units mock

Drive one subtask's REAL output back through the next's REAL consumer, which is where key drift hides:

  • Create → list → read. A sprint created through POST /api/v1/projects/{projectKey}/sprints appears in GET .../sprints and reads identically through GET /api/v1/sprints/{sprintId} — the same sprint, the same field values, through three independently-written mappers.
  • Start → every read. After POST .../start, the activation baseline (committedIssueCount / committedPoints) is non-null on BOTH sprint reads, and state is active everywhere it appears.
  • Move → the two ranked collections. Items moved into a sprint appear in GET /api/v1/sprints/{sprintId}/work-items at the rank tail and are ABSENT from GET .../backlog; moved back, the inverse — the round trip the membership card deliberately left to this level rather than depending on a sibling endpoint.
  • Complete → carry-over. After POST .../complete, the unfinished members are in the destination and the done-category ones remain on the completed sprint.
  • Move → ready. Sprint membership does not change what GET .../ready returns (readiness is not sprint-scoped) — the seam that catches a route quietly filtering the ready set by sprint.

3. Architecture / contract guards — what coverage percentage cannot see

  • No route re-derives readiness, rank, or sprint state. The story's central risk: a route that computes blockedBy.every(done) instead of calling listReady passes every functional test on a shallow fixture and is wrong on a deep tree. Guard it structurally (no route may reach collectReadyLeaves-equivalent logic, sort a ready page, or compare status categories), and back it with the deep-tree fixture where the parent-ready cascade excludes an item whose own blockers are all done.
  • No v1 route imports from lib/mcp/. The two transports align through the service; an import is how they start sharing a shape neither owns. This is a NEW guard — 11.1's shipped tree-wide guard checks Prisma, transactions, the wrapper and the declared scope, not this.
  • Every v1 response is a schema's output. No route in this story's tree hand-shapes a body inline (Amendment 2), and no work-item cuid appears in any response — the same guard tests/api/v1/story-gate.test.ts runs, re-run over the new routes.
  • Cursor isolation. A cursor issued by any one of this story's collections is refused by every other — asserted across the real endpoints, not just at the codec's unit level.
  • The two closed vocabularies are TOTAL by compile-time guard (sprint state, project accessLevel) — assert the guards exist rather than re-testing the values.
  • Cross-tenant isolation across the new tree: every endpoint taking a projectKey or sprintId answers 404, never 403, for another tenant's resource.

Do NOT enumerate at plan time which cases the feature subtasks will cover — that is unknowable until they are built.

Acceptance criteria

  • The per-file coverage floor (≥90%) holds on every file this story added or changed, with the top-up tests written where it did not.
  • Each integration seam above is covered by at least one test that drives REAL output through a REAL consumer against real Postgres.
  • The re-derivation guard exists and FAILS when violated — proven by the shipped pattern of asserting the guard against a deliberately-violating fixture, as tests/api/v1/story-gate.test.ts does for its own guards (a guard nobody has seen fail is not a guard).
  • The lib/mcp/ import guard finds the route tree at all before asserting over it (a guard over zero files proves nothing) and fails on a planted violation.
  • One gate card, one repo, ONE PR against motir-core.

Context refs

  • tests/api/v1/story-gate.test.ts — 11.1's shipped gate: the tree-walking architecture guards, the guards-fail-when-violated pattern, and the cross-tenant sweep to extend.
  • tests/api/v1/work-item-story-gate.test.ts11.2.11's resource-level gate, the closest precedent for this card's shape.
  • motir-core/CLAUDE.md § coverage — the ≥90% per-file gate and how it is run.
  • Blockers: every code subtask of this story — 11.3.2, 11.3.3, 11.3.4, 11.3.5, 11.3.6, 11.3.7, 11.3.8, 11.3.9. Parent story: 11.3.