diff --git a/src/auto-reply/reply/commands-tts.test.ts b/src/auto-reply/reply/commands-tts.test.ts index cfd00cabce3..e89325cc637 100644 --- a/src/auto-reply/reply/commands-tts.test.ts +++ b/src/auto-reply/reply/commands-tts.test.ts @@ -31,6 +31,8 @@ vi.mock("../../tts/provider-registry.js", () => ({ vi.mock("../../tts/tts.js", () => ttsMocks); const { handleTtsCommands } = await import("./commands-tts.js"); +const PRIMARY_TTS_PROVIDER = "acme-speech"; +const FALLBACK_TTS_PROVIDER = "backup-speech"; function buildTtsParams(commandBodyNormalized: string): Parameters[0] { return { @@ -49,7 +51,7 @@ describe("handleTtsCommands status fallback reporting", () => { ttsMocks.resolveTtsConfig.mockReturnValue({}); ttsMocks.resolveTtsPrefsPath.mockReturnValue("/tmp/tts-prefs.json"); ttsMocks.isTtsEnabled.mockReturnValue(true); - ttsMocks.getTtsProvider.mockReturnValue("elevenlabs"); + ttsMocks.getTtsProvider.mockReturnValue(PRIMARY_TTS_PROVIDER); ttsMocks.isTtsProviderConfigured.mockReturnValue(true); ttsMocks.getTtsMaxLength.mockReturnValue(1500); ttsMocks.isSummarizationEnabled.mockReturnValue(true); @@ -62,18 +64,18 @@ describe("handleTtsCommands status fallback reporting", () => { success: true, textLength: 128, summarized: false, - provider: "microsoft", - fallbackFrom: "elevenlabs", - attemptedProviders: ["elevenlabs", "microsoft"], + provider: FALLBACK_TTS_PROVIDER, + fallbackFrom: PRIMARY_TTS_PROVIDER, + attemptedProviders: [PRIMARY_TTS_PROVIDER, FALLBACK_TTS_PROVIDER], attempts: [ { - provider: "elevenlabs", + provider: PRIMARY_TTS_PROVIDER, outcome: "failed", reasonCode: "provider_error", latencyMs: 73, }, { - provider: "microsoft", + provider: FALLBACK_TTS_PROVIDER, outcome: "success", reasonCode: "success", latencyMs: 420, @@ -84,10 +86,14 @@ describe("handleTtsCommands status fallback reporting", () => { const result = await handleTtsCommands(buildTtsParams("/tts status"), true); expect(result?.shouldContinue).toBe(false); - expect(result?.reply?.text).toContain("Fallback: elevenlabs -> microsoft"); - expect(result?.reply?.text).toContain("Attempts: elevenlabs -> microsoft"); expect(result?.reply?.text).toContain( - "Attempt details: elevenlabs:failed(provider_error) 73ms, microsoft:success(ok) 420ms", + `Fallback: ${PRIMARY_TTS_PROVIDER} -> ${FALLBACK_TTS_PROVIDER}`, + ); + expect(result?.reply?.text).toContain( + `Attempts: ${PRIMARY_TTS_PROVIDER} -> ${FALLBACK_TTS_PROVIDER}`, + ); + expect(result?.reply?.text).toContain( + `Attempt details: ${PRIMARY_TTS_PROVIDER}:failed(provider_error) 73ms, ${FALLBACK_TTS_PROVIDER}:success(ok) 420ms`, ); }); @@ -98,10 +104,10 @@ describe("handleTtsCommands status fallback reporting", () => { textLength: 128, summarized: false, error: "TTS conversion failed", - attemptedProviders: ["elevenlabs", "microsoft"], + attemptedProviders: [PRIMARY_TTS_PROVIDER, FALLBACK_TTS_PROVIDER], attempts: [ { - provider: "elevenlabs", + provider: PRIMARY_TTS_PROVIDER, outcome: "failed", reasonCode: "timeout", latencyMs: 999, @@ -113,8 +119,12 @@ describe("handleTtsCommands status fallback reporting", () => { const result = await handleTtsCommands(buildTtsParams("/tts status"), true); expect(result?.shouldContinue).toBe(false); expect(result?.reply?.text).toContain("Error: TTS conversion failed"); - expect(result?.reply?.text).toContain("Attempts: elevenlabs -> microsoft"); - expect(result?.reply?.text).toContain("Attempt details: elevenlabs:failed(timeout) 999ms"); + expect(result?.reply?.text).toContain( + `Attempts: ${PRIMARY_TTS_PROVIDER} -> ${FALLBACK_TTS_PROVIDER}`, + ); + expect(result?.reply?.text).toContain( + `Attempt details: ${PRIMARY_TTS_PROVIDER}:failed(timeout) 999ms`, + ); }); it("persists fallback metadata from /tts audio and renders it in /tts status", async () => { @@ -126,18 +136,18 @@ describe("handleTtsCommands status fallback reporting", () => { ttsMocks.textToSpeech.mockResolvedValue({ success: true, audioPath: "/tmp/fallback.ogg", - provider: "microsoft", - fallbackFrom: "elevenlabs", - attemptedProviders: ["elevenlabs", "microsoft"], + provider: FALLBACK_TTS_PROVIDER, + fallbackFrom: PRIMARY_TTS_PROVIDER, + attemptedProviders: [PRIMARY_TTS_PROVIDER, FALLBACK_TTS_PROVIDER], attempts: [ { - provider: "elevenlabs", + provider: PRIMARY_TTS_PROVIDER, outcome: "failed", reasonCode: "provider_error", latencyMs: 65, }, { - provider: "microsoft", + provider: FALLBACK_TTS_PROVIDER, outcome: "success", reasonCode: "success", latencyMs: 175, @@ -153,11 +163,15 @@ describe("handleTtsCommands status fallback reporting", () => { const statusResult = await handleTtsCommands(buildTtsParams("/tts status"), true); expect(statusResult?.shouldContinue).toBe(false); - expect(statusResult?.reply?.text).toContain("Provider: microsoft"); - expect(statusResult?.reply?.text).toContain("Fallback: elevenlabs -> microsoft"); - expect(statusResult?.reply?.text).toContain("Attempts: elevenlabs -> microsoft"); + expect(statusResult?.reply?.text).toContain(`Provider: ${FALLBACK_TTS_PROVIDER}`); expect(statusResult?.reply?.text).toContain( - "Attempt details: elevenlabs:failed(provider_error) 65ms, microsoft:success(ok) 175ms", + `Fallback: ${PRIMARY_TTS_PROVIDER} -> ${FALLBACK_TTS_PROVIDER}`, + ); + expect(statusResult?.reply?.text).toContain( + `Attempts: ${PRIMARY_TTS_PROVIDER} -> ${FALLBACK_TTS_PROVIDER}`, + ); + expect(statusResult?.reply?.text).toContain( + `Attempt details: ${PRIMARY_TTS_PROVIDER}:failed(provider_error) 65ms, ${FALLBACK_TTS_PROVIDER}:success(ok) 175ms`, ); }); }); diff --git a/src/cli/command-secret-gateway.test.ts b/src/cli/command-secret-gateway.test.ts index f69637c94f2..d56aae2efef 100644 --- a/src/cli/command-secret-gateway.test.ts +++ b/src/cli/command-secret-gateway.test.ts @@ -1,5 +1,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import { + buildTalkTestProviderConfig, + readTalkTestProviderApiKey as readTalkProviderApiKey, + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS, +} from "../test-utils/talk-test-provider.js"; import { resolveCommandSecretRefsViaGateway } from "./command-secret-gateway.js"; const mocks = vi.hoisted(() => ({ @@ -27,19 +33,7 @@ beforeEach(() => { describe("resolveCommandSecretRefsViaGateway", () => { function makeTalkProviderApiKeySecretRefConfig(envKey: string): OpenClawConfig { - return { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: envKey }, - }, - }, - }, - } as unknown as OpenClawConfig; - } - - function readTalkProviderApiKey(config: OpenClawConfig): unknown { - return config.talk?.providers?.elevenlabs?.apiKey; + return buildTalkTestProviderConfig({ source: "env", provider: "default", id: envKey }); } async function withEnvValue( @@ -101,13 +95,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { it("returns config unchanged when no target SecretRefs are configured", async () => { const config = { - talk: { - providers: { - elevenlabs: { - apiKey: "plain", // pragma: allowlist secret - }, - }, - }, + ...buildTalkTestProviderConfig("plain"), // pragma: allowlist secret } as unknown as OpenClawConfig; const result = await resolveCommandSecretRefsViaGateway({ config, @@ -152,22 +140,18 @@ describe("resolveCommandSecretRefsViaGateway", () => { callGateway.mockResolvedValueOnce({ assignments: [ { - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], value: "sk-live", }, ], diagnostics: [], }); - const config = { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, - } as OpenClawConfig; + const config = buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: "TALK_API_KEY", + }); const result = await resolveCommandSecretRefsViaGateway({ config, commandName: "memory status", @@ -234,15 +218,11 @@ describe("resolveCommandSecretRefsViaGateway", () => { try { await expect( resolveCommandSecretRefsViaGateway({ - config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: envKey }, - }, - }, - }, - } as unknown as OpenClawConfig, + config: buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: envKey, + }), commandName: "memory status", targetIds: new Set(["talk.providers.*.apiKey"]), }), @@ -263,13 +243,11 @@ describe("resolveCommandSecretRefsViaGateway", () => { try { const result = await resolveCommandSecretRefsViaGateway({ config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, + ...buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: "TALK_API_KEY", + }), secrets: { providers: { default: { source: "env" }, @@ -452,15 +430,11 @@ describe("resolveCommandSecretRefsViaGateway", () => { }); await expect( resolveCommandSecretRefsViaGateway({ - config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, - } as unknown as OpenClawConfig, + config: buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: "TALK_API_KEY", + }), commandName: "memory status", targetIds: new Set(["talk.providers.*.apiKey"]), }), @@ -471,8 +445,8 @@ describe("resolveCommandSecretRefsViaGateway", () => { callGateway.mockResolvedValueOnce({ assignments: [ { - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], value: "sk-live", }, ], @@ -480,15 +454,11 @@ describe("resolveCommandSecretRefsViaGateway", () => { }); await expect( resolveCommandSecretRefsViaGateway({ - config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, - } as OpenClawConfig, + config: buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: "TALK_API_KEY", + }), commandName: "memory status", targetIds: new Set(["talk.providers.*.apiKey"]), }), @@ -504,7 +474,10 @@ describe("resolveCommandSecretRefsViaGateway", () => { await withEnvValue(envKey, undefined, async () => { await expect(resolveTalkProviderApiKey({ envKey })).rejects.toThrow( - /talk\.providers\.elevenlabs\.apiKey is unresolved in the active runtime snapshot/i, + new RegExp( + `${TALK_TEST_PROVIDER_API_KEY_PATH.replaceAll(".", "\\.")} is unresolved in the active runtime snapshot`, + "i", + ), ); }); }); @@ -513,7 +486,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { callGateway.mockResolvedValueOnce({ assignments: [], diagnostics: [ - "talk.providers.elevenlabs.apiKey: secret ref is configured on an inactive surface; skipping command-time assignment.", + `${TALK_TEST_PROVIDER_API_KEY_PATH}: secret ref is configured on an inactive surface; skipping command-time assignment.`, ], }); @@ -521,7 +494,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { expectTalkProviderApiKeySecretRef(result, "TALK_API_KEY"); expect(result.diagnostics).toEqual([ - "talk.providers.elevenlabs.apiKey: secret ref is configured on an inactive surface; skipping command-time assignment.", + `${TALK_TEST_PROVIDER_API_KEY_PATH}: secret ref is configured on an inactive surface; skipping command-time assignment.`, ]); }); @@ -529,7 +502,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { callGateway.mockResolvedValueOnce({ assignments: [], diagnostics: ["talk api key inactive"], - inactiveRefPaths: ["talk.providers.elevenlabs.apiKey"], + inactiveRefPaths: [TALK_TEST_PROVIDER_API_KEY_PATH], }); const result = await resolveTalkProviderApiKey({ envKey: "TALK_API_KEY" }); @@ -588,10 +561,10 @@ describe("resolveCommandSecretRefsViaGateway", () => { }); expect(readTalkProviderApiKey(result.resolvedConfig)).toBeUndefined(); expect(result.hadUnresolvedTargets).toBe(true); - expect(result.targetStatesByPath["talk.providers.elevenlabs.apiKey"]).toBe("unresolved"); + expect(result.targetStatesByPath[TALK_TEST_PROVIDER_API_KEY_PATH]).toBe("unresolved"); expect( result.diagnostics.some((entry) => - entry.includes("talk.providers.elevenlabs.apiKey is unavailable in this command path"), + entry.includes(`${TALK_TEST_PROVIDER_API_KEY_PATH} is unavailable in this command path`), ), ).toBe(true); }); @@ -612,7 +585,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { }); expect(readTalkProviderApiKey(result.resolvedConfig)).toBeUndefined(); expect(result.hadUnresolvedTargets).toBe(true); - expect(result.targetStatesByPath["talk.providers.elevenlabs.apiKey"]).toBe("unresolved"); + expect(result.targetStatesByPath[TALK_TEST_PROVIDER_API_KEY_PATH]).toBe("unresolved"); }); }); @@ -630,7 +603,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { }); expect(readTalkProviderApiKey(result.resolvedConfig)).toBe("recovered-locally"); expect(result.hadUnresolvedTargets).toBe(false); - expect(result.targetStatesByPath["talk.providers.elevenlabs.apiKey"]).toBe("resolved_local"); + expect(result.targetStatesByPath[TALK_TEST_PROVIDER_API_KEY_PATH]).toBe("resolved_local"); expect( result.diagnostics.some((entry) => entry.includes( @@ -648,8 +621,8 @@ describe("resolveCommandSecretRefsViaGateway", () => { callGateway.mockResolvedValueOnce({ assignments: [ { - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], value: "resolved-by-gateway", }, ], @@ -658,24 +631,18 @@ describe("resolveCommandSecretRefsViaGateway", () => { try { const result = await resolveCommandSecretRefsViaGateway({ - config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: locallyRecoveredKey }, - }, - }, - }, - } as unknown as OpenClawConfig, + config: buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: locallyRecoveredKey, + }), commandName: "message send", targetIds: new Set(["talk.providers.*.apiKey"]), }); expect(readTalkProviderApiKey(result.resolvedConfig)).toBe("resolved-by-gateway"); expect(result.hadUnresolvedTargets).toBe(false); - expect(result.targetStatesByPath["talk.providers.elevenlabs.apiKey"]).toBe( - "resolved_gateway", - ); + expect(result.targetStatesByPath[TALK_TEST_PROVIDER_API_KEY_PATH]).toBe("resolved_gateway"); } finally { if (priorLocallyRecoveredValue === undefined) { delete process.env[locallyRecoveredKey]; @@ -697,13 +664,11 @@ describe("resolveCommandSecretRefsViaGateway", () => { try { const result = await resolveCommandSecretRefsViaGateway({ config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: talkEnvKey }, - }, - }, - }, + ...buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: talkEnvKey, + }), gateway: { auth: { password: { source: "env", provider: "default", id: gatewayEnvKey }, @@ -717,7 +682,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { expect(readTalkProviderApiKey(result.resolvedConfig)).toBe("target-only"); expect(result.hadUnresolvedTargets).toBe(false); - expect(result.targetStatesByPath["talk.providers.elevenlabs.apiKey"]).toBe("resolved_local"); + expect(result.targetStatesByPath[TALK_TEST_PROVIDER_API_KEY_PATH]).toBe("resolved_local"); } finally { if (priorTalkValue === undefined) { delete process.env[talkEnvKey]; @@ -740,15 +705,11 @@ describe("resolveCommandSecretRefsViaGateway", () => { try { const result = await resolveCommandSecretRefsViaGateway({ - config: { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: envKey }, - }, - }, - }, - } as unknown as OpenClawConfig, + config: buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: envKey, + }), commandName: "channels resolve", targetIds: new Set(["talk.providers.*.apiKey"]), mode: "read_only_operational", @@ -756,7 +717,7 @@ describe("resolveCommandSecretRefsViaGateway", () => { expect(readTalkProviderApiKey(result.resolvedConfig)).toBeUndefined(); expect(result.hadUnresolvedTargets).toBe(true); - expect(result.targetStatesByPath["talk.providers.elevenlabs.apiKey"]).toBe("unresolved"); + expect(result.targetStatesByPath[TALK_TEST_PROVIDER_API_KEY_PATH]).toBe("unresolved"); expect( result.diagnostics.some((entry) => entry.includes("attempted local command-secret resolution"), diff --git a/src/config/talk.normalize.test.ts b/src/config/talk.normalize.test.ts index 4e84103b237..03cbeee2fca 100644 --- a/src/config/talk.normalize.test.ts +++ b/src/config/talk.normalize.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { TALK_TEST_PROVIDER_ID } from "../test-utils/talk-test-provider.js"; import { createConfigIO } from "./io.js"; import { buildTalkConfigResponse, normalizeTalkSection } from "./talk.js"; @@ -94,18 +95,18 @@ describe("talk normalization", () => { it("preserves SecretRef apiKey values during normalization", () => { const normalized = normalizeTalkSection({ - provider: "elevenlabs", + provider: TALK_TEST_PROVIDER_ID, providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: { source: "env", provider: "default", id: "ELEVENLABS_API_KEY" }, }, }, }); expect(normalized).toEqual({ - provider: "elevenlabs", + provider: TALK_TEST_PROVIDER_ID, providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: { source: "env", provider: "default", id: "ELEVENLABS_API_KEY" }, }, }, diff --git a/src/gateway/protocol/index.test.ts b/src/gateway/protocol/index.test.ts index fa65ed01a0b..97a97e6cbf1 100644 --- a/src/gateway/protocol/index.test.ts +++ b/src/gateway/protocol/index.test.ts @@ -1,5 +1,6 @@ import type { ErrorObject } from "ajv"; import { describe, expect, it } from "vitest"; +import { TALK_TEST_PROVIDER_ID } from "../../test-utils/talk-test-provider.js"; import { formatValidationErrors, validateTalkConfigResult } from "./index.js"; const makeError = (overrides: Partial): ErrorObject => ({ @@ -69,9 +70,9 @@ describe("validateTalkConfigResult", () => { validateTalkConfigResult({ config: { talk: { - provider: "elevenlabs", + provider: TALK_TEST_PROVIDER_ID, providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: { source: "env", provider: "default", @@ -80,7 +81,7 @@ describe("validateTalkConfigResult", () => { }, }, resolved: { - provider: "elevenlabs", + provider: TALK_TEST_PROVIDER_ID, config: { apiKey: { source: "env", @@ -100,9 +101,9 @@ describe("validateTalkConfigResult", () => { validateTalkConfigResult({ config: { talk: { - provider: "elevenlabs", + provider: TALK_TEST_PROVIDER_ID, providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { voiceId: "voice-normalized", }, }, diff --git a/src/gateway/server-methods/secrets.test.ts b/src/gateway/server-methods/secrets.test.ts index 4b39b9bc131..5f616b612df 100644 --- a/src/gateway/server-methods/secrets.test.ts +++ b/src/gateway/server-methods/secrets.test.ts @@ -1,4 +1,8 @@ import { describe, expect, it, vi } from "vitest"; +import { + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS, +} from "../../test-utils/talk-test-provider.js"; import { createSecretsHandlers } from "./secrets.js"; async function invokeSecretsReload(params: { @@ -90,13 +94,13 @@ describe("secrets handlers", () => { const resolveSecrets = vi.fn().mockResolvedValue({ assignments: [ { - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], value: "sk", }, ], diagnostics: ["note"], - inactiveRefPaths: ["talk.providers.elevenlabs.apiKey"], + inactiveRefPaths: [TALK_TEST_PROVIDER_API_KEY_PATH], }); const handlers = createHandlers({ resolveSecrets }); const respond = vi.fn(); @@ -114,13 +118,13 @@ describe("secrets handlers", () => { ok: true, assignments: [ { - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], value: "sk", }, ], diagnostics: ["note"], - inactiveRefPaths: ["talk.providers.elevenlabs.apiKey"], + inactiveRefPaths: [TALK_TEST_PROVIDER_API_KEY_PATH], }); }); @@ -186,7 +190,7 @@ describe("secrets handlers", () => { it("returns unavailable when secrets.resolve handler returns an invalid payload shape", async () => { const resolveSecrets = vi.fn().mockResolvedValue({ - assignments: [{ path: "talk.providers.elevenlabs.apiKey", pathSegments: [""], value: "sk" }], + assignments: [{ path: TALK_TEST_PROVIDER_API_KEY_PATH, pathSegments: [""], value: "sk" }], diagnostics: [], inactiveRefPaths: [], }); diff --git a/src/gateway/server.reload.test.ts b/src/gateway/server.reload.test.ts index aad53eb24fd..007949a49e6 100644 --- a/src/gateway/server.reload.test.ts +++ b/src/gateway/server.reload.test.ts @@ -3,6 +3,10 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { resolveMainSessionKeyFromConfig } from "../config/sessions.js"; import { drainSystemEvents } from "../infra/system-events.js"; +import { + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_ID, +} from "../test-utils/talk-test-provider.js"; import { openTrackedWs } from "./device-authz.test-helpers.js"; import { connectReq, @@ -237,7 +241,7 @@ describe("gateway hot reload", () => { await writeConfigFile({ talk: { providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: { source: "env", provider: "default", id: refId }, }, }, @@ -769,7 +773,7 @@ describe("gateway hot reload", () => { targetIds: ["talk.providers.*.apiKey"], }); expect(preResolve.ok).toBe(true); - expect(preResolve.payload?.assignments?.[0]?.path).toBe("talk.providers.elevenlabs.apiKey"); + expect(preResolve.payload?.assignments?.[0]?.path).toBe(TALK_TEST_PROVIDER_API_KEY_PATH); expect(preResolve.payload?.assignments?.[0]?.value).toBe("talk-key-before-reload-failure"); delete process.env[refId]; @@ -785,7 +789,7 @@ describe("gateway hot reload", () => { targetIds: ["talk.providers.*.apiKey"], }); expect(postResolve.ok).toBe(true); - expect(postResolve.payload?.assignments?.[0]?.path).toBe("talk.providers.elevenlabs.apiKey"); + expect(postResolve.payload?.assignments?.[0]?.path).toBe(TALK_TEST_PROVIDER_API_KEY_PATH); expect(postResolve.payload?.assignments?.[0]?.value).toBe("talk-key-before-reload-failure"); } finally { if (previousRefValue === undefined) { diff --git a/src/secrets/apply.test.ts b/src/secrets/apply.test.ts index 41905d6c703..b78ed0e24e4 100644 --- a/src/secrets/apply.test.ts +++ b/src/secrets/apply.test.ts @@ -2,6 +2,11 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS, + TALK_TEST_PROVIDER_ID, +} from "../test-utils/talk-test-provider.js"; import { runSecretsApply } from "./apply.js"; import type { SecretsApplyPlan } from "./plan.js"; import { clearSecretsRuntimeSnapshot } from "./runtime.js"; @@ -526,7 +531,7 @@ describe("secrets apply", () => { { talk: { providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: "sk-talk-plaintext", // pragma: allowlist secret }, }, @@ -546,8 +551,8 @@ describe("secrets apply", () => { targets: [ { type: "talk.providers.*.apiKey", - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], ref: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, }, ], @@ -562,9 +567,9 @@ describe("secrets apply", () => { expect(result.changed).toBe(true); const nextConfig = JSON.parse(await fs.readFile(fixture.configPath, "utf8")) as { - talk?: { providers?: { elevenlabs?: { apiKey?: unknown } } }; + talk?: { providers?: Record }; }; - expect(nextConfig.talk?.providers?.elevenlabs?.apiKey).toEqual({ + expect(nextConfig.talk?.providers?.[TALK_TEST_PROVIDER_ID]?.apiKey).toEqual({ source: "env", provider: "default", id: "OPENAI_API_KEY", diff --git a/src/secrets/command-config.test.ts b/src/secrets/command-config.test.ts index ed99d0c5fb0..3c4131d5372 100644 --- a/src/secrets/command-config.test.ts +++ b/src/secrets/command-config.test.ts @@ -1,27 +1,20 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import { + buildTalkTestProviderConfig, + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS, +} from "../test-utils/talk-test-provider.js"; import { collectCommandSecretAssignmentsFromSnapshot } from "./command-config.js"; describe("collectCommandSecretAssignmentsFromSnapshot", () => { it("returns assignments from the active runtime snapshot for configured refs", () => { - const sourceConfig = { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, - } as unknown as OpenClawConfig; - const resolvedConfig = { - talk: { - providers: { - elevenlabs: { - apiKey: "talk-key", // pragma: allowlist secret - }, - }, - }, - } as unknown as OpenClawConfig; + const sourceConfig = buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: "TALK_API_KEY", + }); + const resolvedConfig = buildTalkTestProviderConfig("talk-key"); // pragma: allowlist secret const result = collectCommandSecretAssignmentsFromSnapshot({ sourceConfig, @@ -32,30 +25,20 @@ describe("collectCommandSecretAssignmentsFromSnapshot", () => { expect(result.assignments).toEqual([ { - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], value: "talk-key", }, ]); }); it("throws when configured refs are unresolved in the snapshot", () => { - const sourceConfig = { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, - } as unknown as OpenClawConfig; - const resolvedConfig = { - talk: { - providers: { - elevenlabs: {}, - }, - }, - } as unknown as OpenClawConfig; + const sourceConfig = buildTalkTestProviderConfig({ + source: "env", + provider: "default", + id: "TALK_API_KEY", + }); + const resolvedConfig = buildTalkTestProviderConfig(undefined); expect(() => collectCommandSecretAssignmentsFromSnapshot({ @@ -64,9 +47,7 @@ describe("collectCommandSecretAssignmentsFromSnapshot", () => { commandName: "memory search", targetIds: new Set(["talk.providers.*.apiKey"]), }), - ).toThrow( - /memory search: talk\.providers\.elevenlabs\.apiKey is unresolved in the active runtime snapshot/, - ); + ).toThrow(new RegExp(`memory search: ${TALK_TEST_PROVIDER_API_KEY_PATH} is unresolved`)); }); it("skips unresolved refs that are marked inactive by runtime warnings", () => { diff --git a/src/secrets/configure-plan.test.ts b/src/secrets/configure-plan.test.ts index 40d193c2ec4..675cf66a044 100644 --- a/src/secrets/configure-plan.test.ts +++ b/src/secrets/configure-plan.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import { + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_ID, +} from "../test-utils/talk-test-provider.js"; import { buildConfigureCandidates, buildConfigureCandidatesForScope, @@ -13,7 +17,7 @@ describe("secrets configure plan helpers", () => { const config = { talk: { providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: "plain", // pragma: allowlist secret }, }, @@ -27,7 +31,7 @@ describe("secrets configure plan helpers", () => { const candidates = buildConfigureCandidates(config); const paths = candidates.map((entry) => entry.path); - expect(paths).toContain("talk.providers.elevenlabs.apiKey"); + expect(paths).toContain(TALK_TEST_PROVIDER_API_KEY_PATH); expect(paths).toContain("channels.telegram.botToken"); }); @@ -89,7 +93,7 @@ describe("secrets configure plan helpers", () => { config: { talk: { providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: { source: "env", provider: "default", @@ -121,7 +125,7 @@ describe("secrets configure plan helpers", () => { expect(candidates).toEqual( expect.arrayContaining([ expect.objectContaining({ - path: "talk.providers.elevenlabs.apiKey", + path: TALK_TEST_PROVIDER_API_KEY_PATH, existingRef: { source: "env", provider: "default", @@ -144,9 +148,9 @@ describe("secrets configure plan helpers", () => { const candidates = buildConfigureCandidatesForScope({ config: { talk: { - provider: "elevenlabs", + provider: TALK_TEST_PROVIDER_ID, providers: { - elevenlabs: { + [TALK_TEST_PROVIDER_ID]: { apiKey: "demo-talk-key", // pragma: allowlist secret }, }, @@ -160,24 +164,22 @@ describe("secrets configure plan helpers", () => { } as OpenClawConfig, }); - const normalized = candidates.find( - (entry) => entry.path === "talk.providers.elevenlabs.apiKey", - ); + const normalized = candidates.find((entry) => entry.path === TALK_TEST_PROVIDER_API_KEY_PATH); expect(normalized?.isDerived).toBe(true); }); it("reports configure change presence and builds deterministic plan shape", () => { const selected = new Map([ [ - "talk.providers.elevenlabs.apiKey", + TALK_TEST_PROVIDER_API_KEY_PATH, { type: "talk.providers.*.apiKey", - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], - label: "talk.providers.elevenlabs.apiKey", + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: ["talk", "providers", TALK_TEST_PROVIDER_ID, "apiKey"], + label: TALK_TEST_PROVIDER_API_KEY_PATH, configFile: "openclaw.json" as const, expectedResolvedValue: "string" as const, - providerId: "elevenlabs", + providerId: TALK_TEST_PROVIDER_ID, ref: { source: "env" as const, provider: "default", @@ -205,7 +207,7 @@ describe("secrets configure plan helpers", () => { generatedAt: "2026-02-28T00:00:00.000Z", }); expect(plan.targets).toHaveLength(1); - expect(plan.targets[0]?.path).toBe("talk.providers.elevenlabs.apiKey"); + expect(plan.targets[0]?.path).toBe(TALK_TEST_PROVIDER_API_KEY_PATH); expect(plan.providerUpserts).toBeDefined(); expect(plan.options).toEqual({ scrubEnv: true, diff --git a/src/secrets/exec-secret-ref-id-parity.test.ts b/src/secrets/exec-secret-ref-id-parity.test.ts index f9099c9d6d8..7cd539dc9b1 100644 --- a/src/secrets/exec-secret-ref-id-parity.test.ts +++ b/src/secrets/exec-secret-ref-id-parity.test.ts @@ -7,6 +7,11 @@ import { INVALID_EXEC_SECRET_REF_IDS, VALID_EXEC_SECRET_REF_IDS, } from "../test-utils/secret-ref-test-vectors.js"; +import { + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS, + TALK_TEST_PROVIDER_ID, +} from "../test-utils/talk-test-provider.js"; import { isSecretsApplyPlan } from "./plan.js"; import { isValidExecSecretRefId } from "./ref-contract.js"; import { materializePathTokens, parsePathPattern } from "./target-registry-pattern.js"; @@ -43,9 +48,9 @@ describe("exec SecretRef id parity", () => { targets: [ { type: "talk.providers.*.apiKey", - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], - providerId: "elevenlabs", + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], + providerId: TALK_TEST_PROVIDER_ID, ref: { source: "exec", provider: "vault", id }, }, ], diff --git a/src/secrets/plan.test.ts b/src/secrets/plan.test.ts index 165494bfe4e..d74fca86bec 100644 --- a/src/secrets/plan.test.ts +++ b/src/secrets/plan.test.ts @@ -3,6 +3,11 @@ import { INVALID_EXEC_SECRET_REF_IDS, VALID_EXEC_SECRET_REF_IDS, } from "../test-utils/secret-ref-test-vectors.js"; +import { + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS, + TALK_TEST_PROVIDER_ID, +} from "../test-utils/talk-test-provider.js"; import { isSecretsApplyPlan, resolveValidatedPlanTarget } from "./plan.js"; describe("secrets plan validation", () => { @@ -59,9 +64,9 @@ describe("secrets plan validation", () => { targets: [ { type: "talk.providers.*.apiKey", - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], - providerId: "elevenlabs", + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], + providerId: TALK_TEST_PROVIDER_ID, ref: { source: "env", provider: "default", id: "TALK_API_KEY" }, }, ], @@ -114,9 +119,9 @@ describe("secrets plan validation", () => { targets: [ { type: "talk.providers.*.apiKey", - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], - providerId: "elevenlabs", + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], + providerId: TALK_TEST_PROVIDER_ID, ref: { source: "exec", provider: "vault", id }, }, ], @@ -135,9 +140,9 @@ describe("secrets plan validation", () => { targets: [ { type: "talk.providers.*.apiKey", - path: "talk.providers.elevenlabs.apiKey", - pathSegments: ["talk", "providers", "elevenlabs", "apiKey"], - providerId: "elevenlabs", + path: TALK_TEST_PROVIDER_API_KEY_PATH, + pathSegments: [...TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS], + providerId: TALK_TEST_PROVIDER_ID, ref: { source: "exec", provider: "vault", id }, }, ], diff --git a/src/secrets/target-registry.test.ts b/src/secrets/target-registry.test.ts index e594557c820..f6b1bc7aa85 100644 --- a/src/secrets/target-registry.test.ts +++ b/src/secrets/target-registry.test.ts @@ -2,6 +2,11 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import { + buildTalkTestProviderConfig, + TALK_TEST_PROVIDER_API_KEY_PATH, + TALK_TEST_PROVIDER_ID, +} from "../test-utils/talk-test-provider.js"; import { buildSecretRefCredentialMatrix } from "./credential-matrix.js"; import { discoverConfigSecretTargetsByIds, @@ -83,13 +88,7 @@ describe("secret target registry", () => { it("supports filtered discovery by target ids", () => { const targets = discoverConfigSecretTargetsByIds( { - talk: { - providers: { - elevenlabs: { - apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, - }, - }, - }, + ...buildTalkTestProviderConfig({ source: "env", provider: "default", id: "TALK_API_KEY" }), gateway: { remote: { token: { source: "env", provider: "default", id: "REMOTE_TOKEN" }, @@ -101,7 +100,8 @@ describe("secret target registry", () => { expect(targets).toHaveLength(1); expect(targets[0]?.entry.id).toBe("talk.providers.*.apiKey"); - expect(targets[0]?.path).toBe("talk.providers.elevenlabs.apiKey"); + expect(targets[0]?.providerId).toBe(TALK_TEST_PROVIDER_ID); + expect(targets[0]?.path).toBe(TALK_TEST_PROVIDER_API_KEY_PATH); }); it("resolves config targets by exact path including sibling ref metadata", () => {