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

4.1.5 Tests — sprint state machine, one-active guard, association + same-project guard, rank ordering, bounded backlog at scale

Estimate: 22m · Depends on: 4.1.3, 4.1.4

The closing test subtask — Vitest over the real Postgres (the project convention: no mocks except getSession; tests/helpers/db.ts truncation), proving the data model + rules the rest of Epic 4 depends on. No UI/E2E here (4.1 has no UI; the sprint E2E rides Story 4.5.4 once a board renders sprints).

State machine + guards: assertSprintTransition accepts planned→active + active→complete and rejects every skip/reopen/self transition; the sprint_one_active_per_project partial-unique index rejects a second active sprint in a project (DB-level, asserted via the repository) while allowing one active sprint per project across two projects; createSprint default-naming + window validation; deleteSprint drops a planned sprint and its issues fall to the backlog (SetNull), and refuses the active sprint.

Association + rank: assignToSprint sets sprint_id and removes the issue from the backlog read; the same-project guard rejects a cross-project assignment; moveToBacklog restores it in rank order; rankIssue lands an issue strictly between two neighbours with a SINGLE-row write (assert no other row's rank changed) and handles append/prepend; new issues get a backlog_rank at creation; each write records a 1.4.6 revision row.

Bounded reads (finding #57): getBacklog is cursor-paginated in backlog_rank order and returns the total count; seed a large backlog and assert the read returns ONE bounded page (not every row) and the cursor walks the full ordering deterministically; the rank writes stay O(1) on the large set.

Tenancy: a cross-workspace sprint/backlog read or write is denied by the finding-#26 gate.

Acceptance criteria

  • pnpm test (real Postgres) covers: the transition guard (all legal + illegal pairs), the one-active-per-project index (rejects 2nd active in a project; allows per-project), create-default-naming + window validation, delete-to-backlog + active-delete refusal, association + same-project guard, moveToBacklog, single-row rankIssue (append/prepend + between), create-time rank, the 1.4.6 revision writes, and the cross-workspace denial.
  • A scale test (seeded large backlog) asserts getBacklog returns one bounded page + the total count (never load-all) and the cursor walks the whole rank order; rank writes are O(1).
  • pnpm test:coverage keeps the Story-4.1 service/repository files ≥90% branch/fn/line (the CI coverage gate); the suite uses the real-Postgres harness + the single allowed getSession mock.

Context refs

  • tests/helpers/db.ts — real-Postgres truncation harness + the large-seed fixture pattern
  • Story 4.1.3 (service + guard) + 4.1.4 (association + rank + bounded reads) — the units under test
  • motir-core-coverage-gate (≥90% per-file; empty-input guards need a direct test) + motir-core-local-postgres (the sandbox already has PG@5433) + motir-core/CLAUDE.md (real-Postgres, no mocks)
  • Story 4.5.4 — where the sprint-rendering E2E lives (4.1 ships no UI, so no E2E here)