Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ConfigFileSnapshot, OpenClawConfig } from "../config/types.js";
import type { PreparedSecretsRuntimeSnapshot, SecretResolverWarning } from "../secrets/runtime.js";
import { KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS } from "./known-weak-gateway-secrets.js";
import {
createRuntimeSecretsActivator,
prepareGatewayStartupConfig,
} from "./server-startup-config.js";
import { buildTestConfigSnapshot } from "./test-helpers.config-snapshots.js";
function gatewayTokenConfig(config: OpenClawConfig): OpenClawConfig {
return {
...config,
gateway: {
...config.gateway,
auth: {
...config.gateway?.auth,
mode: config.gateway?.auth?.mode ?? "token",
token: config.gateway?.auth?.token ?? "startup-test-token",
},
},
};
}
function asConfig(value: unknown): OpenClawConfig {
return value as OpenClawConfig;
}
function buildSnapshot(config: OpenClawConfig): ConfigFileSnapshot {
const raw = `${JSON.stringify(config, null, 2)}\n`;
return buildTestConfigSnapshot({
path: "/tmp/openclaw-startup-secrets-test.json",
exists: true,
raw,
parsed: config,
valid: true,
config,
issues: [],
legacyIssues: [],
});
}
function preparedSnapshot(config: OpenClawConfig): PreparedSecretsRuntimeSnapshot {
return {
sourceConfig: config,
config,
authStores: [],
warnings: [],
webTools: {
search: {
providerSource: "none",
diagnostics: [],
},
fetch: {
providerSource: "none",
diagnostics: [],
},
diagnostics: [],
},
};
}
describe("gateway startup config secret preflight", () => {
const previousSkipChannels = process.env.OPENCLAW_SKIP_CHANNELS;
const previousSkipProviders = process.env.OPENCLAW_SKIP_PROVIDERS;
afterEach(() => {
if (previousSkipChannels === undefined) {
delete process.env.OPENCLAW_SKIP_CHANNELS;
} else {
process.env.OPENCLAW_SKIP_CHANNELS = previousSkipChannels;
}
if (previousSkipProviders === undefined) {
delete process.env.OPENCLAW_SKIP_PROVIDERS;
} else {
process.env.OPENCLAW_SKIP_PROVIDERS = previousSkipProviders;
}
});
it("wraps startup secret activation failures without emitting reload state events", async () => {
const error = new Error('Environment variable "OPENAI_API_KEY" is missing or empty.');
const prepareRuntimeSecretsSnapshot = vi.fn(async () => {
throw error;
});
const emitStateEvent = vi.fn();
const activateRuntimeSecrets = createRuntimeSecretsActivator({
logSecrets: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
emitStateEvent,
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot: vi.fn(),
});
await expect(
activateRuntimeSecrets(gatewayTokenConfig({}), {
reason: "startup",
activate: false,
}),
).rejects.toThrow(
'Startup failed: required secrets are unavailable. Error: Environment variable "OPENAI_API_KEY" is missing or empty.',
);
expect(emitStateEvent).not.toHaveBeenCalled();
});
it("does not emit degraded or recovered events for warning-only secret reloads", async () => {
const warning: SecretResolverWarning = {
code: "WEB_SEARCH_KEY_UNRESOLVED_FALLBACK_USED",
path: "plugins.entries.google.config.webSearch.apiKey",
message: "web search provider fell back to environment credentials",
};
const prepareRuntimeSecretsSnapshot = vi.fn(async ({ config }) => ({
...preparedSnapshot(config),
warnings: [warning],
}));
const emitStateEvent = vi.fn();
const logSecrets = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const activateRuntimeSecrets = createRuntimeSecretsActivator({
logSecrets,
emitStateEvent,
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot: vi.fn(),
});
await expect(
activateRuntimeSecrets(
{
plugins: {
entries: {
google: {
enabled: true,
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: "MISSING_GEMINI_KEY" },
},
},
},
},
},
},
{
reason: "reload",
activate: true,
},
),
).resolves.toMatchObject({
warnings: [warning],
});
expect(logSecrets.warn).toHaveBeenCalledWith(
"[WEB_SEARCH_KEY_UNRESOLVED_FALLBACK_USED] web search provider fell back to environment credentials",
);
expect(emitStateEvent).not.toHaveBeenCalled();
});
it.each(KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS)(
"rejects known weak gateway tokens resolved during secret activation: %s",
async (token) => {
const sourceConfig = gatewayTokenConfig({
secrets: {
providers: {
default: { source: "env" },
},
},
gateway: {
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "GATEWAY_TOKEN_REF" },
},
},
});
const prepareRuntimeSecretsSnapshot = vi.fn(async () =>
preparedSnapshot({
...sourceConfig,
gateway: {
...sourceConfig.gateway,
auth: {
...sourceConfig.gateway?.auth,
token,
},
},
}),
);
const activateRuntimeSecretsSnapshot = vi.fn();
const activateRuntimeSecrets = createRuntimeSecretsActivator({
logSecrets: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
emitStateEvent: vi.fn(),
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot,
});
await expect(
activateRuntimeSecrets(sourceConfig, {
reason: "reload",
activate: true,
}),
).rejects.toThrow(/published example placeholder/);
expect(activateRuntimeSecretsSnapshot).not.toHaveBeenCalled();
},
);
it("prunes channel refs from startup secret preflight when channels are skipped", async () => {
process.env.OPENCLAW_SKIP_CHANNELS = "1";
const prepareRuntimeSecretsSnapshot = vi.fn(async ({ config }) => preparedSnapshot(config));
const activateRuntimeSecrets = createRuntimeSecretsActivator({
logSecrets: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
emitStateEvent: vi.fn(),
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot: vi.fn(),
});
const config = gatewayTokenConfig(
asConfig({
channels: {
telegram: {
botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN" },
},
},
}),
);
await expect(
activateRuntimeSecrets(config, {
reason: "startup",
activate: false,
}),
).resolves.toMatchObject({
config: expect.objectContaining({
gateway: expect.any(Object),
}),
});
expect(prepareRuntimeSecretsSnapshot).toHaveBeenCalledWith({
config: expect.not.objectContaining({
channels: expect.anything(),
}),
});
});
it("honors startup auth overrides before secret preflight gating", async () => {
const prepareRuntimeSecretsSnapshot = vi.fn(async ({ config }) => preparedSnapshot(config));
const activateRuntimeSecretsSnapshot = vi.fn();
const result = await prepareGatewayStartupConfig({
configSnapshot: buildSnapshot({
secrets: {
providers: {
default: { source: "env" },
},
},
gateway: {
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "MISSING_STARTUP_GW_TOKEN" },
},
},
}),
authOverride: {
mode: "password",
password: "override-password", // pragma: allowlist secret
},
activateRuntimeSecrets: createRuntimeSecretsActivator({
logSecrets: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
emitStateEvent: vi.fn(),
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot,
}),
});
expect(result.auth).toMatchObject({
mode: "password",
password: "override-password",
});
expect(prepareRuntimeSecretsSnapshot).toHaveBeenNthCalledWith(1, {
config: expect.objectContaining({
gateway: expect.objectContaining({
auth: expect.objectContaining({
mode: "password",
password: "override-password",
}),
}),
}),
});
expect(activateRuntimeSecretsSnapshot).toHaveBeenCalledTimes(1);
});
it("skips inactive gateway auth secret preflight when auth has plain strings", async () => {
const prepareRuntimeSecretsSnapshot = vi.fn(async ({ config }) => preparedSnapshot(config));
const result = await prepareGatewayStartupConfig({
configSnapshot: buildSnapshot(gatewayTokenConfig({})),
activateRuntimeSecrets: createRuntimeSecretsActivator({
logSecrets: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
emitStateEvent: vi.fn(),
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot: vi.fn(),
}),
});
expect(result.auth).toMatchObject({
mode: "token",
token: "startup-test-token",
});
expect(prepareRuntimeSecretsSnapshot).toHaveBeenCalledTimes(1);
expect(prepareRuntimeSecretsSnapshot).toHaveBeenCalledWith({
config: expect.objectContaining({
gateway: expect.objectContaining({
auth: expect.objectContaining({
token: "startup-test-token",
}),
}),
}),
});
});
it("uses gateway auth strings resolved during startup preflight for bootstrap auth", async () => {
const prepareRuntimeSecretsSnapshot = vi.fn(async ({ config }) =>
preparedSnapshot({
...config,
gateway: {
...config.gateway,
auth: {
...config.gateway?.auth,
token: "resolved-gateway-token",
},
},
}),
);
const result = await prepareGatewayStartupConfig({
configSnapshot: buildSnapshot({
secrets: {
providers: {
default: { source: "env" },
},
},
gateway: {
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "GATEWAY_TOKEN_REF" },
},
},
}),
activateRuntimeSecrets: createRuntimeSecretsActivator({
logSecrets: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
emitStateEvent: vi.fn(),
prepareRuntimeSecretsSnapshot,
activateRuntimeSecretsSnapshot: vi.fn(),
}),
});
expect(result.auth).toMatchObject({
mode: "token",
token: "resolved-gateway-token",
});
expect(prepareRuntimeSecretsSnapshot).toHaveBeenCalledTimes(2);
});
});
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|