Canonical fixture data
Every test in this plan asserts against one mock GitLab repository. The mock server (T0.1), the fixture site (T0.2), the unit tests and the Playwright specs all use the values on this page. Do not invent other values; if a test needs something not listed here, add it here first in the same commit.
Project
| Field | Value |
|---|---|
| Project id | 1 |
| Project path | group/project |
| Default branch | main |
| Document path | src/content/doc.md |
| Site id (service) | demo |
| Content roots (service) | ["src/content"] |
The mock’s public and internal GitLab URL are the mock’s own origin. In the
Playwright harness that is http://localhost:9999.
Users
| id | name | username | avatar_url | web_url | |
|---|---|---|---|---|---|
| 2 | Test User | test | [email protected] | /avatar.png | /test |
GET /api/v4/[email protected] returns [user 2].
GET /api/v4/user with Authorization: Bearer mock-user-token returns user 2
including email.
Commits on main (newest first)
| Name | sha | short | title | committed_date | parent_ids | signature |
|---|---|---|---|---|---|---|
| c3 | "c".repeat(40) | cccccccc | Add section | 2026-07-30T12:00:00Z | [c2] | PGP, verified |
| c2 | "b".repeat(40) | bbbbbbbb | Revise intro | 2026-07-29T12:00:00Z | [c1] | 404 (unsigned) |
| c1 | "a".repeat(40) | aaaaaaaa | Add doc | 2026-07-28T12:00:00Z | [] | 404 (unsigned) |
All commits: author_name: "Test User", author_email: "[email protected]",
web_url: "<public>/group/project/-/commit/<sha>", stats: {additions, deletions, total}
may be any integers.
GET /api/v4/projects/1/repository/commits?path=src/content/doc.md&ref_name=main
returns [c3, c2, c1]. With ref_name=revise it returns [d1, c3, c2, c1].
Tags: v1.0.0 at c3. GET .../repository/tags returns [{ name: "v1.0.0", commit: { id: c3 } }].
Document content by ref
Exact text, each file ends with a single newline. Refs resolve for both the sha and the branch name.
V1 at c1:
# Title
Old paragraph.
V2 at c2:
# Title
Revised paragraph.
V3 at c3 and at main:
# Title
Revised paragraph.
## Details
Details paragraph.
VMR at d1 and at revise:
# Title
Proposed paragraph.
## Details
Details paragraph.
GET /api/v4/projects/1/repository/files/src%2Fcontent%2Fdoc.md?ref=<ref> returns
{ content: base64(text), blob_id: "<ref>", commit_id: <sha of ref>, last_commit_id: <sha of ref> }.
A ref the table does not list returns 404.
The fixture site file test/fixtures/site/src/content/doc.md must equal V3
byte for byte. A unit test enforces this (T0.2).
Block ids
Derived by parseDocumentBlocks in semantic.js (package root after T2.1). Listed here so
tests can assert them; never hand-edit the algorithm to match this table.
| Version | Block ids in order |
|---|---|
| V1 | document:heading:title, title:paragraph:paragraph |
| V2 | same ids as V1; paragraph content differs |
| V3 | document:heading:title, title:paragraph:paragraph, title:heading:details, title/details:paragraph:paragraph |
| VMR | same ids as V3; paragraph content differs |
Expected semantic operations:
diffDocumentBlocks(V1, V2): onereplaceoftitle:paragraph:paragraph, confidencehigh.diffDocumentBlocks(V2, V3): twoinserts:title:heading:detailsaftertitle:paragraph:paragraph, thentitle/details:paragraph:paragraphaftertitle:heading:details.diffDocumentBlocks("", V1): twoinserts:document:heading:title(afterBlockIdnull),title:paragraph:paragraph.diffDocumentBlocks(V3, VMR): onereplaceoftitle:paragraph:paragraph, beforeRevised paragraph., afterProposed paragraph..
Expected blockHistory for the document at main (T2.3), newest first:
{
"document:heading:title": [{ "sha": "aaaa…", "type": "insert" }],
"title:paragraph:paragraph": [{ "sha": "bbbb…", "type": "replace" }, { "sha": "aaaa…", "type": "insert" }],
"title:heading:details": [{ "sha": "cccc…", "type": "insert" }],
"title/details:paragraph:paragraph": [{ "sha": "cccc…", "type": "insert" }]
}
Blocks changed since a revision (T3.6): since c1 = 3, since c2 = 2, since c3 = 0.
Expected diffWords("Revised paragraph.", "Proposed paragraph.") (T2.6):
[
{ "type": "delete", "text": "Revised" },
{ "type": "insert", "text": "Proposed" },
{ "type": "equal", "text": " paragraph." }
]
Merge request !7
| Field | Value |
|---|---|
| iid | 7 |
| title | Revise wording |
| state | opened, draft: false |
| source_branch / target_branch | revise / main |
| sha (head) | d1 = "d".repeat(40) |
| source_project_id | 1 |
| detailed_merge_status | mergeable, has_conflicts: false |
| diff_refs | { base_sha: c3, head_sha: d1, start_sha: c3 } |
| author | user 2 |
| web_url | <public>/group/project/-/merge_requests/7 |
Commit d1: title Propose wording, committed 2026-07-31T12:00:00Z, parent [c3], unsigned.
GET .../merge_requests?state=opened&target_branch=mainreturns[!7].GET .../merge_requests/7returns !7.GET .../merge_requests/7/diffsreturns[{ old_path: "src/content/doc.md", new_path: "src/content/doc.md", diff: "@@ -3 +3 @@\n-Revised paragraph.\n+Proposed paragraph.\n" }].GET .../merge_requests/7/approvalsreturns{ approved_by: [], approvals_left: 0 }.GET .../merge_requests/7/discussionsreturns the discussions created on it (initially[]).GET .../merge_requests/7/commitsreturns[d1].
Issues and discussions (stateful)
Initially no issues. The service creates the document issue on the first
published comment with title [Document Review] src/content/doc.md.
GET .../issues?state=opened&search=...&in=titlereturns matching open issues.POST .../issuescreates{ iid: <next from 1>, id: <same>, title, description, state: "opened", web_url: "<public>/group/project/-/issues/<iid>" }.GET .../issues/:iid/discussionsreturns that issue’s discussions.POST .../issues/:iid/discussionswith{ body }creates a discussion{ id: "<32 hex>", individual_note: false, notes: [note] }where the note is{ id: <next from 1>, body, author: user 2, created_at, updated_at, system: false, resolvable: false }. The author is always user 2 (the mock has one user).GET .../issues/:iid/discussions/:idreturns one discussion.POST .../issues/:iid/discussions/:id/noteswith{ body }appends a note.PUT .../issues/:iid/notes/:noteIdwith{ body }updates the note body.DELETE .../issues/:iid/notes/:noteIdremoves the note; removing the first note removes the discussion.
Merge-request discussions follow the same shapes under .../merge_requests/7/...
and support PUT .../merge_requests/7/discussions/:id with { resolved }.
Write endpoints require Authorization: Bearer mock-user-token; any other
authorization returns 401. Read endpoints accept PRIVATE-TOKEN: mock-read-token
or the bearer token.
OAuth
GET /oauth/authorize?client_id=…&redirect_uri=R&state=S&…responds302 Location: R?code=mock-code&state=S. No page, no consent.POST /oauth/tokenwithcode=mock-coderesponds{ access_token: "mock-user-token", token_type: "Bearer", expires_in: 7200, scope: "api" }. Any other code responds 400.POST /oauth/revokeresponds200 {}.
Service reachability
GET /api/v4/versionresponds{ version: "18.3.0", revision: "mock" }with no authorization required.
Control endpoints
Not part of GitLab. Used by tests only.
| Endpoint | Effect |
|---|---|
POST /__mock/reset | discard all created issues, discussions, notes; set down=false |
POST /__mock/down | every /api/* and /oauth/* request responds 503 { message: "down" } until /__mock/up |
POST /__mock/up | clear down |
GET /__mock/state | { down: boolean, issues: [...], discussions: { "<kind>/<iid>": [...] } } |