Adds regression tests for automation tracked-task creation behavior across the dashboard automation and tasks routes, plus a documentation clarification on the semantics of how automation creates tracked tasks. Fusion-Task-Id: FN-3963
215 lines
7.9 KiB
TypeScript
215 lines
7.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { Task } from "@fusion/core";
|
|
|
|
const createIssueMock = vi.fn();
|
|
const resolveAuthMock = vi.fn();
|
|
|
|
vi.mock("../github.js", () => ({
|
|
GitHubClient: vi.fn().mockImplementation(() => ({
|
|
createIssue: createIssueMock,
|
|
})),
|
|
}));
|
|
|
|
vi.mock("../github-auth.js", () => ({
|
|
resolveGithubTrackingAuth: (...args: unknown[]) => resolveAuthMock(...args),
|
|
}));
|
|
|
|
import {
|
|
formatTrackingIssueBody,
|
|
formatTrackingIssueTitle,
|
|
maybeCreateTrackingIssue,
|
|
} from "../github-tracking.js";
|
|
|
|
function buildTask(overrides: Partial<Task> = {}): Task {
|
|
return {
|
|
id: "FN-1",
|
|
description: "desc",
|
|
column: "todo",
|
|
dependencies: [],
|
|
steps: [],
|
|
currentStep: 0,
|
|
log: [],
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
...overrides,
|
|
} as Task;
|
|
}
|
|
|
|
describe("formatTrackingIssueTitle", () => {
|
|
it("formats a normal title", () => {
|
|
expect(formatTrackingIssueTitle({ id: "FN-1", title: "Hello" })).toBe("[FN-1] Hello");
|
|
});
|
|
|
|
it("truncates very long titles while preserving id prefix", () => {
|
|
const longTitle = "x".repeat(400);
|
|
const formatted = formatTrackingIssueTitle({ id: "FN-123", title: longTitle });
|
|
expect(formatted.startsWith("[FN-123] ")).toBe(true);
|
|
expect(formatted.length).toBeLessThanOrEqual(240);
|
|
});
|
|
});
|
|
|
|
describe("formatTrackingIssueBody", () => {
|
|
it("prefers first description paragraph", () => {
|
|
expect(formatTrackingIssueBody({
|
|
id: "FN-X",
|
|
description: "Primary paragraph\n\nSecond paragraph",
|
|
prompt: "Prompt paragraph",
|
|
summary: "Summary paragraph",
|
|
})).toBe("Fusion task: FN-X\n\nPrimary paragraph");
|
|
});
|
|
|
|
it("does not include full prompt content or fusion hyperlinks", () => {
|
|
const body = formatTrackingIssueBody({
|
|
id: "FN-X",
|
|
description: "Short summary only",
|
|
prompt: "# PROMPT\nhttp://localhost:4040/tasks/FN-X\nFull private prompt",
|
|
});
|
|
|
|
expect(body).toContain("Fusion task: FN-X");
|
|
expect(body).toContain("Short summary only");
|
|
expect(body).not.toContain("localhost:4040/tasks/FN-X");
|
|
expect(body).not.toContain("Full private prompt");
|
|
});
|
|
});
|
|
|
|
describe("maybeCreateTrackingIssue", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
resolveAuthMock.mockReturnValue({ ok: true, auth: { mode: "token", token: "tok" } });
|
|
createIssueMock.mockResolvedValue({
|
|
owner: "o",
|
|
repo: "r",
|
|
number: 12,
|
|
htmlUrl: "https://github.com/o/r/issues/12",
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
});
|
|
});
|
|
|
|
it("returns tracking_disabled when not enabled", async () => {
|
|
const result = await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: false } }), {
|
|
taskStore: {} as any,
|
|
projectSettings: {},
|
|
globalSettings: {},
|
|
});
|
|
expect(result).toEqual({ created: false, reason: "tracking_disabled" });
|
|
});
|
|
|
|
it("returns issue_already_linked and does not create again", async () => {
|
|
const result = await maybeCreateTrackingIssue(buildTask({
|
|
githubTracking: {
|
|
enabled: true,
|
|
issue: { owner: "task", repo: "repo", number: 99, url: "https://github.com/task/repo/issues/99" },
|
|
},
|
|
}), {
|
|
taskStore: {} as any,
|
|
projectSettings: { githubTrackingDefaultRepo: "task/repo", githubAuthMode: "token", githubAuthToken: "tok" } as any,
|
|
globalSettings: {},
|
|
});
|
|
|
|
expect(result).toEqual({ created: false, reason: "issue_already_linked" });
|
|
expect(createIssueMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns no_repo_configured and records activity", async () => {
|
|
const recordActivity = vi.fn();
|
|
const result = await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: true } }), {
|
|
taskStore: { recordActivity } as any,
|
|
projectSettings: {},
|
|
globalSettings: {},
|
|
logger: { warn: vi.fn(), info: vi.fn() },
|
|
});
|
|
|
|
expect(result).toEqual({ created: false, reason: "no_repo_configured" });
|
|
expect(recordActivity).toHaveBeenCalledTimes(1);
|
|
expect(createIssueMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("creates issue, links metadata, and records activity", async () => {
|
|
const linkGithubIssue = vi.fn();
|
|
const recordActivity = vi.fn();
|
|
|
|
const result = await maybeCreateTrackingIssue(buildTask({ title: "Test", description: "Short body", githubTracking: { enabled: true } }), {
|
|
taskStore: { linkGithubIssue, recordActivity } as any,
|
|
projectSettings: {},
|
|
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
|
logger: console,
|
|
});
|
|
|
|
expect(result.created).toBe(true);
|
|
expect(createIssueMock).toHaveBeenCalledTimes(1);
|
|
expect(createIssueMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
title: expect.stringContaining("[FN-1]"),
|
|
body: expect.stringContaining("Fusion task: FN-1"),
|
|
}));
|
|
expect(linkGithubIssue).toHaveBeenCalledWith("FN-1", expect.objectContaining({ owner: "o", repo: "r", number: 12 }));
|
|
expect(recordActivity).toHaveBeenCalledWith(expect.objectContaining({
|
|
metadata: expect.objectContaining({ type: "github-issue-created", repo: "o/r", number: 12 }),
|
|
}));
|
|
});
|
|
|
|
it.each([
|
|
["task override", { enabled: true, repoOverride: "task/repo" }, { githubTrackingDefaultRepo: "project/repo" }, { githubTrackingDefaultRepo: "global/repo" }, "task", "repo"],
|
|
["project default", { enabled: true }, { githubTrackingDefaultRepo: "project/repo" }, { githubTrackingDefaultRepo: "global/repo" }, "project", "repo"],
|
|
["global default", { enabled: true }, {}, { githubTrackingDefaultRepo: "global/repo" }, "global", "repo"],
|
|
] as const)("resolves repo from %s", async (_label, tracking, projectSettings, globalSettings, owner, repo) => {
|
|
const linkGithubIssue = vi.fn();
|
|
|
|
await maybeCreateTrackingIssue(buildTask({ githubTracking: tracking }), {
|
|
taskStore: { linkGithubIssue, recordActivity: vi.fn() } as any,
|
|
projectSettings: projectSettings as any,
|
|
globalSettings: globalSettings as any,
|
|
logger: console,
|
|
});
|
|
|
|
expect(createIssueMock).toHaveBeenCalledWith(expect.objectContaining({ owner, repo }));
|
|
});
|
|
|
|
it("creates a tracking issue from explicit task override when defaults are unset", async () => {
|
|
const linkGithubIssue = vi.fn();
|
|
|
|
await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: true, repoOverride: "task/repo" } }), {
|
|
taskStore: { linkGithubIssue, recordActivity: vi.fn() } as any,
|
|
projectSettings: {},
|
|
globalSettings: {},
|
|
logger: console,
|
|
});
|
|
|
|
expect(createIssueMock).toHaveBeenCalledWith(expect.objectContaining({ owner: "task", repo: "repo" }));
|
|
});
|
|
|
|
it("skips creation when tracking is on but no repo is configured", async () => {
|
|
const result = await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: true } }), {
|
|
taskStore: { recordActivity: vi.fn() } as any,
|
|
projectSettings: {},
|
|
globalSettings: {},
|
|
logger: { warn: vi.fn(), info: vi.fn() },
|
|
});
|
|
|
|
expect(result).toEqual({ created: false, reason: "no_repo_configured" });
|
|
expect(createIssueMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns auth reason when resolver fails", async () => {
|
|
resolveAuthMock.mockReturnValue({
|
|
ok: false,
|
|
requestedMode: "token",
|
|
reason: "token_missing",
|
|
message: "missing token",
|
|
});
|
|
const recordActivity = vi.fn();
|
|
|
|
const result = await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: true } }), {
|
|
taskStore: { recordActivity } as any,
|
|
projectSettings: {},
|
|
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
|
|
logger: { warn: vi.fn(), info: vi.fn() },
|
|
});
|
|
|
|
expect(result).toEqual({ created: false, reason: "auth_token_missing" });
|
|
expect(recordActivity).toHaveBeenCalledWith(expect.objectContaining({
|
|
metadata: expect.objectContaining({ type: "github-issue-skipped", reason: "token_missing" }),
|
|
}));
|
|
expect(createIssueMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|