Files
openclaw/src/secrets/runtime.auth.integration.test.ts
Peter Steinberger 8a5c8690e1 fix(secrets): keep unaffected owners live during reload failures (#110779)
* 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
2026-07-18 18:29:24 +01:00

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",
});
});
});
});