merge(templates): permissions des templates persistantes après save/reopen — #259 (QA verte)
This commit is contained in:
@ -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"] },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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<Template[]> {
|
||||
return this.http.invoke<Template[]>("list_templates");
|
||||
async listTemplates(): Promise<Template[]> {
|
||||
return normalizeTemplates(await this.http.invoke<Template[]>("list_templates"));
|
||||
}
|
||||
createTemplate(input: CreateTemplateInput): Promise<Template> {
|
||||
return this.http.invoke<Template>("create_template", {
|
||||
async createTemplate(input: CreateTemplateInput): Promise<Template> {
|
||||
return normalizeTemplate(await this.http.invoke<Template>("create_template", {
|
||||
request: {
|
||||
name: input.name,
|
||||
content: input.content,
|
||||
defaultProfileId: input.defaultProfileId,
|
||||
defaultAgentName: input.defaultAgentName ?? null,
|
||||
systemPermissions: input.systemPermissions ?? null,
|
||||
defaultSystemPermissions: input.systemPermissions ?? null,
|
||||
mcpToolPermissions: input.mcpToolPermissions ?? null,
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
updateTemplate(templateId: string, input: UpdateTemplateInput): Promise<Template> {
|
||||
return this.http.invoke<Template>("update_template", {
|
||||
async updateTemplate(templateId: string, input: UpdateTemplateInput): Promise<Template> {
|
||||
return normalizeTemplate(await this.http.invoke<Template>("update_template", {
|
||||
request: {
|
||||
templateId,
|
||||
name: input.name,
|
||||
content: input.content,
|
||||
defaultProfileId: input.defaultProfileId,
|
||||
defaultAgentName: input.defaultAgentName ?? null,
|
||||
systemPermissions: input.systemPermissions ?? null,
|
||||
defaultSystemPermissions: input.systemPermissions ?? null,
|
||||
mcpToolPermissions: input.mcpToolPermissions ?? null,
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
async deleteTemplate(templateId: string): Promise<void> {
|
||||
await this.http.invoke("delete_template", { templateId });
|
||||
|
||||
@ -14,37 +14,38 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import type { Agent, AgentDrift, Template } from "@/domain";
|
||||
import type { CreateTemplateInput, TemplateGateway, UpdateTemplateInput } from "@/ports";
|
||||
import { normalizeTemplate, normalizeTemplates } from "./templateNormalization";
|
||||
|
||||
export class TauriTemplateGateway implements TemplateGateway {
|
||||
listTemplates(): Promise<Template[]> {
|
||||
return invoke<Template[]>("list_templates");
|
||||
async listTemplates(): Promise<Template[]> {
|
||||
return normalizeTemplates(await invoke<Template[]>("list_templates"));
|
||||
}
|
||||
|
||||
createTemplate(input: CreateTemplateInput): Promise<Template> {
|
||||
return invoke<Template>("create_template", {
|
||||
async createTemplate(input: CreateTemplateInput): Promise<Template> {
|
||||
return normalizeTemplate(await invoke<Template>("create_template", {
|
||||
request: {
|
||||
name: input.name,
|
||||
content: input.content,
|
||||
defaultProfileId: input.defaultProfileId,
|
||||
defaultAgentName: input.defaultAgentName ?? null,
|
||||
systemPermissions: input.systemPermissions ?? null,
|
||||
defaultSystemPermissions: input.systemPermissions ?? null,
|
||||
mcpToolPermissions: input.mcpToolPermissions ?? null,
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
updateTemplate(templateId: string, input: UpdateTemplateInput): Promise<Template> {
|
||||
return invoke<Template>("update_template", {
|
||||
async updateTemplate(templateId: string, input: UpdateTemplateInput): Promise<Template> {
|
||||
return normalizeTemplate(await invoke<Template>("update_template", {
|
||||
request: {
|
||||
templateId,
|
||||
name: input.name,
|
||||
content: input.content,
|
||||
defaultProfileId: input.defaultProfileId,
|
||||
defaultAgentName: input.defaultAgentName ?? null,
|
||||
systemPermissions: input.systemPermissions ?? null,
|
||||
defaultSystemPermissions: input.systemPermissions ?? null,
|
||||
mcpToolPermissions: input.mcpToolPermissions ?? null,
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
async deleteTemplate(templateId: string): Promise<void> {
|
||||
|
||||
19
frontend/src/adapters/templateNormalization.ts
Normal file
19
frontend/src/adapters/templateNormalization.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { McpToolPolicy, SystemPermissionSet, Template } from "@/domain";
|
||||
|
||||
type TemplateDto = Template & {
|
||||
defaultSystemPermissions?: SystemPermissionSet | null;
|
||||
defaultMcpToolPermissions?: McpToolPolicy | null;
|
||||
};
|
||||
|
||||
export function normalizeTemplate(template: TemplateDto): Template {
|
||||
const { defaultSystemPermissions, defaultMcpToolPermissions, ...rest } = template;
|
||||
return {
|
||||
...rest,
|
||||
systemPermissions: rest.systemPermissions ?? defaultSystemPermissions ?? null,
|
||||
mcpToolPermissions: rest.mcpToolPermissions ?? defaultMcpToolPermissions ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeTemplates(templates: TemplateDto[]): Template[] {
|
||||
return templates.map(normalizeTemplate);
|
||||
}
|
||||
Reference in New Issue
Block a user