feat(FN-3870): wire GitHub tracking issue creation
Implements GitHub tracking issue creation (FN-3870) for the dashboard, wiring new routes for planning subtasks and task workflows, adding a dedicated `github-tracking` module with issue creation and auth fallback logic, and updating the main `github.ts` with enhanced capabilities — backed by compreh Fusion-Task-Id: FN-3870
This commit is contained in:
@@ -65,6 +65,63 @@ 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"));
|
||||
const fetchSpy = vi.spyOn(global, "fetch" as any).mockResolvedValue({
|
||||
ok: true,
|
||||
status: 201,
|
||||
json: async () => ({ number: 7, html_url: "https://github.com/o/r/issues/7", created_at: "2026-01-01T00:00:00Z" }),
|
||||
} as any);
|
||||
|
||||
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();
|
||||
fetchSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("uses API path when gh auth is unavailable and token is configured", async () => {
|
||||
mockIsGhAvailable.mockReturnValue(false);
|
||||
const clientWithToken = new GitHubClient("ghp_token");
|
||||
const fetchSpy = vi.spyOn(global, "fetch" as any).mockResolvedValue({
|
||||
ok: true,
|
||||
status: 201,
|
||||
json: async () => ({ number: 9, html_url: "https://github.com/o/r/issues/9", created_at: "2026-01-03T00:00:00Z" }),
|
||||
} as any);
|
||||
|
||||
const issue = await clientWithToken.createIssue({ owner: "o", repo: "r", title: "t", body: "b" });
|
||||
expect(issue.number).toBe(9);
|
||||
fetchSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("throws when gh auth unavailable and no token provided", async () => {
|
||||
mockIsGhAvailable.mockReturnValue(false);
|
||||
await expect(client.createIssue({ owner: "o", repo: "r", title: "t", body: "b" })).rejects.toThrow("GitHub CLI (gh) is not available");
|
||||
});
|
||||
|
||||
it("surfaces 422 API failures with cause", async () => {
|
||||
mockIsGhAvailable.mockReturnValue(false);
|
||||
const clientWithToken = new GitHubClient("ghp_token");
|
||||
vi.spyOn(global, "fetch" as any).mockResolvedValue({ ok: false, status: 422, statusText: "Unprocessable", json: async () => ({ message: "Validation Failed" }) } as any);
|
||||
await expect(clientWithToken.createIssue({ owner: "o", repo: "r", title: "t", body: "b" })).rejects.toThrow("Failed to create GitHub issue");
|
||||
});
|
||||
|
||||
it("surfaces 404 API failures", async () => {
|
||||
mockIsGhAvailable.mockReturnValue(false);
|
||||
const clientWithToken = new GitHubClient("ghp_token");
|
||||
vi.spyOn(global, "fetch" as any).mockResolvedValue({ ok: false, status: 404, statusText: "Not Found", json: async () => ({ message: "Not Found" }) } as any);
|
||||
await expect(clientWithToken.createIssue({ owner: "o", repo: "r", title: "t", body: "b" })).rejects.toThrow("Failed to create GitHub issue");
|
||||
});
|
||||
});
|
||||
|
||||
describe("createPr", () => {
|
||||
const mockPrParams: CreatePrParams = {
|
||||
owner: "test-owner",
|
||||
|
||||
Reference in New Issue
Block a user