mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 09:48:39 +00:00
test(config): cover operator policy overlay
This commit is contained in:
@@ -47,6 +47,13 @@ describe("config io write", () => {
|
|||||||
return { configPath, io, snapshot };
|
return { configPath, io, snapshot };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeOperatorPolicy(params: { home: string; policy: Record<string, unknown> }) {
|
||||||
|
const policyPath = path.join(params.home, ".openclaw", "operator-policy.json5");
|
||||||
|
await fs.mkdir(path.dirname(policyPath), { recursive: true });
|
||||||
|
await fs.writeFile(policyPath, JSON.stringify(params.policy, null, 2), "utf-8");
|
||||||
|
return policyPath;
|
||||||
|
}
|
||||||
|
|
||||||
async function writeTokenAuthAndReadConfig(params: {
|
async function writeTokenAuthAndReadConfig(params: {
|
||||||
io: { writeConfigFile: (config: Record<string, unknown>) => Promise<void> };
|
io: { writeConfigFile: (config: Record<string, unknown>) => Promise<void> };
|
||||||
snapshot: { config: Record<string, unknown> };
|
snapshot: { config: Record<string, unknown> };
|
||||||
@@ -142,6 +149,86 @@ describe("config io write", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("applies immutable operator policy to the effective config snapshot", async () => {
|
||||||
|
await withSuiteHome(async (home) => {
|
||||||
|
await writeOperatorPolicy({
|
||||||
|
home,
|
||||||
|
policy: {
|
||||||
|
tools: { profile: "messaging" },
|
||||||
|
approvals: { exec: { enabled: false } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { io, snapshot } = await writeConfigAndCreateIo({
|
||||||
|
home,
|
||||||
|
initialConfig: { gateway: { mode: "local" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const reloaded = await io.readConfigFileSnapshot();
|
||||||
|
expect(reloaded.valid).toBe(true);
|
||||||
|
expect(reloaded.config.gateway?.mode).toBe("local");
|
||||||
|
expect(reloaded.config.tools?.profile).toBe("messaging");
|
||||||
|
expect(reloaded.config.approvals?.exec?.enabled).toBe(false);
|
||||||
|
expect(reloaded.resolved.tools?.profile).toBeUndefined();
|
||||||
|
expect(reloaded.policy?.exists).toBe(true);
|
||||||
|
expect(reloaded.policy?.valid).toBe(true);
|
||||||
|
expect(reloaded.policy?.lockedPaths).toEqual(
|
||||||
|
expect.arrayContaining(["tools.profile", "approvals.exec.enabled"]),
|
||||||
|
);
|
||||||
|
expect(snapshot.policy?.lockedPaths).toEqual(reloaded.policy?.lockedPaths);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects writes that conflict with locked operator policy paths", async () => {
|
||||||
|
await withSuiteHome(async (home) => {
|
||||||
|
await writeOperatorPolicy({
|
||||||
|
home,
|
||||||
|
policy: {
|
||||||
|
tools: { profile: "messaging" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { io, snapshot } = await writeConfigAndCreateIo({
|
||||||
|
home,
|
||||||
|
initialConfig: { gateway: { mode: "local" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const next = structuredClone(snapshot.config);
|
||||||
|
next.tools = {
|
||||||
|
...next.tools,
|
||||||
|
profile: "full",
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(io.writeConfigFile(next)).rejects.toThrow(
|
||||||
|
"Config path locked by operator policy: tools.profile",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps locked operator policy paths out of the mutable config file", async () => {
|
||||||
|
await withSuiteHome(async (home) => {
|
||||||
|
await writeOperatorPolicy({
|
||||||
|
home,
|
||||||
|
policy: {
|
||||||
|
tools: { profile: "messaging" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { configPath, io, snapshot } = await writeConfigAndCreateIo({
|
||||||
|
home,
|
||||||
|
initialConfig: { gateway: { mode: "local" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const next = structuredClone(snapshot.config);
|
||||||
|
next.gateway = { mode: "remote" };
|
||||||
|
await io.writeConfigFile(next);
|
||||||
|
|
||||||
|
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as {
|
||||||
|
gateway?: { mode?: string };
|
||||||
|
tools?: { profile?: string };
|
||||||
|
};
|
||||||
|
expect(persisted.gateway?.mode).toBe("remote");
|
||||||
|
expect(persisted.tools?.profile).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('shows actionable guidance for dmPolicy="open" without wildcard allowFrom', async () => {
|
it('shows actionable guidance for dmPolicy="open" without wildcard allowFrom', async () => {
|
||||||
await withSuiteHome(async (home) => {
|
await withSuiteHome(async (home) => {
|
||||||
const io = createConfigIO({
|
const io = createConfigIO({
|
||||||
|
|||||||
Reference in New Issue
Block a user