Files
IdeA/frontend/src/features/permissions/permissions.test.tsx
Blomios 27597eb64e feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree,
indissociables au niveau fichier mais tous deux verts (cargo test
--workspace + tests frontend permissions au vert).

Permissions — voie « projection CLI » (advisory), complète :
- LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve
  deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs).
- LP1 store : FsPermissionStore (.ideai/permissions.json).
- LP2 use cases : Get/Update project, Update agent override, Resolve.
- LP3 projecteurs Claude/Codex (settings.local.json / config.toml),
  câblage launch-path + PermissionProjectorRegistry, nettoyage des
  fichiers Replace orphelins au swap de profil (LP3-4), composition root
  + commandes Tauri, UI PermissionsPanel (projet + override agent).
- ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap).

Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS
airtight (Landlock fichiers) + résumé de permissions injecté.

Inclut aussi le chantier Codex/input/sessions structurées en cours
(McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les
mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:39:18 +02:00

90 lines
2.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import {
MockAgentGateway,
MockPermissionGateway,
MockProfileGateway,
} from "@/adapters/mock";
import { DIProvider } from "@/app/di";
import type { Gateways } from "@/ports";
import { PermissionsPanel } from "./PermissionsPanel";
const PROJECT_ID = "proj-permissions-test";
async function renderPanel() {
const agent = new MockAgentGateway();
const permission = new MockPermissionGateway();
await agent.createAgent(PROJECT_ID, { name: "Builder", profileId: "p1" });
const gateways = {
agent,
permission,
profile: new MockProfileGateway(),
} as unknown as Gateways;
render(
<DIProvider gateways={gateways}>
<PermissionsPanel projectId={PROJECT_ID} />
</DIProvider>,
);
await waitFor(() => {
expect(screen.getByText("Builder")).toBeTruthy();
});
return { agent, permission };
}
function clickChoice(groupName: string, choice: string) {
const group = screen.getByRole("group", { name: groupName });
fireEvent.click(within(group).getByRole("button", { name: choice }));
}
describe("PermissionsPanel", () => {
it("saves project defaults from the compact policy editor", async () => {
const { permission } = await renderPanel();
fireEvent.change(screen.getByLabelText("Project defaults fallback"), {
target: { value: "allow" },
});
clickChoice("Read permission", "Allow");
clickChoice("Bash permission", "Deny");
fireEvent.click(screen.getByRole("button", { name: "Save" }));
await waitFor(async () => {
const doc = await permission.getProjectPermissions(PROJECT_ID);
expect(doc.projectDefaults?.fallback).toBe("allow");
expect(doc.projectDefaults?.rules).toEqual([
{ capability: "read", effect: "allow", paths: ["**"] },
{ capability: "executeBash", effect: "deny", commands: [] },
]);
});
});
it("saves and clears an agent override", async () => {
const { permission } = await renderPanel();
fireEvent.click(screen.getByText("Builder"));
fireEvent.change(screen.getByLabelText("Override — Builder fallback"), {
target: { value: "deny" },
});
clickChoice("Write permission", "Allow");
fireEvent.click(screen.getByRole("button", { name: "Save" }));
await waitFor(async () => {
const doc = await permission.getProjectPermissions(PROJECT_ID);
expect(doc.agents?.[0]?.permissions.fallback).toBe("deny");
expect(doc.agents?.[0]?.permissions.rules).toEqual([
{ capability: "write", effect: "allow", paths: ["**"] },
]);
});
fireEvent.click(screen.getByRole("button", { name: "Clear" }));
await waitFor(async () => {
const doc = await permission.getProjectPermissions(PROJECT_ID);
expect(doc.agents).toEqual([]);
});
});
});