fix: expose issue lifecycle json fields
This commit is contained in:
@@ -98,6 +98,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
user: { login: "tester" },
|
||||
created_at: "2026-05-20T00:00:00Z",
|
||||
updated_at: "2026-05-20T01:00:00Z",
|
||||
closed_at: null,
|
||||
};
|
||||
const shorthandIssue = {
|
||||
id: 7000,
|
||||
@@ -110,6 +111,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
user: { login: "tester" },
|
||||
created_at: "2026-05-20T02:00:00Z",
|
||||
updated_at: shorthandIssueUpdatedAt,
|
||||
closed_at: null,
|
||||
};
|
||||
const boardIssueBodyInitial = [
|
||||
"# Code Queue",
|
||||
@@ -254,6 +256,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
labels: [{ name: "cli", color: "1d76db", description: "CLI work" }],
|
||||
created_at: "2026-05-20T02:00:00Z",
|
||||
updated_at: "2026-05-20T03:00:00Z",
|
||||
closed_at: null,
|
||||
},
|
||||
{
|
||||
id: 2002,
|
||||
@@ -267,6 +270,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
labels: [],
|
||||
created_at: "2026-05-20T02:05:00Z",
|
||||
updated_at: "2026-05-20T03:05:00Z",
|
||||
closed_at: null,
|
||||
},
|
||||
{
|
||||
id: 3001,
|
||||
@@ -296,6 +300,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
labels: [],
|
||||
created_at: "2026-05-20T02:30:00Z",
|
||||
updated_at: "2026-05-20T03:30:00Z",
|
||||
closed_at: null,
|
||||
},
|
||||
];
|
||||
const scanIssues = [
|
||||
@@ -413,6 +418,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
labels: [],
|
||||
created_at: "2026-05-18T02:00:00Z",
|
||||
updated_at: "2026-05-20T03:00:00Z",
|
||||
closed_at: "2026-05-20T03:15:00Z",
|
||||
},
|
||||
{
|
||||
id: 2040,
|
||||
@@ -426,6 +432,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
labels: [],
|
||||
created_at: "2026-05-19T02:00:00Z",
|
||||
updated_at: "2026-05-20T03:00:00Z",
|
||||
closed_at: "2026-05-20T03:20:00Z",
|
||||
},
|
||||
{
|
||||
id: 2041,
|
||||
@@ -439,6 +446,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
labels: [],
|
||||
created_at: "2026-05-19T02:05:00Z",
|
||||
updated_at: "2026-05-20T03:05:00Z",
|
||||
closed_at: "2026-05-20T03:25:00Z",
|
||||
},
|
||||
];
|
||||
const comments = [
|
||||
@@ -663,6 +671,11 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
|
||||
const mock = await startMockGitHub();
|
||||
const tmp = mkdtempSync(join(tmpdir(), "unidesk-gh-issue-guard-"));
|
||||
const startedAt = Date.now();
|
||||
const heartbeat = setInterval(() => {
|
||||
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1000);
|
||||
process.stderr.write(`[gh-issue-contract] elapsed=${elapsedSeconds}s requests=${mock.requests.length}\n`);
|
||||
}, 10_000);
|
||||
const env = {
|
||||
GH_TOKEN: "contract-token-should-not-print",
|
||||
UNIDESK_GITHUB_API_URL: mock.baseUrl,
|
||||
@@ -680,6 +693,18 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(listOpenIssues[0]?.title === "master:补齐 UniDesk CLI gh issue list 与 PR 驱动最小闭环前置能力", "issue list should expose title field", listOpenData);
|
||||
assertCondition(!("labels" in listOpenIssues[0]), "issue list --json should select only requested fields", listOpenIssues[0]);
|
||||
|
||||
const listOpenLifecycle = await runCli(["gh", "issue", "list", "--repo", "pikasTech/unidesk", "--state", "open", "--limit", "2", "--json", "number,state,closed,closedAt"], env);
|
||||
assertCondition(listOpenLifecycle.status === 0, "issue list lifecycle fields should succeed", listOpenLifecycle.json ?? { stdout: listOpenLifecycle.stdout });
|
||||
const listOpenLifecycleData = dataOf(listOpenLifecycle.json ?? {});
|
||||
const listOpenLifecycleIssues = listOpenLifecycleData.issues as JsonRecord[];
|
||||
assertCondition(listOpenLifecycleIssues[0]?.closed === false && listOpenLifecycleIssues[0]?.closedAt === null, "open issue list rows should expose closed=false and closedAt=null", listOpenLifecycleData);
|
||||
|
||||
const listClosedLifecycle = await runCli(["gh", "issue", "list", "--repo", "pikasTech/unidesk", "--state", "closed", "--limit", "2", "--json", "number,state,closed,closedAt"], env);
|
||||
assertCondition(listClosedLifecycle.status === 0, "closed issue list lifecycle fields should succeed", listClosedLifecycle.json ?? { stdout: listClosedLifecycle.stdout });
|
||||
const listClosedLifecycleData = dataOf(listClosedLifecycle.json ?? {});
|
||||
const listClosedLifecycleIssues = listClosedLifecycleData.issues as JsonRecord[];
|
||||
assertCondition(listClosedLifecycleIssues[0]?.state === "closed" && listClosedLifecycleIssues[0]?.closed === true && listClosedLifecycleIssues[0]?.closedAt === "2026-05-20T03:15:00Z", "closed issue list rows should expose closed=true and closedAt", listClosedLifecycleData);
|
||||
|
||||
const acceptanceList = await runCli(["gh", "issue", "list", "--repo", "pikasTech/unidesk", "--state", "open", "--limit", "5", "--json", "number,title,state,url"], env);
|
||||
assertCondition(acceptanceList.status === 0, "acceptance issue list command should succeed under mock GitHub", acceptanceList.json ?? { stdout: acceptanceList.stdout });
|
||||
const acceptanceListData = dataOf(acceptanceList.json ?? {});
|
||||
@@ -1256,18 +1281,19 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(shorthandConflictData.degradedReason === "validation-failed", "conflicting --repo should be validation-failed", shorthandConflictData);
|
||||
assertCondition(String(shorthandConflictData.message ?? "").includes("resolves to repo pikasTech/HWLAB"), "conflict message should name the derived repo", shorthandConflictData);
|
||||
const issueConflictCommands = shorthandConflictData.supportedCommands as string[];
|
||||
assertCondition(Array.isArray(issueConflictCommands) && issueConflictCommands.some((command) => command === "bun scripts/cli.ts gh issue read 7 --repo pikasTech/HWLAB --json body,title,state,comments"), "conflict should include the exact supported issue read command", shorthandConflictData);
|
||||
assertCondition(Array.isArray(issueConflictCommands) && issueConflictCommands.some((command) => command === "bun scripts/cli.ts gh issue read 7 --repo pikasTech/HWLAB --json body,title,state,closed,closedAt,comments,number,url,author,createdAt,updatedAt"), "conflict should include the exact supported issue read command", shorthandConflictData);
|
||||
|
||||
const rawIssueList = await runCli(["gh", "issue", "list", "--raw"], env);
|
||||
assertCondition(rawIssueList.status === 0, "issue list --raw should be a supported explicit list disclosure path", rawIssueList.json ?? { stdout: rawIssueList.stdout });
|
||||
const rawIssueListData = dataOf(rawIssueList.json ?? {});
|
||||
assertCondition(rawIssueListData.command === "issue list" && rawIssueListData.rawCount === 3, "issue list --raw should keep compact list semantics with raw pagination metadata", rawIssueListData);
|
||||
|
||||
const readFields = await runCli(["gh", "issue", "read", "20", "--repo", "pikasTech/unidesk", "--json", "body,title,state,comments"], env);
|
||||
const readFields = await runCli(["gh", "issue", "read", "20", "--repo", "pikasTech/unidesk", "--json", "body,title,state,closed,closedAt,comments"], env);
|
||||
assertCondition(readFields.status === 0, "common --json field selection should succeed", readFields.json ?? { stdout: readFields.stdout });
|
||||
const readFieldsData = dataOf(readFields.json ?? {});
|
||||
const fieldsJson = readFieldsData.json as JsonRecord;
|
||||
assertCondition(fieldsJson.title === "长期总看板", "selected json title should be exposed", fieldsJson);
|
||||
assertCondition(fieldsJson.closed === false && fieldsJson.closedAt === null, "open issue read should expose lifecycle fields", fieldsJson);
|
||||
assertCondition(Array.isArray(fieldsJson.comments) && fieldsJson.comments.length === 1, "selected json comments should be exposed", fieldsJson);
|
||||
|
||||
const unsupported = await runCli(["gh", "issue", "read", "20", "--repo", "pikasTech/unidesk", "--json", "body,unknown"], env);
|
||||
@@ -1760,7 +1786,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
"issue create dry-run parses repeated/comma labels, supports stdin, rejects inline --body, and exposes request plan",
|
||||
"issue create sends labels through REST and preserves GitHub validation errors for missing labels",
|
||||
"issue list unsupported fields and states fail structurally",
|
||||
"issue read supports body,title,state,comments selection",
|
||||
"issue read supports body,title,state,closed,closedAt,comments selection",
|
||||
"unknown/full disclosure option guidance remains actionable",
|
||||
"unsupported --json fields fail structurally",
|
||||
"issue edit --body-file rejects literal null",
|
||||
@@ -1783,6 +1809,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
],
|
||||
};
|
||||
} finally {
|
||||
clearInterval(heartbeat);
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
await mock.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user