feat(FN-4032): add tracking retry with actionable create issue error in tas
The merge adds a GitHub integration retry lifecycle to the task tracking system, including an actionable retry mechanism for failed issue creation, fixes to tracking and auth retry flow, and corresponding tests in the dashboard and engine packages. Documentation is updated in `docs/architecture.md` Fusion-Task-Id: FN-4032
This commit is contained in:
@@ -85,6 +85,24 @@ describe("resolveGithubTrackingAuth", () => {
|
||||
expect(result).toEqual({ ok: true, auth: { mode: "gh-cli" } });
|
||||
});
|
||||
|
||||
it("uses global githubAuthMode/githubAuthToken fallback when present", () => {
|
||||
const result = resolveGithubTrackingAuth({
|
||||
projectSettings: {},
|
||||
globalSettings: { githubAuthMode: "token", githubAuthToken: "global-token" },
|
||||
env: {},
|
||||
});
|
||||
expect(result).toEqual({ ok: true, auth: { mode: "token", token: "global-token" } });
|
||||
});
|
||||
|
||||
it("uses defensive projectGithubAuthMode/projectGithubAuthToken fallback keys", () => {
|
||||
const result = resolveGithubTrackingAuth({
|
||||
projectSettings: {},
|
||||
globalSettings: { projectGithubAuthMode: "token", projectGithubAuthToken: "project-global-token" },
|
||||
env: {},
|
||||
});
|
||||
expect(result).toEqual({ ok: true, auth: { mode: "token", token: "project-global-token" } });
|
||||
});
|
||||
|
||||
it("returns invalid_mode for unsupported mode values", () => {
|
||||
const result = resolveGithubTrackingAuth({
|
||||
projectSettings: { githubAuthMode: "weird" as "gh-cli" },
|
||||
|
||||
@@ -211,4 +211,16 @@ describe("maybeCreateTrackingIssue", () => {
|
||||
}));
|
||||
expect(createIssueMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes global settings through to auth resolution", async () => {
|
||||
const globalSettings = { githubTrackingDefaultRepo: "o/r", githubAuthMode: "token" } as any;
|
||||
await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: true } }), {
|
||||
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn() } as any,
|
||||
projectSettings: {},
|
||||
globalSettings,
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
});
|
||||
|
||||
expect(resolveAuthMock).toHaveBeenCalledWith(expect.objectContaining({ globalSettings }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1871,6 +1871,42 @@ describe("PATCH /tasks/:id", () => {
|
||||
createIssueSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("retries tracking issue creation on non-tracking patch when task is enabled but unlinked", async () => {
|
||||
const createIssueSpy = vi.spyOn(GitHubClient.prototype, "createIssue").mockResolvedValue({
|
||||
owner: "runfusion",
|
||||
repo: "fusion",
|
||||
number: 101,
|
||||
htmlUrl: "https://github.com/runfusion/fusion/issues/101",
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ githubAuthMode: "token", githubAuthToken: "tok" });
|
||||
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...FAKE_TASK_DETAIL,
|
||||
id: "KB-001",
|
||||
title: "Retitled",
|
||||
githubTracking: { enabled: true, repoOverride: "runfusion/fusion" },
|
||||
});
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...FAKE_TASK_DETAIL,
|
||||
id: "KB-001",
|
||||
title: "Retitled",
|
||||
githubTracking: {
|
||||
enabled: true,
|
||||
repoOverride: "runfusion/fusion",
|
||||
issue: { owner: "runfusion", repo: "fusion", number: 101, url: "https://github.com/runfusion/fusion/issues/101", createdAt: "2026-01-01T00:00:00.000Z" },
|
||||
},
|
||||
});
|
||||
|
||||
const res = await REQUEST(buildApp(), "PATCH", "/api/tasks/KB-001", JSON.stringify({ title: "Retitled" }), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(createIssueSpy).toHaveBeenCalledWith(expect.objectContaining({ owner: "runfusion", repo: "fusion" }));
|
||||
expect(store.linkGithubIssue).toHaveBeenCalledWith("KB-001", expect.objectContaining({ number: 101 }));
|
||||
createIssueSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("returns 400 for invalid githubTracking repo override format", async () => {
|
||||
const res = await REQUEST(buildApp(), "PATCH", "/api/tasks/KB-001", JSON.stringify({
|
||||
githubTracking: {
|
||||
|
||||
Reference in New Issue
Block a user