diff --git a/frontend/src/adapters/http/httpInvoker.test.ts b/frontend/src/adapters/http/httpInvoker.test.ts index 4c5c134e..c58fe6b7 100644 --- a/frontend/src/adapters/http/httpInvoker.test.ts +++ b/frontend/src/adapters/http/httpInvoker.test.ts @@ -8,7 +8,11 @@ import { describe, it, expect, vi } from "vitest"; import type { FetchLike } from "./httpInvoker"; import { HttpInvoker, defaultFetch } from "./httpInvoker"; -import { HttpProjectGateway, HttpGitGateway } from "./requestResponseGateways"; +import { + HttpProjectGateway, + HttpGitGateway, + HttpTemplateGateway, +} from "./requestResponseGateways"; /** * Installs a global `fetch` that records its receiver (`this`), runs `body`, then @@ -251,4 +255,76 @@ describe("request/response gateways preserve the Tauri command contract", () => expect(spy).toHaveBeenCalledWith("git_status", { projectId: "p1" }); }); + + it("HttpTemplateGateway normalizes saved default MCP tool permissions from backend DTOs", async () => { + const http = new HttpInvoker({ baseUrl: "https://h" }); + const template = { + id: "t1", + name: "Permissioned", + contentMd: "# Body", + version: 1, + defaultProfileId: "p1", + defaultSystemPermissions: { network: "allow" }, + defaultMcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }; + const spy = vi + .spyOn(http, "invoke") + .mockResolvedValueOnce([template] as never) + .mockResolvedValueOnce(template as never) + .mockResolvedValueOnce(template as never); + const gw = new HttpTemplateGateway(http); + + await expect(gw.listTemplates()).resolves.toMatchObject([ + { + systemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }, + ]); + await expect( + gw.createTemplate({ + name: "Permissioned", + content: "# Body", + defaultProfileId: "p1", + systemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }), + ).resolves.toMatchObject({ + systemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }); + await expect( + gw.updateTemplate("t1", { + name: "Permissioned", + content: "# Body", + defaultProfileId: "p1", + systemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }), + ).resolves.toMatchObject({ + systemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }); + + expect(spy).toHaveBeenNthCalledWith(2, "create_template", { + request: { + name: "Permissioned", + content: "# Body", + defaultProfileId: "p1", + defaultAgentName: null, + defaultSystemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }, + }); + expect(spy).toHaveBeenNthCalledWith(3, "update_template", { + request: { + templateId: "t1", + name: "Permissioned", + content: "# Body", + defaultProfileId: "p1", + defaultAgentName: null, + defaultSystemPermissions: { network: "allow" }, + mcpToolPermissions: { allowedTools: ["idea_ticket_read"] }, + }, + }); + }); }); diff --git a/frontend/src/adapters/http/requestResponseGateways.ts b/frontend/src/adapters/http/requestResponseGateways.ts index aead477c..f02bf198 100644 --- a/frontend/src/adapters/http/requestResponseGateways.ts +++ b/frontend/src/adapters/http/requestResponseGateways.ts @@ -80,6 +80,7 @@ import { normalizeProjectWorkState } from "../workStateNormalization"; import { unsupportedOnWeb } from "./unsupported"; import { normalizeTurnPage } from "../conversationNormalization"; import { normalizeProfileModelCatalog } from "../profileCatalog"; +import { normalizeTemplate, normalizeTemplates } from "../templateNormalization"; import type { HttpInvoker } from "./httpInvoker"; export class HttpProjectGateway implements ProjectGateway { @@ -282,33 +283,33 @@ export class HttpModelServerGateway implements ModelServerGateway { export class HttpTemplateGateway implements TemplateGateway { constructor(private readonly http: HttpInvoker) {} - listTemplates(): Promise { - return this.http.invoke("list_templates"); + async listTemplates(): Promise { + return normalizeTemplates(await this.http.invoke("list_templates")); } - createTemplate(input: CreateTemplateInput): Promise