Skip to content

Commit 206f804

Browse files
committed
test(mcp): cover init-client's host and mode guards (#9526)
The five branch partials codecov/patch was holding the PR on, all in init-client's argument handling: no host at all, an unknown host, the --client spelling as well as --print, a host/mode pair the grid refuses, and the per-host remote caveat's absent side. Each asserts the message rather than merely the throw -- "unsupported client" without the list leaves a reader guessing at a five-value set they cannot see from outside the process.
1 parent 31a38b3 commit 206f804

2 files changed

Lines changed: 153 additions & 3 deletions

File tree

apps/loopover-ui/public/openapi.json

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17365,6 +17365,9 @@
1736517365
}
1736617366
}
1736717367
},
17368+
"400": {
17369+
"description": "Malformed installation id"
17370+
},
1736817371
"404": {
1736917372
"description": "Installation health not found"
1737017373
}
@@ -17407,6 +17410,9 @@
1740717410
}
1740817411
}
1740917412
},
17413+
"400": {
17414+
"description": "Malformed installation id"
17415+
},
1741017416
"404": {
1741117417
"description": "Installation health not found"
1741217418
}
@@ -17449,6 +17455,9 @@
1744917455
}
1745017456
}
1745117457
},
17458+
"400": {
17459+
"description": "Malformed installation id"
17460+
},
1745217461
"404": {
1745317462
"description": "Installation not found"
1745417463
}
@@ -20793,7 +20802,7 @@
2079320802
],
2079420803
"responses": {
2079520804
"200": {
20796-
"description": "{ anchors: [{ id, seq, rowHash, keyId, backend, backendRef, status, error, createdAt }], nextBefore } — a failed attempt is returned identically to a successful one, never filtered out or reshaped"
20805+
"description": "{ anchors: [{ id, seq, rowHash, keyId, backend, backendRef, status, error, createdAt }], nextBefore, status } — a failed attempt is returned identically to a successful one, never filtered out or reshaped. The top-level `status` (anchored | empty_ledger | unconfigured | pending) says why the list looks as it does, so an empty list cannot be mistaken for a healthy one; it is omitted when a backend/before filter is applied, where empty just means none matched"
2079720806
}
2079820807
},
2079920808
"operationId": "listPublicDecisionLedgerAnchors",
@@ -20892,11 +20901,25 @@
2089220901
},
2089320902
"400": {
2089420903
"description": "Malformed JSON or invalid payload shape"
20904+
},
20905+
"401": {
20906+
"description": "Missing or invalid ingest credential"
20907+
},
20908+
"403": {
20909+
"description": "Instance not authenticated"
20910+
},
20911+
"413": {
20912+
"description": "Batch exceeds the 1 MiB (MAX_ORB_INGEST_BODY_BYTES) body ceiling"
2089520913
}
2089620914
},
2089720915
"operationId": "postOrbIngest",
2089820916
"tags": [
2089920917
"ORB"
20918+
],
20919+
"security": [
20920+
{
20921+
"OrbBearer": []
20922+
}
2090020923
]
2090120924
}
2090220925
},
@@ -24011,6 +24034,9 @@
2401124034
"200": {
2401224035
"description": "Installation health"
2401324036
},
24037+
"400": {
24038+
"description": "Malformed installation id"
24039+
},
2401424040
"401": {
2401524041
"description": "Not signed in"
2401624042
},
@@ -24052,6 +24078,9 @@
2405224078
"200": {
2405324079
"description": "Repair plan"
2405424080
},
24081+
"400": {
24082+
"description": "Malformed installation id"
24083+
},
2405524084
"401": {
2405624085
"description": "Not signed in"
2405724086
},
@@ -24093,6 +24122,9 @@
2409324122
"200": {
2409424123
"description": "Repair plan recomputed"
2409524124
},
24125+
"400": {
24126+
"description": "Malformed installation id"
24127+
},
2409624128
"401": {
2409724129
"description": "Not signed in"
2409824130
},
@@ -24136,7 +24168,7 @@
2413624168
"description": "Settings applied"
2413724169
},
2413824170
"400": {
24139-
"description": "Malformed settings"
24171+
"description": "Malformed installation id or settings"
2414024172
},
2414124173
"401": {
2414224174
"description": "Not signed in"
@@ -25897,6 +25929,12 @@
2589725929
"responses": {
2589825930
"200": {
2589925931
"description": "Badge payload"
25932+
},
25933+
"404": {
25934+
"description": "The repo has no public badge (unknown, private, uninstalled, or badgeEnabled off)"
25935+
},
25936+
"503": {
25937+
"description": "The badge data could not be loaded (a transient loader failure, short-cached)"
2590025938
}
2590125939
},
2590225940
"security": []
@@ -25930,6 +25968,12 @@
2593025968
"responses": {
2593125969
"200": {
2593225970
"description": "SVG badge"
25971+
},
25972+
"404": {
25973+
"description": "The repo has no public badge (unknown, private, uninstalled, or badgeEnabled off)"
25974+
},
25975+
"503": {
25976+
"description": "The badge data could not be loaded (a transient loader failure, short-cached)"
2593325977
}
2593425978
},
2593525979
"security": []
@@ -26045,14 +26089,17 @@
2604526089
}
2604626090
],
2604726091
"responses": {
26048-
"202": {
26092+
"200": {
2604926093
"description": "Batch accepted"
2605026094
},
2605126095
"400": {
2605226096
"description": "Malformed batch"
2605326097
},
2605426098
"401": {
2605526099
"description": "Missing or invalid ingest credential"
26100+
},
26101+
"413": {
26102+
"description": "Batch exceeds the 1 MiB (MAX_ORB_INGEST_BODY_BYTES) body ceiling"
2605626103
}
2605726104
}
2605826105
}
@@ -26703,6 +26750,82 @@
2670326750
}
2670426751
}
2670526752
}
26753+
},
26754+
"/v1/public/repos/{owner}/{repo}/proof": {
26755+
"get": {
26756+
"operationId": "getPublicRepoProof",
26757+
"tags": [
26758+
"Public"
26759+
],
26760+
"summary": "Public proof summary for one repo — ledger status, anchor, calibration with coverage and interval, sample records",
26761+
"parameters": [
26762+
{
26763+
"schema": {
26764+
"type": "string"
26765+
},
26766+
"required": true,
26767+
"name": "owner",
26768+
"in": "path"
26769+
},
26770+
{
26771+
"schema": {
26772+
"type": "string"
26773+
},
26774+
"required": true,
26775+
"name": "repo",
26776+
"in": "path"
26777+
}
26778+
],
26779+
"responses": {
26780+
"200": {
26781+
"description": "ProofSummary. Any accuracy figure carries its coverage AND a Wilson confidence interval; below the sample floor it is an explicit `insufficient_data` state, never a bare percentage. Carries the verification-boundary statement in the payload"
26782+
},
26783+
"404": {
26784+
"description": "The proof page is disabled fleet-wide, or this repo has opted out"
26785+
},
26786+
"503": {
26787+
"description": "Composition failed — no partial or fabricated summary is served"
26788+
}
26789+
}
26790+
}
26791+
},
26792+
"/v1/public/repos/{owner}/{repo}/proof-badge.svg": {
26793+
"get": {
26794+
"operationId": "getPublicRepoProofBadge",
26795+
"tags": [
26796+
"Public"
26797+
],
26798+
"summary": "README badge for the proof page — reports the ledger's state, never a bare accuracy percentage",
26799+
"parameters": [
26800+
{
26801+
"schema": {
26802+
"type": "string"
26803+
},
26804+
"required": true,
26805+
"name": "owner",
26806+
"in": "path"
26807+
},
26808+
{
26809+
"schema": {
26810+
"type": "string"
26811+
},
26812+
"required": true,
26813+
"name": "repo",
26814+
"in": "path"
26815+
}
26816+
],
26817+
"responses": {
26818+
"200": {
26819+
"description": "SVG badge"
26820+
},
26821+
"404": {
26822+
"description": "Disabled or opted out — still an SVG (a neutral 'unavailable' badge), so a README never shows a broken image"
26823+
},
26824+
"503": {
26825+
"description": "Same neutral SVG on an internal error"
26826+
}
26827+
}
26828+
}
2670626829
}
2670726830
},
2670826831
"servers": [

test/unit/mcp-cli-basics.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,33 @@ describe("loopover-mcp CLI — basics", () => {
125125
await expect(runInProcess(["init-client", "--print", "claude", "--mode", "carrier-pigeon"])).rejects.toThrow(/Unsupported mode.*stdio, remote, miner/);
126126
});
127127

128+
it("names the hosts it accepts when told nothing, or something it does not know (#9526)", async () => {
129+
// The error has to enumerate: "unsupported client" without the list leaves a reader guessing at a
130+
// five-value set they cannot see from the outside.
131+
await expect(runInProcess(["init-client"])).rejects.toThrow(/Pass --print with one of: codex, claude, cursor, mcp, vscode/);
132+
await expect(runInProcess(["init-client", "--print", "emacs"])).rejects.toThrow(/Unsupported client: emacs.*codex, claude, cursor, mcp, vscode/);
133+
});
134+
135+
it("accepts --client as well as --print, since both spellings reached this command (#9526)", async () => {
136+
const viaClient = JSON.parse(await runInProcess(["init-client", "--client", "cursor", "--json"])) as { client: string; snippet: string };
137+
expect(viaClient.client).toBe("cursor");
138+
expect(viaClient.snippet).toBe(clientConfigSnippet("cursor", "stdio"));
139+
});
140+
141+
it("refuses a host/mode pair it cannot vouch for, naming both (#9526)", async () => {
142+
// The generic `mcpServers` bucket is an unnamed host; guessing its remote dialect would print config
143+
// that fails on paste, and the stdio gateway already serves it the remote tools.
144+
await expect(runInProcess(["init-client", "--print", "mcp", "--mode", "remote"])).rejects.toThrow(/cannot connect over the Remote streamable-http mode/);
145+
});
146+
147+
it("carries the host's own remote caveat when it has one, and nothing when it does not (#9526)", async () => {
148+
const codex = JSON.parse(await runInProcess(["init-client", "--print", "codex", "--mode", "remote", "--json"])) as { notes: string[] };
149+
expect(codex.notes.join("\n")).toContain("experimental_use_rmcp_client");
150+
151+
const cursor = JSON.parse(await runInProcess(["init-client", "--print", "cursor", "--mode", "remote", "--json"])) as { notes: string[] };
152+
expect(cursor.notes.join("\n")).not.toContain("experimental_use_rmcp_client");
153+
});
154+
128155
it("prints human-approved agent profile instructions for supported MCP clients", async () => {
129156
const payload = JSON.parse(await runInProcess(["init-client", "--print", "codex", "--agent-profile", "miner-planner", "--json"])) as {
130157
agentProfile: {

0 commit comments

Comments
 (0)