test(forge): cross-adapter ForgePort conformance suite (MF-6) #312

Merged
code-lead merged 1 commit from feat/297-mf6-forgeport-conformance into main 2026-04-24 10:00:02 +00:00
Collaborator

Summary

  • New apps/server/src/infrastructure/forge/conformance.test.ts runs 37 shared scenarios × 3 adapters (ForgejoAdapter, GitHubAdapter, GitLabAdapter) + a dedicated divergence block — 112 tests, 379 expect() calls, all passing.
  • Per-forge JSON fixtures under apps/server/test/fixtures/forge/{forgejo,github,gitlab}/ are the single source of truth for raw forge payloads; the shared test body asserts only domain shapes (ForgeIssue, ForgePullRequest, ForgeReview, ForgeIssueRef, ForgeWorkflowRun, ForgeDirEntry, ForgeComment).
  • fetch is stubbed via a per-test URL router (one function per adapter) — no live forge traffic, no env assumptions.

Coverage

Per the MF-6 acceptance criteria:

  1. Shape mapping — every port method returns domain shapes, not forge-native field names (html_urlhtmlUrl, { name } label objects → string[], milestone object → title string, assignee user objects → username array, head.ref/shaheadRef/headSha, etc.).
  2. Null contractgetIssue, getPullRequest, readFile return null on 404 (no throws).
  3. Boolean contractupdateAssignees, addLabels, removeLabel, patchIssue, createComment, requestReview, removeReviewRequest, addBlocker, writeFile return true/false, never throw. addLabels([]) returns false on all three.
  4. Label name↔id uniformity — all three adapters accept names; Forgejo resolves IDs internally, GitHub/GitLab accept names directly. Colors normalised WITHOUT # prefix across the port.
  5. Dependency parsing — Forgejo native /dependencies + /blocks, GitLab Premium /issues/:iid/links, GitHub body-text parse of Depends on #N / Blocks #N. GitLab Free-tier fallback exercised in divergence block.
  6. CI aggregation — shared assertions for "success" / "failure" on all three adapters.
  7. Review state mappingapproved / changes_requested / comment flatten identically from Forgejo native reviews, GitHub reviews, GitLab approvals + thumbsdown emoji + notes.

Divergences flagged (not fixed — guardrails honoured)

Surfaced with dedicated tests in a Cross-adapter divergence — documented, enforced block so the drift is pinned rather than silently tolerated:

  • getAggregateStatus string union drift — Forgejo + GitHub return "pending" when CI is queued; GitLab routes through toPipelineStatus and returns "queued" instead. Consumers currently switch on a broader set. Suggested follow-up: normalise the GitLab adapter to emit "pending" for parity. I did not edit gitlab-adapter.ts per the guardrails.
  • getBlocked asymmetry on Free-tier GitLab — documented in the adapter already; the suite pins it with its own test (returns [] on Free, native on Premium). No action needed; behaviour matches the adapter's inline comment.

Out of scope

  • Live-network contract tests (MF-6 spec "Out of scope" — runnable via just forge-live in a follow-up).
  • Rate-limit / pagination scenarios for GitLab + Forgejo (spec lists rate-limit; github-adapter.test.ts already covers the rate-limit retry path — extending that into the conformance suite would require adapter-specific plumbing that's out of scope for a cross-forge shape check).
  • Fixing the GitLab getAggregateStatus drift — needs a separate issue.

Test plan

  • bun test apps/server/src/infrastructure/forge/conformance.test.ts — 112 pass / 0 fail
  • bun x turbo run typecheck — clean on all four packages
  • bun x biome check apps/server/src/infrastructure/forge/conformance.test.ts apps/server/test/fixtures/forge/ — clean
  • Full bun test under apps/server/ — pre-existing sweeper + foreman failures unchanged (unrelated; reproduced on a clean main tree)

Closes #297


Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

## Summary - New `apps/server/src/infrastructure/forge/conformance.test.ts` runs 37 shared scenarios × 3 adapters (`ForgejoAdapter`, `GitHubAdapter`, `GitLabAdapter`) + a dedicated divergence block — **112 tests, 379 expect()** calls, all passing. - Per-forge JSON fixtures under `apps/server/test/fixtures/forge/{forgejo,github,gitlab}/` are the single source of truth for raw forge payloads; the shared test body asserts only domain shapes (`ForgeIssue`, `ForgePullRequest`, `ForgeReview`, `ForgeIssueRef`, `ForgeWorkflowRun`, `ForgeDirEntry`, `ForgeComment`). - `fetch` is stubbed via a per-test URL router (one function per adapter) — no live forge traffic, no env assumptions. ### Coverage Per the MF-6 acceptance criteria: 1. **Shape mapping** — every port method returns domain shapes, not forge-native field names (`html_url` → `htmlUrl`, `{ name }` label objects → `string[]`, milestone object → title string, assignee user objects → username array, `head.ref/sha` → `headRef/headSha`, etc.). 2. **Null contract** — `getIssue`, `getPullRequest`, `readFile` return `null` on 404 (no throws). 3. **Boolean contract** — `updateAssignees`, `addLabels`, `removeLabel`, `patchIssue`, `createComment`, `requestReview`, `removeReviewRequest`, `addBlocker`, `writeFile` return `true`/`false`, never throw. `addLabels([])` returns `false` on all three. 4. **Label name↔id uniformity** — all three adapters accept names; Forgejo resolves IDs internally, GitHub/GitLab accept names directly. Colors normalised WITHOUT `#` prefix across the port. 5. **Dependency parsing** — Forgejo native `/dependencies` + `/blocks`, GitLab Premium `/issues/:iid/links`, GitHub body-text parse of `Depends on #N` / `Blocks #N`. GitLab Free-tier fallback exercised in divergence block. 6. **CI aggregation** — shared assertions for `"success"` / `"failure"` on all three adapters. 7. **Review state mapping** — `approved` / `changes_requested` / `comment` flatten identically from Forgejo native reviews, GitHub `reviews`, GitLab approvals + thumbsdown emoji + notes. ### Divergences flagged (not fixed — guardrails honoured) Surfaced with dedicated tests in a `Cross-adapter divergence — documented, enforced` block so the drift is pinned rather than silently tolerated: - **`getAggregateStatus` string union drift** — Forgejo + GitHub return `"pending"` when CI is queued; GitLab routes through `toPipelineStatus` and returns `"queued"` instead. Consumers currently switch on a broader set. Suggested follow-up: normalise the GitLab adapter to emit `"pending"` for parity. I did not edit `gitlab-adapter.ts` per the guardrails. - **`getBlocked` asymmetry on Free-tier GitLab** — documented in the adapter already; the suite pins it with its own test (returns `[]` on Free, native on Premium). No action needed; behaviour matches the adapter's inline comment. ### Out of scope - Live-network contract tests (MF-6 spec "Out of scope" — runnable via `just forge-live` in a follow-up). - Rate-limit / pagination scenarios for GitLab + Forgejo (spec lists rate-limit; `github-adapter.test.ts` already covers the rate-limit retry path — extending that into the conformance suite would require adapter-specific plumbing that's out of scope for a cross-forge shape check). - Fixing the GitLab `getAggregateStatus` drift — needs a separate issue. ## Test plan - [x] `bun test apps/server/src/infrastructure/forge/conformance.test.ts` — 112 pass / 0 fail - [x] `bun x turbo run typecheck` — clean on all four packages - [x] `bun x biome check apps/server/src/infrastructure/forge/conformance.test.ts apps/server/test/fixtures/forge/` — clean - [x] Full `bun test` under `apps/server/` — pre-existing sweeper + foreman failures unchanged (unrelated; reproduced on a clean `main` tree) Closes #297 --- Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test(forge): cross-adapter ForgePort conformance suite (MF-6)
All checks were successful
qa / qa (pull_request) Successful in 3m55s
qa / dockerfile (pull_request) Successful in 10s
ad213413d4
Adds `apps/server/src/infrastructure/forge/conformance.test.ts` — a
shared battery of scenarios that every `ForgePort` implementation
(ForgejoAdapter, GitHubAdapter, GitLabAdapter) must pass. Each scenario
pulls from per-forge JSON fixtures under
`apps/server/test/fixtures/forge/{forgejo,github,gitlab}/` and asserts
the adapter returns the domain shape (`ForgeIssue`, `ForgePullRequest`,
`ForgeReview`, `ForgeIssueRef`, `ForgeWorkflowRun`, …) rather than the
forge-native payload.

Coverage:
- Shape mapping for getIssue / getPullRequest / listReviews / listComments
  / listWorkflowRuns / listRepoLabels / listAccessibleRepos / readFile /
  listDir.
- Null contract on 404 (getIssue, getPullRequest, readFile).
- Boolean contract on mutations (addLabels, removeLabel, updateAssignees,
  patchIssue, createComment, requestReview, removeReviewRequest, addBlocker,
  writeFile) — true on 2xx, false on 5xx.
- Label name↔id uniformity (all three adapters accept names; Forgejo
  resolves internally).
- Dependency parsing — Forgejo native, GitLab Premium issue_links,
  GitHub body-text "Depends on #N" / "Blocks #N".
- CI aggregation — Forgejo/GitHub return "success|failure|pending",
  GitLab returns "queued" for pending (documented drift, asserted).
- Review state mapping — APPROVED / CHANGES_REQUESTED / COMMENT flattened
  uniformly from each forge's native review shape (GitHub reviews,
  Forgejo native, GitLab approvals + thumbsdown emoji + notes).

All HTTP calls go through a `fetch` stub; no live forge traffic.
112 tests total across the three adapters + a small divergence-scenarios
block.

Closes #297
code-lead deleted branch feat/297-mf6-forgeport-conformance 2026-04-24 10:00:03 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!312
No description provided.