test(FN-4777): complete Step 4 — add dedup tracking coverage
Fusion-Task-Id: FN-4777 Fusion-Task-Lineage: ed00f639-c928-4c14-be1f-2b43db6022c7
This commit is contained in:
committed by
gsxdsm
parent
f60dde01fe
commit
1ca873f64e
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildIssueSearchQueries,
|
||||
DEDUP_MATCH_THRESHOLD,
|
||||
extractFileScopePaths,
|
||||
extractSymptomKeywords,
|
||||
scoreCandidateIssue,
|
||||
} from "../github-tracking-dedup.js";
|
||||
|
||||
describe("extractFileScopePaths", () => {
|
||||
it("parses file scope block, dedupes, strips trailing globs, and caps results", () => {
|
||||
const prompt = `# Task\n\n- before\n\n## File Scope\n- \`packages/dashboard/src/routes/register-session-diff-routes.ts\`\n- packages/foo/*\n- packages/foo/*\n- packages/bar/**\n- packages/a\n- packages/b\n- packages/c\n- packages/d\n- packages/e\n- packages/f\n\n## Steps\n- after`;
|
||||
|
||||
expect(extractFileScopePaths({ description: "desc", prompt })).toEqual([
|
||||
"packages/dashboard/src/routes/register-session-diff-routes.ts",
|
||||
"packages/foo",
|
||||
"packages/bar",
|
||||
"packages/a",
|
||||
"packages/b",
|
||||
"packages/c",
|
||||
"packages/d",
|
||||
"packages/e",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns empty when file scope section is missing", () => {
|
||||
expect(extractFileScopePaths({ description: "no section" })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractSymptomKeywords", () => {
|
||||
it("extracts identifiers and error names, drops stopwords, respects max", () => {
|
||||
const keywords = extractSymptomKeywords({
|
||||
title: "Fix rebaseMergeTruncation and ParseIssueError",
|
||||
description: "Touches `registerSessionDiffRoutes` and `fusion` and `tokenizeIssueBody`",
|
||||
}, { max: 3 });
|
||||
|
||||
expect(keywords).toEqual(["registerSessionDiffRoutes", "tokenizeIssueBody", "ParseIssueError"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildIssueSearchQueries", () => {
|
||||
it("returns keyword query and quoted path queries (1-3 total)", () => {
|
||||
const queries = buildIssueSearchQueries(
|
||||
["packages/dashboard/src/routes/register-session-diff-routes.ts", "packages/dashboard/src/github.ts"],
|
||||
["rebaseMerge", "registerSessionDiffRoutes", "ParseIssueError"],
|
||||
);
|
||||
|
||||
expect(queries).toEqual([
|
||||
"rebaseMerge registerSessionDiffRoutes ParseIssueError",
|
||||
'"packages/dashboard/src/routes/register-session-diff-routes.ts"',
|
||||
'"packages/dashboard/src/github.ts"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("scoreCandidateIssue", () => {
|
||||
it("scores path + keyword matches and threshold gate is usable", () => {
|
||||
const scored = scoreCandidateIssue(
|
||||
{
|
||||
title: "Rebase merge truncation in registerSessionDiffRoutes",
|
||||
body: "File: packages/dashboard/src/routes/register-session-diff-routes.ts throws ParseIssueError",
|
||||
},
|
||||
["packages/dashboard/src/routes/register-session-diff-routes.ts"],
|
||||
["registerSessionDiffRoutes", "ParseIssueError", "nonMatchKeyword"],
|
||||
);
|
||||
|
||||
expect(scored.matchedPaths).toEqual(["packages/dashboard/src/routes/register-session-diff-routes.ts"]);
|
||||
expect(scored.matchedKeywords).toEqual(["registerSessionDiffRoutes", "ParseIssueError"]);
|
||||
expect(scored.score).toBe(4);
|
||||
expect(scored.score).toBeGreaterThanOrEqual(DEDUP_MATCH_THRESHOLD);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Task } from "@fusion/core";
|
||||
|
||||
const createIssueMock = vi.fn();
|
||||
const searchIssuesMock = vi.fn();
|
||||
const resolveAuthMock = vi.fn();
|
||||
const summarizeTitleMock = vi.fn();
|
||||
|
||||
@@ -16,6 +17,7 @@ vi.mock("@fusion/core", async () => {
|
||||
vi.mock("../github.js", () => ({
|
||||
GitHubClient: vi.fn().mockImplementation(() => ({
|
||||
createIssue: createIssueMock,
|
||||
searchIssues: searchIssuesMock,
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -157,6 +159,7 @@ describe("maybeCreateTrackingIssue", () => {
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
summarizeTitleMock.mockResolvedValue(null);
|
||||
searchIssuesMock.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it("returns tracking_disabled when not enabled", async () => {
|
||||
@@ -247,6 +250,131 @@ describe("maybeCreateTrackingIssue", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("links existing issue when dedup match is found", async () => {
|
||||
const linkGithubIssue = vi.fn();
|
||||
const recordActivity = vi.fn();
|
||||
|
||||
searchIssuesMock.mockResolvedValue([
|
||||
{
|
||||
number: 400,
|
||||
title: "Diff route truncation in packages/dashboard/src/routes/register-session-diff-routes.ts",
|
||||
body: "rebase-merge path drops output",
|
||||
html_url: "https://github.com/o/r/issues/400",
|
||||
state: "closed",
|
||||
updatedAt: "2026-05-01T00:00:00.000Z",
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await maybeCreateTrackingIssue(buildTask({
|
||||
title: "Fix rebase-merge truncation in registerSessionDiffRoutes",
|
||||
description: "## File Scope\n- packages/dashboard/src/routes/register-session-diff-routes.ts",
|
||||
githubTracking: { enabled: true },
|
||||
}), {
|
||||
taskStore: { linkGithubIssue, recordActivity } as any,
|
||||
projectSettings: {},
|
||||
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
||||
rootDir,
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ created: false, reason: "existing_issue_found" });
|
||||
expect(createIssueMock).not.toHaveBeenCalled();
|
||||
expect(linkGithubIssue).toHaveBeenCalledWith("FN-1", expect.objectContaining({
|
||||
owner: "o",
|
||||
repo: "r",
|
||||
number: 400,
|
||||
url: "https://github.com/o/r/issues/400",
|
||||
}));
|
||||
expect(recordActivity).toHaveBeenCalledWith(expect.objectContaining({
|
||||
metadata: expect.objectContaining({ type: "github-issue-dedup-matched", number: 400 }),
|
||||
}));
|
||||
});
|
||||
|
||||
it("falls through to create issue when dedup search has no qualifying match", async () => {
|
||||
searchIssuesMock.mockResolvedValue([
|
||||
{
|
||||
number: 401,
|
||||
title: "Unrelated docs cleanup",
|
||||
body: "touches readme only",
|
||||
html_url: "https://github.com/o/r/issues/401",
|
||||
state: "closed",
|
||||
updatedAt: "2026-05-01T00:00:00.000Z",
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await maybeCreateTrackingIssue(buildTask({
|
||||
title: "Fix rebase-merge truncation in registerSessionDiffRoutes",
|
||||
description: "## File Scope\n- packages/dashboard/src/routes/register-session-diff-routes.ts",
|
||||
githubTracking: { enabled: true },
|
||||
}), {
|
||||
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn() } as any,
|
||||
projectSettings: {},
|
||||
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
||||
rootDir,
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
});
|
||||
|
||||
expect(searchIssuesMock).toHaveBeenCalled();
|
||||
expect(createIssueMock).toHaveBeenCalledTimes(1);
|
||||
expect(result).toMatchObject({ created: true });
|
||||
});
|
||||
|
||||
it("skips dedup search when disabled in project settings", async () => {
|
||||
await maybeCreateTrackingIssue(buildTask({
|
||||
title: "Fix rebase-merge truncation in registerSessionDiffRoutes",
|
||||
description: "## File Scope\n- packages/dashboard/src/routes/register-session-diff-routes.ts",
|
||||
githubTracking: { enabled: true },
|
||||
}), {
|
||||
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn() } as any,
|
||||
projectSettings: { githubTrackingDedupEnabled: false } as any,
|
||||
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
||||
rootDir,
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
});
|
||||
|
||||
expect(searchIssuesMock).not.toHaveBeenCalled();
|
||||
expect(createIssueMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("continues issue creation when dedup search errors", async () => {
|
||||
const logger = { warn: vi.fn(), info: vi.fn() };
|
||||
searchIssuesMock.mockRejectedValue(new Error("search failed"));
|
||||
|
||||
const result = await maybeCreateTrackingIssue(buildTask({
|
||||
title: "Fix rebase-merge truncation in registerSessionDiffRoutes",
|
||||
description: "## File Scope\n- packages/dashboard/src/routes/register-session-diff-routes.ts",
|
||||
githubTracking: { enabled: true },
|
||||
}), {
|
||||
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn() } as any,
|
||||
projectSettings: {},
|
||||
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
||||
rootDir,
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ created: true });
|
||||
expect(createIssueMock).toHaveBeenCalledTimes(1);
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("duplicate-search failed"));
|
||||
});
|
||||
|
||||
it("skips dedup search when there is no file-scope or keyword signal", async () => {
|
||||
await maybeCreateTrackingIssue(buildTask({
|
||||
title: "Fix bug",
|
||||
description: "tiny",
|
||||
githubTracking: { enabled: true },
|
||||
}), {
|
||||
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn() } as any,
|
||||
projectSettings: {},
|
||||
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
||||
rootDir,
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
});
|
||||
|
||||
expect(searchIssuesMock).not.toHaveBeenCalled();
|
||||
expect(createIssueMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("creates issue for github_import tasks when tracking is explicitly enabled", async () => {
|
||||
const linkGithubIssue = vi.fn();
|
||||
const recordActivity = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user