MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-50

1.4.7 Integration tests: kind/depth/cycle triggers, concurrent key allocation, revision atomicity, link cycle + ready-set

Done
Description

Estimate: 26m · Depends on: 1.4.4, 1.4.5, 1.4.6

Comprehensive integration tests against a real Postgres covering the structural invariants. Per Yue's standing rule (and feedback_planner_decides_user_approves's no-mocks-on-DB principle), these tests exercise the actual Postgres triggers + RLS + service-layer transactions. They are the safety net Epic 2-7 will lean on every time they touch the work-item path; landing them in 1.4 means later Epics can confidently extend without fear of silently breaking the kind-parent rule.

Test areas:

  • Kind-parent matrix: for every (parentKind, childKind) pair in the AC matrix, assert legal pairs succeed and illegal pairs reject with IllegalParentTypeError. Drive via workItemsService.createWorkItem AND via direct repo writes (proves the trigger fires regardless of path).
  • Depth limit: build a 4-deep chain (epic → story → task → subtask). Asserts the 4-level legal max succeeds. Then attempt to create a child of the depth-4 subtask — rejects with DepthLimitExceededError.
  • Cycle prevention: create A → B → C; attempt to re-parent A under C — rejects with ParentCycleError.
  • Concurrent key allocation: fire 20 concurrent createWorkItem calls against the same project; assert the resulting keys are 1..20 (or contiguous from the starting count) with no duplicates and no lost slots beyond rolled-back transactions (none roll back here, so no gaps expected).
  • Workspace RLS isolation: user A in workspace W1, user B in workspace W2. With W1's GUC set, B's items are invisible. With NO GUC set, all tables return zero rows. WITH CHECK: A cannot insert a work item with W2's workspace_id (constraint rejection).
  • Project RLS narrowing: with W1 + P1 set, P2's items are invisible (even though both belong to W1). With W1 set and no project, both projects' items are visible.
  • Fractional indexing: reorder via moveWorkItem; the resulting positions sort lexically as expected. Edge cases: move-to-start, move-to-end, move-between.
  • Revision atomicity: inject a failure inside the revision-repo write; assert the work-item write rolls back too. Inject a failure in the work-item write; assert no revision row is left orphaned.
  • Revision diff correctness: update only title → revision has { title: { from, to } } and nothing else. Update title + assigneeId → both in the diff. No-op patch → no revision written, no transaction opened. Update explanationMd while explanationSource = ai_draft → diff includes both explanationMd AND the auto-transitioned explanationSource: { from: ai_draft, to: user_edited } (the source transition is itself an audit-worthy event).
  • Explanation-source state machine: a fresh-row create with explanationMd = NULL has source user_authored. A subsequent update writing explanationMd + explicit source ai_draft (the path AI-drafting Epic 7 takes) sets source = ai_draft. A subsequent update patching only explanationMd (no explicit source in the patch) auto-transitions source to user_edited — verified by an integration test in 1.4.7. A subsequent update with explicit source ai_draft (a regenerate) resets the badge. Direct PATCH of explanationSource alone (no explanationMd) is allowed (e.g., user manually dismisses the AI-draft badge) — the diff records it.
  • Link cycle prevention: A is_blocked_by B; attempt B is_blocked_by A → trigger rejects with WorkItemLinkCycleError. Deeper cycle: A→B→C→A; rejected on the closing edge. relates_to A↔B does NOT trigger cycle check (intended).
  • Self-link rejection: linkWorkItems(A, A, *) rejects with SelfLinkError.
  • Cross-workspace link rejection: A in W1, B in W2; linkWorkItems(A, B, *) rejects with CrossWorkspaceLinkError at the service layer, and the trigger backstops if the service is bypassed.
  • Symmetric relates_to: linkWorkItems(A, B, 'relates_to') produces TWO rows (A→B and B→A); unlinkWorkItems on either deletes both.
  • Duplicate link rejection: linkWorkItems(A, B, 'is_blocked_by') called twice — second call rejects with DuplicateLinkError (unique constraint).
  • Link revision audit: linkWorkItems writes a revision row on the from item with the added link in the diff; unlinkWorkItems writes the removal.
  • Ready-set predicate: A is_blocked_by B + C; isReady(A) returns false. Mark B done → still false (C blocks). Mark C done → returns true. Unlink C while B is still open → returns false again.
  • Cross-project links work: A in project P1, B in project P2 (same workspace W1); linkWorkItems succeeds. getBlockers(A) returns B even when called under a P1-narrowed project context (link table is workspace-scoped, not project-scoped).

What you'll do: Add tests under tests/integration/work-items/. Use the test-fixture helpers from 1.2.7 + 1.3.5 that spin up users + workspaces + projects against the real Postgres. Add workItemFixtures.ts in tests/fixtures for repeatable work-item setups. Tests run against the same docker-compose'd Postgres as 1.2.7 / 1.3.5.

Acceptance criteria

  • New tests under tests/integration/work-items/ cover every area enumerated above; every test names the invariant it protects in its describe-block.
  • Tests run against the real Postgres (per Yue's no-mocks rule).
  • Concurrent-key-allocation test uses Promise.all over 20 createWorkItem calls; resulting keys form a contiguous set.
  • RLS tests reset the GUC between cases to avoid cross-test bleed; the test suite passes when run in parallel under Vitest's default concurrency.
  • Revision-atomicity tests prove transactional rollback for both the work-item-fails and revision-fails directions.
  • Test suite green; CI green; coverage report shows the workItemsService + workItemRepository + workItemLinkRepository + workItemRevisionRepository at >= 90%.
  • All quality gates green; existing suite stays green.

Context refs

  • tests/integration/projects/ and tests/integration/workspaces/ — the integration-test pattern, fixture helpers
  • tests/fixtures/userFixtures.ts / workspaceFixtures.ts / projectFixtures.ts
  • lib/services/workItemsService.ts + repos + errors (the system under test)
  • This Story page — the invariants the tests must protect