From 14934f0b7c874e6af4ed93a1c307a17d222ef739 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 20:25:41 +0100 Subject: [PATCH] test(google-meet): verify twilio setup readiness --- extensions/google-meet/index.test.ts | 90 +++++++++++++++++++++++++++ extensions/google-meet/src/runtime.ts | 2 +- extensions/google-meet/src/setup.ts | 73 +++++++++++++++++++++- 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/extensions/google-meet/index.test.ts b/extensions/google-meet/index.test.ts index 1bd2257fdca..0f4d282a776 100644 --- a/extensions/google-meet/index.test.ts +++ b/extensions/google-meet/index.test.ts @@ -80,6 +80,7 @@ type NodeListResult = { function setup( config: Record = {}, options: { + fullConfig?: Record; nodesListResult?: NodeListResult; nodesInvokeResult?: unknown; browserActResult?: Record; @@ -163,6 +164,7 @@ function setup( description: "test", version: "0", source: "test", + config: options.fullConfig ?? {}, pluginConfig: config, runtime: { system: { @@ -537,6 +539,94 @@ describe("google-meet plugin", () => { expect(result.details.ok).toBe(true); }); + it("reports Twilio delegation readiness when voice-call is enabled", async () => { + vi.stubEnv("TWILIO_ACCOUNT_SID", "AC123"); + vi.stubEnv("TWILIO_AUTH_TOKEN", "secret"); + vi.stubEnv("TWILIO_FROM_NUMBER", "+15550001234"); + const { tools } = setup( + { + defaultTransport: "chrome-node", + chromeNode: { node: "parallels-macos" }, + }, + { + fullConfig: { + plugins: { + allow: ["google-meet", "voice-call"], + entries: { + "voice-call": { + enabled: true, + config: { provider: "twilio" }, + }, + }, + }, + }, + }, + ); + const tool = tools[0] as { + execute: ( + id: string, + params: unknown, + ) => Promise<{ details: { ok?: boolean; checks?: unknown[] } }>; + }; + + const result = await tool.execute("id", { action: "setup_status" }); + + expect(result.details.ok).toBe(true); + expect(result.details.checks).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "twilio-voice-call-plugin", + ok: true, + }), + expect.objectContaining({ + id: "twilio-voice-call-credentials", + ok: true, + }), + ]), + ); + }); + + it("reports missing voice-call wiring for Twilio transport", async () => { + vi.stubEnv("TWILIO_ACCOUNT_SID", ""); + vi.stubEnv("TWILIO_AUTH_TOKEN", ""); + vi.stubEnv("TWILIO_FROM_NUMBER", ""); + const { tools } = setup( + { defaultTransport: "twilio" }, + { + fullConfig: { + plugins: { + allow: ["google-meet"], + entries: { + "voice-call": { enabled: false }, + }, + }, + }, + }, + ); + const tool = tools[0] as { + execute: ( + id: string, + params: unknown, + ) => Promise<{ details: { ok?: boolean; checks?: unknown[] } }>; + }; + + const result = await tool.execute("id", { action: "setup_status" }); + + expect(result.details.ok).toBe(false); + expect(result.details.checks).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "twilio-voice-call-plugin", + ok: false, + }), + expect.objectContaining({ + id: "twilio-voice-call-credentials", + ok: false, + }), + ]), + ); + }); + it("launches Chrome after the BlackHole check", async () => { const originalPlatform = process.platform; Object.defineProperty(process, "platform", { value: "darwin" }); diff --git a/extensions/google-meet/src/runtime.ts b/extensions/google-meet/src/runtime.ts index 6306ef9c4ce..5d9ef08935a 100644 --- a/extensions/google-meet/src/runtime.ts +++ b/extensions/google-meet/src/runtime.ts @@ -81,7 +81,7 @@ export class GoogleMeetRuntime { } setupStatus() { - return getGoogleMeetSetupStatus(this.params.config); + return getGoogleMeetSetupStatus(this.params.config, { fullConfig: this.params.fullConfig }); } async join(request: GoogleMeetJoinRequest): Promise { diff --git a/extensions/google-meet/src/setup.ts b/extensions/google-meet/src/setup.ts index e0f0fb2811b..816ccd3e350 100644 --- a/extensions/google-meet/src/setup.ts +++ b/extensions/google-meet/src/setup.ts @@ -22,8 +22,32 @@ function resolveUserPath(input: string): string { export function getGoogleMeetSetupStatus(config: GoogleMeetConfig): { ok: boolean; checks: SetupCheck[]; -} { +}; +export function getGoogleMeetSetupStatus( + config: GoogleMeetConfig, + options?: { + env?: NodeJS.ProcessEnv; + fullConfig?: unknown; + }, +): { + ok: boolean; + checks: SetupCheck[]; +}; +export function getGoogleMeetSetupStatus( + config: GoogleMeetConfig, + options?: { + env?: NodeJS.ProcessEnv; + fullConfig?: unknown; + }, +) { const checks: SetupCheck[] = []; + const env = options?.env ?? process.env; + const fullConfig = asRecord(options?.fullConfig); + const pluginEntries = asRecord(asRecord(fullConfig.plugins).entries); + const pluginAllow = asRecord(fullConfig.plugins).allow; + const voiceCallEntry = asRecord(pluginEntries["voice-call"]); + const voiceCallConfig = asRecord(voiceCallEntry.config); + const voiceCallTwilioConfig = asRecord(voiceCallConfig.twilio); if (config.auth.tokenPath) { const tokenPath = resolveUserPath(config.auth.tokenPath); @@ -110,8 +134,55 @@ export function getGoogleMeetSetupStatus(config: GoogleMeetConfig): { : "Set chrome.waitForInCallMs to delay realtime intro until the Meet tab is in-call", }); + const shouldCheckTwilioDelegation = + config.voiceCall.enabled && + (config.defaultTransport === "twilio" || + Boolean(config.twilio.defaultDialInNumber) || + Object.hasOwn(pluginEntries, "voice-call")); + if (shouldCheckTwilioDelegation) { + const voiceCallAllowed = !Array.isArray(pluginAllow) || pluginAllow.includes("voice-call"); + const voiceCallEnabled = voiceCallEntry.enabled !== false; + checks.push({ + id: "twilio-voice-call-plugin", + ok: voiceCallAllowed && voiceCallEnabled, + message: + voiceCallAllowed && voiceCallEnabled + ? "Twilio transport can delegate dialing to the voice-call plugin" + : "Enable plugins.entries.voice-call and include voice-call in plugins.allow for Twilio dialing", + }); + + const provider = normalizeOptionalString(voiceCallConfig.provider) ?? "twilio"; + if (provider === "twilio") { + const accountSid = normalizeOptionalString(voiceCallTwilioConfig.accountSid); + const authToken = normalizeOptionalString(voiceCallTwilioConfig.authToken); + const fromNumber = normalizeOptionalString(voiceCallConfig.fromNumber); + const twilioReady = Boolean( + (accountSid || env.TWILIO_ACCOUNT_SID) && + (authToken || env.TWILIO_AUTH_TOKEN) && + (fromNumber || env.TWILIO_FROM_NUMBER), + ); + checks.push({ + id: "twilio-voice-call-credentials", + ok: twilioReady, + message: twilioReady + ? "Twilio voice-call credentials are configured" + : "Set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_FROM_NUMBER or configure voice-call Twilio credentials", + }); + } + } + return { ok: checks.every((check) => check.ok), checks, }; } + +function asRecord(value: unknown): Record { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : {}; +} + +function normalizeOptionalString(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim() : undefined; +}