From 042b70a32eadbabfe90d34d7e7ffcc2e346f799a Mon Sep 17 00:00:00 2001 From: Olivier BERTHET Date: Wed, 29 Jul 2026 00:36:12 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=EF=BB=BFtest(frontend):=20add=20Vitest=20c?= =?UTF-8?q?overage=20for=20thread=20settings=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover openThreadSettingsModal, closeThreadSettingsModal, and submitThreadSettings with DOM fixtures and api() mocks. --- .../__tests__/thread-settings-modal.test.js | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 frontend/src/__tests__/thread-settings-modal.test.js diff --git a/frontend/src/__tests__/thread-settings-modal.test.js b/frontend/src/__tests__/thread-settings-modal.test.js new file mode 100644 index 0000000..205623f --- /dev/null +++ b/frontend/src/__tests__/thread-settings-modal.test.js @@ -0,0 +1,186 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import "../../../web-ui/js/components/acb-modal-shell.js"; +import "../../../web-ui/js/shared-modals.js"; + +const THREAD_ID = "thread-abc-123"; + +function getOverlay() { + return document.getElementById("thread-settings-modal-overlay"); +} + +function createThreadSettingsApi(overrides = {}) { + const agents = overrides.agents ?? [ + { id: "agent-1", emoji: "🦊" }, + ]; + const settings = overrides.settings ?? { + timeout_seconds: 90, + switch_timeout_seconds: 120, + }; + const admin = overrides.admin ?? { + admin_id: "agent-1", + admin_name: "Coordinator", + admin_type: "creator", + }; + const saveResponse = overrides.saveResponse ?? { ok: true }; + + return vi.fn(async (path, options) => { + if (path === "/api/agents") { + return agents; + } + if (path === `/api/threads/${THREAD_ID}/settings` && !options?.method) { + return settings; + } + if (path === `/api/threads/${THREAD_ID}/admin`) { + return admin; + } + if (path === `/api/threads/${THREAD_ID}/settings` && options?.method === "POST") { + if (typeof overrides.saveResponse === "function") { + return overrides.saveResponse(path, options); + } + return saveResponse; + } + throw new Error(`Unexpected request: ${path}`); + }); +} + +describe("thread settings modal", () => { + beforeEach(() => { + document.body.innerHTML = ""; + const shell = document.createElement("acb-modal-shell"); + document.body.appendChild(shell); + window.currentThreadId = THREAD_ID; + }); + + afterEach(() => { + document.body.innerHTML = ""; + delete window.currentThreadId; + vi.restoreAllMocks(); + vi.useRealTimers(); + }); + + it("does not open when currentThreadId is missing", async () => { + delete window.currentThreadId; + const api = createThreadSettingsApi(); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + await window.AcbModals.openThreadSettingsModal(api); + + expect(api).not.toHaveBeenCalled(); + expect(getOverlay().style.display).toBe("none"); + expect(errorSpy).toHaveBeenCalledWith("No current thread selected"); + }); + + it("does not submit when currentThreadId is missing", async () => { + delete window.currentThreadId; + const api = createThreadSettingsApi(); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + await window.AcbModals.submitThreadSettings(api); + + expect(api).not.toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalledWith("No current thread selected"); + }); + + it("open populates timeout fields and current admin from API", async () => { + const api = createThreadSettingsApi(); + + await window.AcbModals.openThreadSettingsModal(api); + + expect(getOverlay().style.display).toBe("flex"); + expect(document.getElementById("ts-timeout-seconds").value).toBe("90"); + expect(document.getElementById("ts-switch-timeout-seconds").value).toBe("120"); + expect(document.getElementById("ts-current-admin").textContent).toBe( + "🦊 Coordinator (Creator)" + ); + expect(document.getElementById("thread-settings-message").style.display).toBe("none"); + + expect(api).toHaveBeenCalledWith("/api/agents"); + expect(api).toHaveBeenCalledWith(`/api/threads/${THREAD_ID}/settings`); + expect(api).toHaveBeenCalledWith(`/api/threads/${THREAD_ID}/admin`); + }); + + it("closeThreadSettingsModal hides the overlay", async () => { + const api = createThreadSettingsApi(); + await window.AcbModals.openThreadSettingsModal(api); + expect(getOverlay().style.display).toBe("flex"); + + window.AcbModals.closeThreadSettingsModal(); + + expect(getOverlay().style.display).toBe("none"); + }); + + it("closeThreadSettingsModal ignores clicks that are not on the overlay", async () => { + const api = createThreadSettingsApi(); + await window.AcbModals.openThreadSettingsModal(api); + + const innerModal = document.getElementById("thread-settings-modal"); + window.AcbModals.closeThreadSettingsModal({ target: innerModal }); + + expect(getOverlay().style.display).toBe("flex"); + }); + + it("submit success shows success message and auto-closes the modal", async () => { + vi.useFakeTimers(); + const api = createThreadSettingsApi(); + await window.AcbModals.openThreadSettingsModal(api); + + document.getElementById("ts-timeout-seconds").value = "45"; + document.getElementById("ts-switch-timeout-seconds").value = "75"; + + const submitPromise = window.AcbModals.submitThreadSettings(api); + await submitPromise; + + const msg = document.getElementById("thread-settings-message"); + expect(msg.textContent).toBe("Settings saved successfully!"); + expect(msg.style.display).toBe("block"); + expect(msg.style.color).toBe("var(--green)"); + expect(getOverlay().style.display).toBe("flex"); + + const postCall = api.mock.calls.find( + ([path, options]) => + path === `/api/threads/${THREAD_ID}/settings` && options?.method === "POST" + ); + expect(postCall).toBeTruthy(); + const payload = JSON.parse(postCall[1].body); + expect(payload.timeout_seconds).toBe(45); + expect(payload.switch_timeout_seconds).toBe(75); + expect(payload.auto_administrator_enabled).toBe(true); + expect(payload.auto_coordinator_enabled).toBe(true); + + await vi.advanceTimersByTimeAsync(1500); + expect(getOverlay().style.display).toBe("none"); + }); + + it("submit error with detail keeps modal open and shows error message", async () => { + const api = createThreadSettingsApi({ + saveResponse: { detail: "Invalid timeout value" }, + }); + await window.AcbModals.openThreadSettingsModal(api); + + await window.AcbModals.submitThreadSettings(api); + + const msg = document.getElementById("thread-settings-message"); + expect(msg.textContent).toBe("Invalid timeout value"); + expect(msg.style.display).toBe("block"); + expect(msg.style.color).toBe("var(--red, #f05555)"); + expect(getOverlay().style.display).toBe("flex"); + }); + + it("submit rejection shows generic error and keeps modal open", async () => { + const api = createThreadSettingsApi({ + saveResponse: () => { + throw new Error("Network failure"); + }, + }); + await window.AcbModals.openThreadSettingsModal(api); + vi.spyOn(console, "error").mockImplementation(() => {}); + + await window.AcbModals.submitThreadSettings(api); + + const msg = document.getElementById("thread-settings-message"); + expect(msg.textContent).toBe("Error saving settings"); + expect(msg.style.display).toBe("block"); + expect(msg.style.color).toBe("var(--red, #f05555)"); + expect(getOverlay().style.display).toBe("flex"); + }); +}); From 1af8c917fb3418de5e0a960554a3a4419151103b Mon Sep 17 00:00:00 2001 From: Olivier BERTHET Date: Wed, 29 Jul 2026 00:48:33 +0200 Subject: [PATCH 2/2] fix(ci): pin mcp below 2.0 for Server.list_tools compatibility --- deprecated_src/python_standalone/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deprecated_src/python_standalone/pyproject.toml b/deprecated_src/python_standalone/pyproject.toml index c3bd1cf..98b9ca4 100644 --- a/deprecated_src/python_standalone/pyproject.toml +++ b/deprecated_src/python_standalone/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ "Operating System :: OS Independent" ] dependencies = [ - "mcp[cli]>=1.3.0", + "mcp[cli]>=1.3.0,<2.0.0", "fastapi>=0.115.0", "uvicorn[standard]>=0.30.0", "aiosqlite>=0.20.0",