feat(FN-3279): refresh review state on manual trigger and restore roadmap e
Adds review tab refresh UX, metadata, and API (FN-3279) alongside permanent-agent action gating coordination tools (FN-3724), and introduces two new workspace plugin packages: `fusion-plugin-even-cards` (board/task cards with auth and board-routes) and `fusion-plugin-even-realities-glasses` (cards, Fusion-Task-Id: FN-3279
This commit is contained in:
@@ -1012,8 +1012,58 @@ describe("GitHubClient", () => {
|
||||
const snapshot = await client.getPrReviewSnapshot("owner", "repo", 1);
|
||||
expect(snapshot.items).toHaveLength(2);
|
||||
expect(snapshot.summary?.reviewDecision).toBe("CHANGES_REQUESTED");
|
||||
expect(snapshot.prInfo.number).toBe(1);
|
||||
expect(snapshot.commentCount).toBe(1);
|
||||
expect(snapshot.summary?.reviewers[0]).toEqual(expect.objectContaining({ login: "octocat", state: "CHANGES_REQUESTED" }));
|
||||
});
|
||||
|
||||
it("falls back to API review details when gh fails and token is available", async () => {
|
||||
mockRunGhJsonAsync.mockImplementation(() => {
|
||||
throw new Error("gh down");
|
||||
});
|
||||
const clientWithToken = new GitHubClient("ghp_token");
|
||||
const mockFetch = vi.fn()
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
data: {
|
||||
repository: {
|
||||
pullRequest: {
|
||||
reviewDecision: "APPROVED",
|
||||
comments: { nodes: [{ id: "C_1", body: "lgtm", createdAt: "2024-01-01T00:00:00Z", updatedAt: "2024-01-01T00:00:01Z", url: "https://example.com/c1", author: { login: "bot" } }] },
|
||||
reviews: { nodes: [{ id: "R_1", state: "APPROVED", body: "good", submittedAt: "2024-01-01T00:00:00Z", url: "https://example.com/r1", author: { login: "reviewer" } }] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
data: {
|
||||
repository: {
|
||||
pullRequest: {
|
||||
number: 1,
|
||||
url: "https://github.com/owner/repo/pull/1",
|
||||
title: "PR",
|
||||
state: "OPEN",
|
||||
reviewDecision: "APPROVED",
|
||||
baseRefName: "main",
|
||||
headRefName: "fn/fn-1",
|
||||
comments: { totalCount: 1 },
|
||||
commits: { nodes: [{ commit: { statusCheckRollup: { contexts: { nodes: [] } } } }] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
global.fetch = mockFetch as any;
|
||||
|
||||
const snapshot = await clientWithToken.getPrReviewSnapshot("owner", "repo", 1);
|
||||
expect(snapshot.summary?.reviewDecision).toBe("APPROVED");
|
||||
expect(snapshot.items).toHaveLength(2);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
describe("mergePr", () => {
|
||||
|
||||
@@ -2137,6 +2137,16 @@ describe("POST /tasks/:id/review/refresh", () => {
|
||||
vi.spyOn(GitHubClient.prototype, "getPrReviewSnapshot").mockResolvedValue({
|
||||
decision: "CHANGES_REQUESTED",
|
||||
checks: [],
|
||||
prInfo: {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "PR",
|
||||
headBranch: "fusion/fn-1",
|
||||
baseBranch: "main",
|
||||
commentCount: 1,
|
||||
},
|
||||
commentCount: 1,
|
||||
summary: {
|
||||
reviewDecision: "CHANGES_REQUESTED",
|
||||
reviewers: [],
|
||||
@@ -2153,10 +2163,42 @@ describe("POST /tasks/:id/review/refresh", () => {
|
||||
expect(res.status).toBe(200);
|
||||
expect((store.updateTask as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
|
||||
"FN-001",
|
||||
expect.objectContaining({ reviewState: expect.objectContaining({ source: "pull-request" }) }),
|
||||
expect.objectContaining({
|
||||
reviewState: expect.objectContaining({
|
||||
source: "pull-request",
|
||||
refreshSource: "manual",
|
||||
refreshStatus: "ready",
|
||||
lastRefreshedAt: expect.any(String),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns scoped refresh error payload in PR mode when GitHub refresh fails", async () => {
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...FAKE_TASK_DETAIL,
|
||||
prInfo: {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "PR",
|
||||
headBranch: "fusion/fn-1",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
reviewState: { source: "pull-request", items: [], addressing: [] },
|
||||
});
|
||||
vi.spyOn(GitHubClient.prototype, "getPrReviewSnapshot").mockRejectedValue(new Error("GitHub outage"));
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/tasks/FN-001/review/refresh", JSON.stringify({}), {
|
||||
"content-type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.reviewState.refreshStatus).toBe("error");
|
||||
expect(res.body.reviewState.refreshError).toContain("GitHub outage");
|
||||
});
|
||||
|
||||
it("refreshes direct-mode review payload without PR", async () => {
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...FAKE_TASK_DETAIL,
|
||||
@@ -2167,13 +2209,34 @@ describe("POST /tasks/:id/review/refresh", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const getSnapshotSpy = vi.spyOn(GitHubClient.prototype, "getPrReviewSnapshot");
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/tasks/FN-001/review/refresh", JSON.stringify({}), {
|
||||
"content-type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect((store.updateTask as ReturnType<typeof vi.fn>)).toHaveBeenCalled();
|
||||
expect((store.updateTask as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith(
|
||||
"FN-001",
|
||||
expect.objectContaining({
|
||||
reviewState: expect.objectContaining({
|
||||
source: "reviewer-agent",
|
||||
refreshSource: "manual",
|
||||
refreshStatus: "ready",
|
||||
lastRefreshedAt: expect.any(String),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(res.body.reviewState.source).toBe("reviewer-agent");
|
||||
expect(getSnapshotSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns 404 when task is missing", async () => {
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockRejectedValue(Object.assign(new Error("not found"), { code: "ENOENT" }));
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/tasks/FN-404/review/refresh", JSON.stringify({}), {
|
||||
"content-type": "application/json",
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user