mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 23:41:42 +00:00
* fix(secrets): isolate reload failures per owner * refactor(secrets): split runtime activation helpers * fix(secrets): export web warning type * fix(secrets): reject unsafe degraded config writes * fix(secrets): derive reload defaults type * fix(secrets): defer reload state publication * fix(secrets): preserve partial refresh state * fix(secrets): retry superseded reload preflight * fix(secrets): bind stale credentials to owner contracts * fix(secrets): scope degraded credential contracts * fix(secrets): restore source ownership guards * fix(secrets): recover provider-only degradation * fix(secrets): enforce degraded reload contracts * fix(secrets): preserve scoped reload state * fix(secrets): reconcile deferred descendant state * fix(secrets): commit reload state atomically * fix(secrets): preserve source transaction lineage * test(secrets): use non-secret lineage marker * chore(plugin-sdk): refresh API baseline * fix(secrets): canonicalize web owner contracts * fix(plugin-sdk): preserve legacy secret owner contracts * fix(secrets): satisfy startup activation types * test(secrets): align reload fixtures with owner contracts * refactor(secrets): move source recovery scope helper * fix(secrets): preserve owner contracts on web failures * fix(secrets): bind legacy web resolution contract * fix(secrets): retry stale auth publication
193 lines
5.6 KiB
TypeScript
193 lines
5.6 KiB
TypeScript
/** Integration tests for auth-profile secret runtime preparation. */
|
|
import { readFileSync } from "node:fs";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { AuthProfileStore } from "../agents/auth-profiles.js";
|
|
import { withTempHome } from "../config/home-env.test-harness.js";
|
|
import {
|
|
asConfig,
|
|
beginSecretsRuntimeIsolationForTest,
|
|
EMPTY_LOADABLE_PLUGIN_ORIGINS,
|
|
endSecretsRuntimeIsolationForTest,
|
|
OPENAI_ENV_KEY_REF,
|
|
type SecretsRuntimeEnvSnapshot,
|
|
} from "./runtime-auth.integration.test-helpers.js";
|
|
import {
|
|
activateSecretsRuntimeSnapshot,
|
|
getActiveSecretsRuntimeSnapshot,
|
|
prepareSecretsRuntimeSnapshot,
|
|
} from "./runtime.js";
|
|
|
|
vi.unmock("../version.js");
|
|
|
|
vi.mock("./runtime-prepare.runtime.js", () => ({
|
|
createResolverContext: ({
|
|
sourceConfig,
|
|
env,
|
|
}: {
|
|
sourceConfig: unknown;
|
|
env: NodeJS.ProcessEnv;
|
|
}) => ({
|
|
sourceConfig,
|
|
env,
|
|
cache: {},
|
|
warnings: [],
|
|
warningKeys: new Set<string>(),
|
|
assignments: [],
|
|
}),
|
|
collectConfigAssignments: () => {},
|
|
collectAuthStoreAssignments: ({
|
|
store,
|
|
context,
|
|
}: {
|
|
store: AuthProfileStore;
|
|
context: { env: NodeJS.ProcessEnv };
|
|
}) => {
|
|
for (const profile of Object.values(store.profiles)) {
|
|
if (
|
|
profile?.type === "api_key" &&
|
|
profile.keyRef?.source === "env" &&
|
|
typeof profile.keyRef.id === "string"
|
|
) {
|
|
const key = context.env[profile.keyRef.id];
|
|
if (typeof key === "string" && key.length > 0) {
|
|
profile.key = key;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
resolveRuntimeWebTools: async () => ({
|
|
metadata: {
|
|
search: { providerSource: "none", diagnostics: [] },
|
|
fetch: { providerSource: "none", diagnostics: [] },
|
|
diagnostics: [],
|
|
},
|
|
degradedOwners: [],
|
|
secretOwners: [],
|
|
}),
|
|
}));
|
|
|
|
vi.mock("./runtime-owner-assignments.js", () => ({
|
|
listSecretAssignmentOwners: () => [],
|
|
resolveAndApplySecretAssignments: async () => ({
|
|
degradedOwners: [],
|
|
resolvedValues: new Map(),
|
|
}),
|
|
}));
|
|
|
|
function loadAuthStoreFromTestFile(agentDir?: string): AuthProfileStore {
|
|
if (!agentDir) {
|
|
return { version: 1, profiles: {} };
|
|
}
|
|
try {
|
|
const raw = readFileSync(path.join(agentDir, "auth-profiles.json"), "utf8");
|
|
return JSON.parse(raw) as AuthProfileStore;
|
|
} catch {
|
|
return { version: 1, profiles: {} };
|
|
}
|
|
}
|
|
|
|
describe("secrets runtime snapshot auth integration", () => {
|
|
let envSnapshot: SecretsRuntimeEnvSnapshot;
|
|
|
|
beforeEach(() => {
|
|
envSnapshot = beginSecretsRuntimeIsolationForTest();
|
|
});
|
|
|
|
afterEach(() => {
|
|
endSecretsRuntimeIsolationForTest(envSnapshot);
|
|
});
|
|
|
|
it("recomputes config-derived agent dirs when refreshing active secrets runtime snapshots", async () => {
|
|
await withTempHome("openclaw-secrets-runtime-agent-dirs-", async (home) => {
|
|
const mainAgentDir = path.join(home, ".openclaw", "agents", "main", "agent");
|
|
const opsAgentDir = path.join(home, ".openclaw", "agents", "ops", "agent");
|
|
await fs.mkdir(mainAgentDir, { recursive: true });
|
|
await fs.mkdir(opsAgentDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(mainAgentDir, "auth-profiles.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
version: 1,
|
|
profiles: {
|
|
"openai:default": {
|
|
type: "api_key",
|
|
provider: "openai",
|
|
keyRef: OPENAI_ENV_KEY_REF,
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
{ encoding: "utf8", mode: 0o600 },
|
|
);
|
|
await fs.writeFile(
|
|
path.join(opsAgentDir, "auth-profiles.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
version: 1,
|
|
profiles: {
|
|
"anthropic:ops": {
|
|
type: "api_key",
|
|
provider: "anthropic",
|
|
keyRef: { source: "env", provider: "default", id: "ANTHROPIC_API_KEY" },
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
{ encoding: "utf8", mode: 0o600 },
|
|
);
|
|
|
|
const prepared = await prepareSecretsRuntimeSnapshot({
|
|
config: asConfig({}),
|
|
env: {
|
|
OPENAI_API_KEY: "sk-main-runtime",
|
|
ANTHROPIC_API_KEY: "sk-ops-runtime",
|
|
},
|
|
loadAuthStore: loadAuthStoreFromTestFile,
|
|
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
|
|
});
|
|
|
|
activateSecretsRuntimeSnapshot(prepared);
|
|
expect(
|
|
getActiveSecretsRuntimeSnapshot()?.authStores.find(
|
|
(entry) => entry.agentDir === opsAgentDir,
|
|
),
|
|
).toBeUndefined();
|
|
|
|
const refreshed = await prepareSecretsRuntimeSnapshot({
|
|
config: asConfig({
|
|
agents: {
|
|
list: [{ id: "ops", agentDir: opsAgentDir }],
|
|
},
|
|
}),
|
|
env: {
|
|
OPENAI_API_KEY: "sk-main-runtime",
|
|
ANTHROPIC_API_KEY: "sk-ops-runtime",
|
|
},
|
|
loadAuthStore: loadAuthStoreFromTestFile,
|
|
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
|
|
});
|
|
activateSecretsRuntimeSnapshot(refreshed);
|
|
|
|
const profile = getActiveSecretsRuntimeSnapshot()?.authStores.find(
|
|
(entry) => entry.agentDir === opsAgentDir,
|
|
)?.store.profiles["anthropic:ops"];
|
|
expect(profile?.type).toBe("api_key");
|
|
if (profile?.type !== "api_key") {
|
|
throw new Error("Expected anthropic ops API key profile");
|
|
}
|
|
expect(profile.key).toBe("sk-ops-runtime");
|
|
expect(profile.keyRef).toEqual({
|
|
source: "env",
|
|
provider: "default",
|
|
id: "ANTHROPIC_API_KEY",
|
|
});
|
|
});
|
|
});
|
|
});
|