openclaw/extensions/voice-call/src/config.test.ts

209 lines
7.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { validateProviderConfig, resolveVoiceCallConfig, type VoiceCallConfig } from "./config.js";
function createBaseConfig(provider: "telnyx" | "twilio" | "plivo" | "mock"): VoiceCallConfig {
return {
enabled: true,
provider,
fromNumber: "+15550001234",
inboundPolicy: "disabled",
allowFrom: [],
outbound: { defaultMode: "notify", notifyHangupDelaySec: 3 },
maxDurationSeconds: 300,
staleCallReaperSeconds: 600,
silenceTimeoutMs: 800,
transcriptTimeoutMs: 180000,
ringTimeoutMs: 30000,
maxConcurrentCalls: 1,
serve: { port: 3334, bind: "127.0.0.1", path: "/voice/webhook" },
tailscale: { mode: "off", path: "/voice/webhook" },
tunnel: { provider: "none", allowNgrokFreeTierLoopbackBypass: false },
webhookSecurity: {
allowedHosts: [],
trustForwardingHeaders: false,
trustedProxyIPs: [],
},
streaming: {
enabled: false,
sttProvider: "openai-realtime",
sttModel: "gpt-4o-transcribe",
silenceDurationMs: 800,
vadThreshold: 0.5,
streamPath: "/voice/stream",
preStartTimeoutMs: 5000,
maxPendingConnections: 32,
maxPendingConnectionsPerIp: 4,
maxConnections: 128,
},
skipSignatureVerification: false,
stt: { provider: "openai", model: "whisper-1" },
tts: {
provider: "openai",
openai: { model: "gpt-4o-mini-tts", voice: "coral" },
},
responseModel: "openai/gpt-4o-mini",
responseTimeoutMs: 30000,
};
}
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([]);
});
});
});