diff --git a/extensions/google-meet/index.test.ts b/extensions/google-meet/index.test.ts index 0f55859e141c..58a8e8fb4be4 100644 --- a/extensions/google-meet/index.test.ts +++ b/extensions/google-meet/index.test.ts @@ -376,6 +376,57 @@ function requireSetupCheck(checks: unknown[] | undefined, id: string): Record; +}): Promise> { + vi.stubEnv("TWILIO_ACCOUNT_SID", params.env.accountSid); + vi.stubEnv("TWILIO_AUTH_TOKEN", params.env.authToken); + vi.stubEnv("TWILIO_FROM_NUMBER", params.env.fromNumber); + 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", + publicUrl: "https://voice.example.com/voice/webhook", + fromNumber: params.configured?.fromNumber, + twilio: { + accountSid: params.configured?.accountSid, + authToken: params.configured?.authToken, + }, + }, + }, + }, + }, + }, + }, + ); + const tool = tools[0] as { + execute: ( + id: string, + params: unknown, + ) => Promise<{ details: { checks?: unknown[] } }>; + }; + + const result = await tool.execute("id", { action: "setup_status" }); + return requireSetupCheck(result.details.checks, "twilio-voice-call-credentials"); +} + function requireFetchGuardCall(auditContext: string): Record { const call = ( fetchGuardMocks.fetchWithSsrFGuard.mock.calls as Array<[Record]> @@ -2604,6 +2655,64 @@ describe("google-meet plugin", () => { expect(requireSetupCheck(result.details.checks, "twilio-voice-call-webhook").ok).toBe(true); }); + it.each([ + { + label: "environment account SID", + env: { accountSid: " ", authToken: "test-auth-token", fromNumber: "+15550001234" }, + }, + { + label: "environment auth token", + env: { accountSid: "AC123", authToken: " ", fromNumber: "+15550001234" }, + }, + { + label: "environment from number", + env: { accountSid: "AC123", authToken: "test-auth-token", fromNumber: " " }, + }, + { + label: "configured account SID", + env: { accountSid: "", authToken: "", fromNumber: "" }, + configured: { accountSid: " ", authToken: "test-auth-token", fromNumber: "+15550001234" }, + }, + { + label: "configured auth token", + env: { accountSid: "", authToken: "", fromNumber: "" }, + configured: { accountSid: "AC123", authToken: " ", fromNumber: "+15550001234" }, + }, + { + label: "configured from number", + env: { accountSid: "", authToken: "", fromNumber: "" }, + configured: { accountSid: "AC123", authToken: "test-auth-token", fromNumber: " " }, + }, + ])("reports a blank $label as missing", async ({ env, configured }) => { + const check = await getTwilioVoiceCallCredentialsCheck({ env, configured }); + + expect(check.ok).toBe(false); + }); + + it.each([ + { + label: "environment", + env: { + accountSid: " AC123 ", + authToken: " test-auth-token ", + fromNumber: " +15550001234 ", + }, + }, + { + label: "configuration", + env: { accountSid: "", authToken: "", fromNumber: "" }, + configured: { + accountSid: " AC123 ", + authToken: " test-auth-token ", + fromNumber: " +15550001234 ", + }, + }, + ])("accepts padded Twilio credentials from $label", async ({ env, configured }) => { + const check = await getTwilioVoiceCallCredentialsCheck({ env, configured }); + + expect(check.ok).toBe(true); + }); + it("reports missing voice-call wiring for explicit Twilio transport", async () => { vi.stubEnv("TWILIO_ACCOUNT_SID", ""); vi.stubEnv("TWILIO_AUTH_TOKEN", ""); diff --git a/extensions/google-meet/src/setup.ts b/extensions/google-meet/src/setup.ts index b51c52d30ec9..2dd8874f80e4 100644 --- a/extensions/google-meet/src/setup.ts +++ b/extensions/google-meet/src/setup.ts @@ -36,6 +36,10 @@ function isProviderUnreachableWebhookUrl(webhookUrl: string): boolean { } } +function resolveVoiceCallSetupValue(configured: unknown, fallback: unknown): string | undefined { + return normalizeOptionalString(configured) ?? normalizeOptionalString(fallback); +} + function getVoiceCallWebhookExposureCheck(voiceCallConfig: Record): SetupCheck { const publicUrl = normalizeOptionalString(voiceCallConfig.publicUrl); const tunnel = asRecord(voiceCallConfig.tunnel); @@ -240,14 +244,19 @@ export function getGoogleMeetSetupStatus( 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), + const accountSid = resolveVoiceCallSetupValue( + voiceCallTwilioConfig.accountSid, + env.TWILIO_ACCOUNT_SID, ); + const authToken = resolveVoiceCallSetupValue( + voiceCallTwilioConfig.authToken, + env.TWILIO_AUTH_TOKEN, + ); + const fromNumber = resolveVoiceCallSetupValue( + voiceCallConfig.fromNumber, + env.TWILIO_FROM_NUMBER, + ); + const twilioReady = Boolean(accountSid && authToken && fromNumber); checks.push({ id: "twilio-voice-call-credentials", ok: twilioReady,