fix(permissions): validate network access flow

This commit is contained in:
2026-07-26 10:52:46 +02:00
parent 3047dc9195
commit 13fb538880
70 changed files with 4784 additions and 158 deletions

View File

@ -102,7 +102,7 @@ describe("PermissionsPanel", () => {
expect(screen.getAllByText("Réseau autorisé").length).toBeGreaterThan(0);
});
it("renders the network control read-only when the resolved runtime says so", async () => {
it("renders the network control read-only when persisted control says so", async () => {
const agent = new MockAgentGateway();
const permission = new MockPermissionGateway();
const created = await agent.createAgent(PROJECT_ID, {
@ -118,6 +118,7 @@ describe("PermissionsPanel", () => {
reason: "Runtime network is locked.",
},
control: { mode: "readOnly", reason: "Runtime network is locked." },
runtimeControl: { mode: "readOnly", reason: "Runtime network is locked." },
});
const gateways = {
agent,
@ -132,7 +133,7 @@ describe("PermissionsPanel", () => {
);
await waitFor(() => {
expect(screen.getByText("Verrouillé par le runtime")).toBeTruthy();
expect(screen.getByText("Runtime verrouillé")).toBeTruthy();
});
expect(screen.getByText("Runtime network is locked.")).toBeTruthy();
expect(
@ -140,4 +141,95 @@ describe("PermissionsPanel", () => {
.disabled,
).toBe(true);
});
it("keeps wanted network editable when no runtime is active", async () => {
const { permission } = await renderPanel();
await waitFor(() => {
expect(screen.getByText("Aucun runtime actif")).toBeTruthy();
});
expect(
screen.getByText(
"Aucun runtime actif. La configuration sera appliquée au prochain lancement.",
),
).toBeTruthy();
expect(
(screen.getByLabelText("Réseau — defaults projet network") as HTMLSelectElement)
.disabled,
).toBe(false);
fireEvent.change(screen.getByLabelText("Réseau — defaults projet network"), {
target: { value: "deny" },
});
fireEvent.click(screen.getByRole("button", { name: "Save network" }));
await waitFor(async () => {
const doc = await permission.getProjectSystemPermissions(PROJECT_ID);
expect(doc.projectDefault?.network).toBe("deny");
});
});
it("keeps an idle agent editable by default and only enables Save after a change", async () => {
await renderPanel();
fireEvent.click(screen.getByText("Builder"));
await waitFor(() => {
expect(screen.getByText("Aucun runtime actif")).toBeTruthy();
});
expect(screen.queryByText("Lecture seule")).toBeNull();
expect(screen.queryByText("Runtime verrouillé")).toBeNull();
const select = screen.getByLabelText("Réseau — Builder network") as HTMLSelectElement;
const save = screen.getByRole("button", { name: "Save network" });
expect(select.disabled).toBe(false);
expect(save).toHaveProperty("disabled", true);
fireEvent.change(select, { target: { value: "allow" } });
expect(save).toHaveProperty("disabled", false);
});
it("keeps wanted network editable when only runtime control is read-only", async () => {
const agent = new MockAgentGateway();
const permission = new MockPermissionGateway();
const created = await agent.createAgent(PROJECT_ID, {
name: "Builder",
profileId: "p1",
});
permission.setResolvedAgentSystemPermissions(created.id, {
wanted: "allow",
effective: "deny",
runtimeLock: {
state: "locked",
source: "external-runtime",
reason: "IdeA cannot change this active runtime.",
},
control: { mode: "editable" },
runtimeControl: {
mode: "readOnly",
reason: "IdeA cannot change this active runtime.",
},
});
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("Runtime verrouillé")).toBeTruthy();
});
expect(
(screen.getByLabelText("Réseau — defaults projet network") as HTMLSelectElement)
.disabled,
).toBe(false);
});
});