feat(FN-4116): fix github tracking issue creation

Fixes GitHub CLI issue creation by repairing the `gh issue create` call in the dashboard and adds logging for tracking failures, with tests covering both the core GitHub module and the tracking layer.

Fusion-Task-Id: FN-4116
This commit is contained in:
Fusion
2026-05-12 08:17:56 -07:00
committed by gsxdsm
parent 34da8cffc2
commit 8f2b370b9d
5 changed files with 143 additions and 11 deletions

View File

@@ -66,16 +66,9 @@ describe("GitHubClient", () => {
});
describe("createIssue", () => {
it("uses gh path when authenticated", async () => {
mockRunGhJsonAsync.mockResolvedValue({ url: "https://github.com/o/r/issues/8", number: 8, createdAt: "2026-01-02T00:00:00Z" } as any);
const issue = await client.createIssue({ owner: "o", repo: "r", title: "t", body: "b", labels: ["bug"] });
expect(mockRunGhJsonAsync).toHaveBeenCalled();
expect(issue.number).toBe(8);
});
it("falls back to API when gh path fails and token is configured", async () => {
const clientWithToken = new GitHubClient("ghp_token");
mockRunGhJsonAsync.mockRejectedValue(new Error("gh failed"));
mockRunGhAsync.mockRejectedValue(new Error("gh failed"));
const fetchSpy = vi.spyOn(global, "fetch" as any).mockResolvedValue({
ok: true,
status: 201,
@@ -84,7 +77,8 @@ describe("GitHubClient", () => {
const issue = await clientWithToken.createIssue({ owner: "o", repo: "r", title: "t", body: "b" });
expect(issue).toEqual({ owner: "o", repo: "r", number: 7, htmlUrl: "https://github.com/o/r/issues/7", createdAt: "2026-01-01T00:00:00Z" });
expect(mockRunGhJsonAsync).toHaveBeenCalled();
expect(mockRunGhAsync).toHaveBeenCalled();
expect(mockRunGhJsonAsync).not.toHaveBeenCalled();
fetchSpy.mockRestore();
});
@@ -122,6 +116,82 @@ describe("GitHubClient", () => {
});
});
describe("GitHubClient.createIssue (gh-cli mode)", () => {
it("creates an issue from gh stdout and follow-up view metadata", async () => {
mockRunGhAsync.mockResolvedValue("https://github.com/acme/repo/issues/42\n");
mockRunGhJsonAsync.mockResolvedValue({
number: 42,
url: "https://github.com/acme/repo/issues/42",
createdAt: "2025-01-02T03:04:05Z",
} as any);
const ghClient = new GitHubClient({ forceMode: "gh-cli" });
const issue = await ghClient.createIssue({ owner: "acme", repo: "repo", title: "T", body: "B" });
expect(issue).toEqual({
owner: "acme",
repo: "repo",
number: 42,
htmlUrl: "https://github.com/acme/repo/issues/42",
createdAt: "2025-01-02T03:04:05Z",
});
expect(mockRunGhAsync).toHaveBeenCalledWith([
"issue",
"create",
"--repo",
"acme/repo",
"--title",
"T",
"--body",
"B",
]);
expect(mockRunGhAsync).toHaveBeenCalledWith(expect.not.arrayContaining(["--json"]));
expect(mockRunGhJsonAsync).toHaveBeenCalledWith([
"issue",
"view",
"https://github.com/acme/repo/issues/42",
"--json",
"number,url,createdAt",
]);
});
it("passes labels without adding --json to issue create", async () => {
mockRunGhAsync.mockResolvedValue("https://github.com/acme/repo/issues/42\n");
mockRunGhJsonAsync.mockResolvedValue({
number: 42,
url: "https://github.com/acme/repo/issues/42",
createdAt: "2025-01-02T03:04:05Z",
} as any);
const ghClient = new GitHubClient({ forceMode: "gh-cli" });
await ghClient.createIssue({ owner: "acme", repo: "repo", title: "T", body: "B", labels: ["a", "b"] });
expect(mockRunGhAsync).toHaveBeenCalledWith([
"issue",
"create",
"--repo",
"acme/repo",
"--title",
"T",
"--body",
"B",
"--label",
"a,b",
]);
expect(mockRunGhAsync).toHaveBeenCalledWith(expect.not.arrayContaining(["--json"]));
});
it("rejects malformed gh stdout without calling issue view", async () => {
mockRunGhAsync.mockResolvedValue("oops");
const ghClient = new GitHubClient({ forceMode: "gh-cli" });
await expect(ghClient.createIssue({ owner: "acme", repo: "repo", title: "T", body: "B" })).rejects.toThrow(
"Failed to parse issue URL from gh output",
);
expect(mockRunGhJsonAsync).not.toHaveBeenCalled();
});
});
describe("createPr", () => {
const mockPrParams: CreatePrParams = {
owner: "test-owner",