fix: 修复 CLI repo 与状态别名解析

This commit is contained in:
Codex
2026-05-24 02:15:28 +00:00
parent 2257c986eb
commit 2a386a17da
7 changed files with 307 additions and 27 deletions
+33
View File
@@ -149,6 +149,10 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
sendJson(res, 200, [pullRequest]);
return;
}
if (req.method === "GET" && req.url === "/repos/pikasTech/HWLAB/pulls?state=open&per_page=4") {
sendJson(res, 200, [shorthandPullRequest]);
return;
}
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/pulls?state=closed&per_page=4") {
sendJson(res, 200, [mergedPullRequest]);
return;
@@ -330,6 +334,20 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
assertCondition(!("body" in listOpenPrs[0]), "pr list --json should keep progressive disclosure", listOpenPrs[0]);
assertCondition(mock.requests.some((request) => request.method === "GET" && request.url === "/repos/pikasTech/unidesk/pulls?state=open&per_page=4"), "pr list --state open should query REST state=open", mock.requests);
const positionalRepoList = await runCli(["gh", "pr", "list", "pikasTech/HWLAB", "--state", "open", "--limit", "4", "--json", "number,title,state,url"], env);
assertCondition(positionalRepoList.status === 0, "pr list positional owner/repo should succeed", positionalRepoList.json ?? { stdout: positionalRepoList.stdout });
const positionalRepoListData = dataOf(positionalRepoList.json ?? {});
assertCondition(positionalRepoListData.repo === "pikasTech/HWLAB", "pr list positional repo should become the actual request repo", positionalRepoListData);
const positionalRepoPrs = positionalRepoListData.pullRequests as JsonRecord[];
assertCondition(Array.isArray(positionalRepoPrs) && positionalRepoPrs[0]?.number === 7, "pr list positional repo should return HWLAB fixture PR", positionalRepoListData);
assertCondition(mock.requests.some((request) => request.method === "GET" && request.url === "/repos/pikasTech/HWLAB/pulls?state=open&per_page=4"), "pr list positional repo should query derived repo REST path", mock.requests);
const positionalRepoConflict = await runCli(["gh", "pr", "list", "pikasTech/HWLAB", "--repo", "pikasTech/unidesk", "--state", "open"], env);
assertCondition(positionalRepoConflict.status !== 0, "pr list conflicting positional repo and --repo should fail", positionalRepoConflict.json ?? { stdout: positionalRepoConflict.stdout });
const positionalRepoConflictData = failedDataOf(positionalRepoConflict.json ?? {});
assertCondition(positionalRepoConflictData.degradedReason === "validation-failed", "pr list repo conflict should be validation-failed", positionalRepoConflictData);
assertCondition(String((positionalRepoConflictData.details as JsonRecord)?.message ?? "").includes("positional repo pikasTech/HWLAB"), "pr list repo conflict should name positional repo", positionalRepoConflictData);
const listClosed = await runCli(["gh", "pr", "list", "--repo", "pikasTech/unidesk", "--state", "closed", "--limit", "4", "--json", "number,state,url"], env);
assertCondition(listClosed.status === 0, "pr list should support --state closed", listClosed.json ?? { stdout: listClosed.stdout });
const listClosedData = dataOf(listClosed.json ?? {});
@@ -359,6 +377,19 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
const selected = readData.json as JsonRecord;
assertCondition(selected.body === "PR body" && selected.title === "contract PR", "pr read --json should select fields", readData);
const readNumberAlias = await runCli(["gh", "pr", "read", "--repo", "pikasTech/HWLAB", "--number", "7", "--json", "body,title,state,head,base"], env);
assertCondition(readNumberAlias.status === 0, "pr read should accept --number alias", readNumberAlias.json ?? { stdout: readNumberAlias.stdout });
const readNumberAliasData = dataOf(readNumberAlias.json ?? {});
assertCondition(readNumberAliasData.repo === "pikasTech/HWLAB", "pr read --number should preserve explicit repo", readNumberAliasData);
const readNumberAliasPr = readNumberAliasData.pullRequest as JsonRecord;
assertCondition(readNumberAliasPr.number === 7 && readNumberAliasPr.url === "https://github.com/pikasTech/HWLAB/pull/7", "pr read --number should read the requested PR", readNumberAliasData);
assertCondition(mock.requests.some((request) => request.method === "GET" && request.url === "/repos/pikasTech/HWLAB/pulls/7"), "pr read --number should call explicit repo REST path", mock.requests);
const numberAliasUnsupported = await runCli(["gh", "pr", "list", "--repo", "pikasTech/unidesk", "--number", "7"], env);
assertCondition(numberAliasUnsupported.status !== 0, "--number should not be silently ignored outside pr read/view", numberAliasUnsupported.json ?? { stdout: numberAliasUnsupported.stdout });
const numberAliasUnsupportedData = failedDataOf(numberAliasUnsupported.json ?? {});
assertCondition(numberAliasUnsupportedData.degradedReason === "validation-failed", "unsupported --number should be validation-failed", numberAliasUnsupportedData);
const openLifecycle = await runCli(["gh", "pr", "read", "42", "--repo", "pikasTech/unidesk", "--json", "state,stateDetail,closed,closedAt,merged,mergedAt,mergeCommit,headRefName,baseRefName"], env);
assertCondition(openLifecycle.status === 0, "open pr lifecycle fields should succeed through REST", openLifecycle.json ?? { stdout: openLifecycle.stdout });
const openLifecycleData = dataOf(openLifecycle.json ?? {});
@@ -674,6 +705,8 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
checks: [
"gh help lists pr create/comment",
"pr list/read/view work through REST with token and no gh binary dependency",
"pr list positional owner/repo targets the requested repo and conflicting --repo fails",
"pr read supports --number alias without silently ignoring it elsewhere",
"pr read/view accept owner/repo#number shorthand and reject conflicting --repo",
"pr read/view --raw is explicit full disclosure",
"pr list rejects closeout fields and points to pr view",