Files
kandev/apps/web/lib/ws/handlers/gitlab.test.ts
T
Alassane 262ecdf270 feat(mcp): handle task change links (#3506)
Co-authored-by: Carlos Florêncio <carlosmflorencio@gmail.com>
Co-authored-by: ayattara <alassane.yattara@savoirfairelinux.com>
2026-09-14 15:00:35 +01:00

162 lines
5.4 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { StoreApi } from "zustand";
import type { AppState } from "@/lib/state/store";
import type { TaskMR, TaskMRAutomationOptions } from "@/lib/types/gitlab";
import { registerGitLabHandlers } from "./gitlab";
const WORKSPACE_A = "workspace-a";
function makeStore(activeWorkspaceId: string | null, options?: TaskMRAutomationOptions) {
const setTaskMR = vi.fn();
const removeTaskMR = vi.fn();
const setTaskMRAutomationOptions = vi.fn();
const markTaskMRAutomationExternalUpdate = vi.fn();
const state = {
workspaces: { activeId: activeWorkspaceId },
taskMRAutomation: { byTaskId: options ? { [options.task_id]: options } : {} },
setTaskMR,
removeTaskMR,
setTaskMRAutomationOptions,
markTaskMRAutomationExternalUpdate,
} as unknown as AppState;
return {
store: { getState: () => state } as StoreApi<AppState>,
setTaskMR,
removeTaskMR,
setTaskMRAutomationOptions,
markTaskMRAutomationExternalUpdate,
};
}
function taskMRAutomationOptions(
overrides: Partial<TaskMRAutomationOptions> = {},
): TaskMRAutomationOptions {
return {
task_id: "task-1",
auto_fix_enabled: false,
auto_merge_enabled: false,
auto_fix_max_rounds: 10,
effective_auto_fix_prompt: "",
using_default_prompt: true,
prompt_on_review_requested: false,
prompt_on_merged: true,
prompt_on_closed: false,
review_reviewer_username: "",
updated_at: "2026-01-01T00:00:00Z",
mr_states: [],
mr_options: [],
...overrides,
};
}
function taskMR(workspaceId: string) {
return {
workspace_id: workspaceId,
id: "mr-1",
task_id: "task-1",
host: "https://gitlab.example.test",
project_path: "group/project",
mr_iid: 7,
} as TaskMR & { workspace_id: string };
}
describe("GitLab WebSocket handlers", () => {
it("upserts a task MR for the active workspace", () => {
const { store, setTaskMR } = makeStore(WORKSPACE_A);
const handler = registerGitLabHandlers(store)["gitlab.task_mr.updated"]!;
const mr = taskMR(WORKSPACE_A);
handler({ type: "notification", action: "gitlab.task_mr.updated", payload: mr });
expect(setTaskMR).toHaveBeenCalledWith(WORKSPACE_A, "task-1", mr);
});
it("removes a task MR for the active workspace", () => {
const { store, removeTaskMR } = makeStore(WORKSPACE_A);
const handler = (registerGitLabHandlers(store) as Record<string, (message: never) => void>)[
"gitlab.task_mr.deleted"
]!;
handler({
type: "notification",
action: "gitlab.task_mr.deleted",
payload: { workspace_id: WORKSPACE_A, task_id: "task-1", association_id: "mr-1" },
} as never);
expect(removeTaskMR).toHaveBeenCalledWith(WORKSPACE_A, "mr-1");
});
it("ignores task MR deletion from another workspace", () => {
const { store, removeTaskMR } = makeStore("workspace-b");
const handler = (registerGitLabHandlers(store) as Record<string, (message: never) => void>)[
"gitlab.task_mr.deleted"
]!;
handler({
type: "notification",
action: "gitlab.task_mr.deleted",
payload: { workspace_id: WORKSPACE_A, task_id: "task-1", association_id: "mr-1" },
} as never);
expect(removeTaskMR).not.toHaveBeenCalled();
});
it("ignores task MRs from another workspace", () => {
const { store, setTaskMR } = makeStore("workspace-b");
const handler = registerGitLabHandlers(store)["gitlab.task_mr.updated"]!;
handler({
type: "notification",
action: "gitlab.task_mr.updated",
payload: taskMR(WORKSPACE_A),
});
expect(setTaskMR).not.toHaveBeenCalled();
});
it("stores a task MR automation options update pushed for the current task", () => {
const { store, setTaskMRAutomationOptions, markTaskMRAutomationExternalUpdate } =
makeStore(WORKSPACE_A);
const handler = registerGitLabHandlers(store)["gitlab.task_mr_options.updated"]!;
const options = taskMRAutomationOptions();
handler({ type: "notification", action: "gitlab.task_mr_options.updated", payload: options });
expect(setTaskMRAutomationOptions).toHaveBeenCalledWith("task-1", options);
// Marks the write as externally-sourced so an in-flight local
// refresh()/update() in the hook knows not to overwrite it.
expect(markTaskMRAutomationExternalUpdate).toHaveBeenCalledWith("task-1");
});
it("drops an older MR automation snapshot pushed after a newer one", () => {
const current = taskMRAutomationOptions({ automation_revision: 2 });
const { store, setTaskMRAutomationOptions, markTaskMRAutomationExternalUpdate } = makeStore(
WORKSPACE_A,
current,
);
const handler = registerGitLabHandlers(store)["gitlab.task_mr_options.updated"]!;
handler({
type: "notification",
action: "gitlab.task_mr_options.updated",
payload: taskMRAutomationOptions({ automation_revision: 1 }),
});
expect(setTaskMRAutomationOptions).not.toHaveBeenCalled();
expect(markTaskMRAutomationExternalUpdate).not.toHaveBeenCalled();
});
it("ignores a task MR automation options update with no task_id", () => {
const { store, setTaskMRAutomationOptions } = makeStore(WORKSPACE_A);
const handler = registerGitLabHandlers(store)["gitlab.task_mr_options.updated"]!;
handler({
type: "notification",
action: "gitlab.task_mr_options.updated",
payload: taskMRAutomationOptions({ task_id: "" }),
});
expect(setTaskMRAutomationOptions).not.toHaveBeenCalled();
});
});