feat(FN-1875): merge fusion/fn-1875

This commit is contained in:
gsxdsm
2026-04-15 09:45:57 -07:00
parent 85f6bf1b5e
commit 72076c65d1
6 changed files with 455 additions and 12 deletions

View File

@@ -1056,4 +1056,177 @@ describe("CronRunner", () => {
expect(result.stepResults![0].error).toContain("timed out");
});
});
// ── Create-task step execution ─────────────────────────────────────────
describe("create-task step execution", () => {
function makeCreateTaskStep(overrides: Partial<AutomationStep> = {}): AutomationStep {
return {
id: randomUUID(),
type: "create-task",
name: "Create task step",
taskDescription: "Review dependencies for security vulnerabilities",
...overrides,
};
}
it("successfully creates a task when taskDescription is provided", async () => {
const mockTask = { id: "FN-1234", title: "Review", description: "Review dependencies" };
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
const store = createMockStore({} as any);
(store as any).createTask = createTaskMock;
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({ taskDescription: "Check npm packages" })],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
const result = await runner.executeSchedule(schedule);
expect(result.success).toBe(true);
expect(result.stepResults).toHaveLength(1);
expect(result.stepResults![0].success).toBe(true);
expect(result.stepResults![0].output).toContain("Created task FN-1234");
expect(createTaskMock).toHaveBeenCalledTimes(1);
});
it("returns error when taskDescription is missing", async () => {
const store = createMockStore();
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({ taskDescription: "" })],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
const result = await runner.executeSchedule(schedule);
expect(result.success).toBe(false);
expect(result.stepResults).toHaveLength(1);
expect(result.stepResults![0].success).toBe(false);
expect(result.stepResults![0].error).toContain("no task description specified");
});
it("passes taskTitle, taskColumn, modelProvider, modelId to store.createTask()", async () => {
const mockTask = { id: "FN-5678", title: "Weekly Review", description: "Check packages" };
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
const store = createMockStore({} as any);
(store as any).createTask = createTaskMock;
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({
taskTitle: "Weekly Review",
taskDescription: "Check packages",
taskColumn: "todo",
modelProvider: "anthropic",
modelId: "claude-sonnet-4-5",
})],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
await runner.executeSchedule(schedule);
expect(createTaskMock).toHaveBeenCalledWith({
title: "Weekly Review",
description: "Check packages",
column: "todo",
modelProvider: "anthropic",
modelId: "claude-sonnet-4-5",
});
});
it("defaults column to triage when taskColumn is not set", async () => {
const mockTask = { id: "FN-9999", title: "", description: "Some task" };
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
const store = createMockStore({} as any);
(store as any).createTask = createTaskMock;
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({ taskDescription: "Some task" })],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
await runner.executeSchedule(schedule);
expect(createTaskMock).toHaveBeenCalledWith(
expect.objectContaining({ column: "triage" }),
);
});
it("handles store.createTask() errors gracefully", async () => {
const createTaskMock = vi.fn().mockRejectedValue(new Error("Database constraint violation"));
const store = createMockStore({} as any);
(store as any).createTask = createTaskMock;
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({ taskDescription: "This will fail" })],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
const result = await runner.executeSchedule(schedule);
expect(result.success).toBe(false);
expect(result.stepResults).toHaveLength(1);
expect(result.stepResults![0].success).toBe(false);
expect(result.stepResults![0].error).toContain("Database constraint violation");
});
it("trims whitespace from task fields", async () => {
const mockTask = { id: "FN-0001", title: "Cleaned", description: "Trimmed" };
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
const store = createMockStore({} as any);
(store as any).createTask = createTaskMock;
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({
taskTitle: " Cleaned ",
taskDescription: " Trimmed ",
modelProvider: " anthropic ",
modelId: " claude-sonnet-4-5 ",
})],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
await runner.executeSchedule(schedule);
expect(createTaskMock).toHaveBeenCalledWith(
expect.objectContaining({
title: "Cleaned",
description: "Trimmed",
modelProvider: "anthropic",
modelId: "claude-sonnet-4-5",
}),
);
});
it("sets title to undefined when taskTitle is not provided", async () => {
const mockTask = { id: "FN-0002", title: "", description: "Minimal task" };
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
const store = createMockStore({} as any);
(store as any).createTask = createTaskMock;
const schedule = createMockSchedule({
command: "",
steps: [makeCreateTaskStep({ taskTitle: undefined })],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
await runner.executeSchedule(schedule);
// When taskTitle is undefined, title should be undefined in the input
const callArg = createTaskMock.mock.calls[0][0];
expect(callArg.title).toBeUndefined();
});
});
});