Files
openclaw/extensions/elevenlabs/shared.test.ts
dwc1997 8b03d5ce24 fix(elevenlabs): reject malformed base URL overrides (#105163)
* fix(elevenlabs): fall back to default base URL when config value is malformed

* fix(elevenlabs): reject malformed and non-http(s) base URL overrides

* fix(elevenlabs): redact configured URL from base URL validation errors

* fix(elevenlabs): preserve realtime WebSocket overrides

* test(elevenlabs): avoid stale realtime test overlap

* style(elevenlabs): format realtime URL assertion

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-18 07:08:45 +01:00

78 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
DEFAULT_ELEVENLABS_BASE_URL,
normalizeElevenLabsBaseUrl,
normalizeElevenLabsRealtimeBaseUrl,
} from "./shared.js";
describe("normalizeElevenLabsBaseUrl", () => {
it("returns the default when the base URL is missing or blank", () => {
expect(normalizeElevenLabsBaseUrl(undefined)).toBe(DEFAULT_ELEVENLABS_BASE_URL);
expect(normalizeElevenLabsBaseUrl(" ")).toBe(DEFAULT_ELEVENLABS_BASE_URL);
});
it("trims and strips trailing slashes from a valid URL", () => {
expect(normalizeElevenLabsBaseUrl(" https://custom.example.com/ ")).toBe(
"https://custom.example.com",
);
expect(normalizeElevenLabsBaseUrl("http://localhost:8080")).toBe("http://localhost:8080");
});
it("rejects an explicit malformed override instead of silently retargeting", () => {
// An operator's explicit endpoint must not be swapped for the default; fail
// actionably here so the request cannot target an unintended host, and so a
// downstream `new URL(...)` never throws an opaque TypeError.
expect(() => normalizeElevenLabsBaseUrl("not a url")).toThrow(/Invalid ElevenLabs baseUrl/);
expect(() => normalizeElevenLabsBaseUrl("////")).toThrow(/Invalid ElevenLabs baseUrl/);
});
it("rejects a parseable but unsupported (non-HTTP(S)) scheme", () => {
// `new URL()` accepts ftp:/data:/custom schemes, but downstream fetch and
// WebSocket paths only support http(s) ElevenLabs endpoints.
expect(() => normalizeElevenLabsBaseUrl("ftp://files.example.com")).toThrow(
/unsupported scheme/,
);
expect(() => normalizeElevenLabsBaseUrl("data:text/plain,x")).toThrow(/unsupported scheme/);
});
it("does not leak URL credentials or sensitive query values in validation errors", () => {
// Rejection errors may reach logs/diagnostics; they must not echo userinfo
// or credential-bearing query parameters from the configured baseUrl.
const nonHttp = "ftp://user:sup3r-secret@files.example.com/x?api_key=leak-me";
expect(() => normalizeElevenLabsBaseUrl(nonHttp)).toThrow(/unsupported scheme/);
try {
normalizeElevenLabsBaseUrl(nonHttp);
} catch (error) {
const message = (error as Error).message;
expect(message).not.toContain("sup3r-secret");
expect(message).not.toContain("leak-me");
expect(message).not.toContain("api_key");
}
// A malformed value that embeds a token must not be echoed either.
const malformed = "http://:not a url token=abcd1234secret";
try {
normalizeElevenLabsBaseUrl(malformed);
} catch (error) {
expect((error as Error).message).not.toContain("abcd1234secret");
}
});
it("keeps every accepted result parseable as an http(s) URL", () => {
for (const input of ["https://ok.example.com/", "http://a.b:9000"]) {
const normalized = normalizeElevenLabsBaseUrl(input);
const url = new URL(normalized);
expect(["http:", "https:"]).toContain(url.protocol);
}
});
it("maps HTTP endpoints and preserves explicit WebSocket endpoints for realtime", () => {
expect(normalizeElevenLabsRealtimeBaseUrl("https://api.example.com/")).toBe(
"wss://api.example.com",
);
expect(normalizeElevenLabsRealtimeBaseUrl("wss://realtime.example.com/")).toBe(
"wss://realtime.example.com",
);
expect(normalizeElevenLabsRealtimeBaseUrl("ws://localhost:8080/")).toBe("ws://localhost:8080");
});
});