import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { validateProviderConfig, normalizeVoiceCallConfig, resolveVoiceCallConfig, type VoiceCallConfig, } from "./config.js"; import { createVoiceCallBaseConfig } from "./test-fixtures.js"; function createBaseConfig(provider: "telnyx" | "twilio" | "plivo" | "mock"): VoiceCallConfig { return createVoiceCallBaseConfig({ provider }); } describe("validateProviderConfig", () => { const originalEnv = { ...process.env }; const clearProviderEnv = () => { delete process.env.TWILIO_ACCOUNT_SID; delete process.env.TWILIO_AUTH_TOKEN; delete process.env.TELNYX_API_KEY; delete process.env.TELNYX_CONNECTION_ID; delete process.env.TELNYX_PUBLIC_KEY; delete process.env.PLIVO_AUTH_ID; delete process.env.PLIVO_AUTH_TOKEN; }; beforeEach(() => { clearProviderEnv(); }); afterEach(() => { // Restore original env process.env = { ...originalEnv }; }); describe("provider credential sources", () => { it("passes validation when credentials come from config or environment", () => { for (const provider of ["twilio", "telnyx", "plivo"] as const) { clearProviderEnv(); const fromConfig = createBaseConfig(provider); if (provider === "twilio") { fromConfig.twilio = { accountSid: "AC123", authToken: "secret" }; } else if (provider === "telnyx") { fromConfig.telnyx = { apiKey: "KEY123", connectionId: "CONN456", publicKey: "public-key", }; } else { fromConfig.plivo = { authId: "MA123", authToken: "secret" }; } expect(validateProviderConfig(fromConfig)).toMatchObject({ valid: true, errors: [] }); clearProviderEnv(); if (provider === "twilio") { process.env.TWILIO_ACCOUNT_SID = "AC123"; process.env.TWILIO_AUTH_TOKEN = "secret"; } else if (provider === "telnyx") { process.env.TELNYX_API_KEY = "KEY123"; process.env.TELNYX_CONNECTION_ID = "CONN456"; process.env.TELNYX_PUBLIC_KEY = "public-key"; } else { process.env.PLIVO_AUTH_ID = "MA123"; process.env.PLIVO_AUTH_TOKEN = "secret"; } const fromEnv = resolveVoiceCallConfig(createBaseConfig(provider)); expect(validateProviderConfig(fromEnv)).toMatchObject({ valid: true, errors: [] }); } }); }); describe("twilio provider", () => { it("passes validation with mixed config and env vars", () => { process.env.TWILIO_AUTH_TOKEN = "secret"; let config = createBaseConfig("twilio"); config.twilio = { accountSid: "AC123" }; config = resolveVoiceCallConfig(config); const result = validateProviderConfig(config); expect(result.valid).toBe(true); expect(result.errors).toEqual([]); }); it("fails validation when required twilio credentials are missing", () => { process.env.TWILIO_AUTH_TOKEN = "secret"; const missingSid = validateProviderConfig(resolveVoiceCallConfig(createBaseConfig("twilio"))); expect(missingSid.valid).toBe(false); expect(missingSid.errors).toContain( "plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)", ); delete process.env.TWILIO_AUTH_TOKEN; process.env.TWILIO_ACCOUNT_SID = "AC123"; const missingToken = validateProviderConfig( resolveVoiceCallConfig(createBaseConfig("twilio")), ); expect(missingToken.valid).toBe(false); expect(missingToken.errors).toContain( "plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)", ); }); }); describe("telnyx provider", () => { it("fails validation when apiKey is missing everywhere", () => { process.env.TELNYX_CONNECTION_ID = "CONN456"; let config = createBaseConfig("telnyx"); config = resolveVoiceCallConfig(config); const result = validateProviderConfig(config); expect(result.valid).toBe(false); expect(result.errors).toContain( "plugins.entries.voice-call.config.telnyx.apiKey is required (or set TELNYX_API_KEY env)", ); }); it("requires a public key unless signature verification is skipped", () => { const missingPublicKey = createBaseConfig("telnyx"); missingPublicKey.inboundPolicy = "allowlist"; missingPublicKey.telnyx = { apiKey: "KEY123", connectionId: "CONN456" }; const missingPublicKeyResult = validateProviderConfig(missingPublicKey); expect(missingPublicKeyResult.valid).toBe(false); expect(missingPublicKeyResult.errors).toContain( "plugins.entries.voice-call.config.telnyx.publicKey is required (or set TELNYX_PUBLIC_KEY env)", ); const withPublicKey = createBaseConfig("telnyx"); withPublicKey.inboundPolicy = "allowlist"; withPublicKey.telnyx = { apiKey: "KEY123", connectionId: "CONN456", publicKey: "public-key", }; expect(validateProviderConfig(withPublicKey)).toMatchObject({ valid: true, errors: [] }); const skippedVerification = createBaseConfig("telnyx"); skippedVerification.skipSignatureVerification = true; skippedVerification.telnyx = { apiKey: "KEY123", connectionId: "CONN456" }; expect(validateProviderConfig(skippedVerification)).toMatchObject({ valid: true, errors: [], }); }); }); describe("plivo provider", () => { it("fails validation when authId is missing everywhere", () => { process.env.PLIVO_AUTH_TOKEN = "secret"; let config = createBaseConfig("plivo"); config = resolveVoiceCallConfig(config); const result = validateProviderConfig(config); expect(result.valid).toBe(false); expect(result.errors).toContain( "plugins.entries.voice-call.config.plivo.authId is required (or set PLIVO_AUTH_ID env)", ); }); }); describe("disabled config", () => { it("skips validation when enabled is false", () => { const config = createBaseConfig("twilio"); config.enabled = false; const result = validateProviderConfig(config); expect(result.valid).toBe(true); expect(result.errors).toEqual([]); }); }); }); describe("normalizeVoiceCallConfig", () => { it("fills nested runtime defaults from a partial config boundary", () => { const normalized = normalizeVoiceCallConfig({ enabled: true, provider: "mock", streaming: { enabled: true, streamPath: "/custom-stream", }, }); expect(normalized.serve.path).toBe("/voice/webhook"); expect(normalized.streaming.streamPath).toBe("/custom-stream"); expect(normalized.streaming.sttModel).toBe("gpt-4o-transcribe"); expect(normalized.tunnel.provider).toBe("none"); expect(normalized.webhookSecurity.allowedHosts).toEqual([]); }); it("accepts partial nested TTS overrides and preserves nested objects", () => { const normalized = normalizeVoiceCallConfig({ tts: { provider: "elevenlabs", elevenlabs: { apiKey: { source: "env", provider: "elevenlabs", id: "ELEVENLABS_API_KEY", }, voiceSettings: { speed: 1.1, }, }, }, }); expect(normalized.tts?.provider).toBe("elevenlabs"); expect(normalized.tts?.elevenlabs?.apiKey).toEqual({ source: "env", provider: "elevenlabs", id: "ELEVENLABS_API_KEY", }); expect(normalized.tts?.elevenlabs?.voiceSettings).toEqual({ speed: 1.1 }); }); });