From d9faefb8aa0e3df43b3114bea4654d1b6b7dcef5 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Mon, 7 Sep 2026 01:27:15 +0000 Subject: [PATCH 1/2] fix(cloud_functions): lowercase Android error codes with Locale.ROOT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turkish and Azerbaijani locales map 'I' to 'ı', so callable codes such as failed-precondition no longer match the documented ASCII strings. --- .github/workflows/android_unit_tests.yaml | 2 +- .../cloud_functions/android/build.gradle | 5 ++ .../FlutterFirebaseFunctionsPlugin.kt | 7 +- .../FlutterFirebaseFunctionsPluginTest.kt | 73 +++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt diff --git a/.github/workflows/android_unit_tests.yaml b/.github/workflows/android_unit_tests.yaml index 1ff80014f6a7..9dabddd4745e 100644 --- a/.github/workflows/android_unit_tests.yaml +++ b/.github/workflows/android_unit_tests.yaml @@ -43,7 +43,7 @@ jobs: # Tasks are listed per package rather than using the root testDebugUnitTest # task, which would also build the Dart sources of the aggregate test app. # Add a task here when a package gains an android/src/test directory. - run: cd tests/android && ./gradlew :firebase_crashlytics:testDebugUnitTest + run: cd tests/android && ./gradlew :firebase_crashlytics:testDebugUnitTest :cloud_functions:testDebugUnitTest - name: 'Upload test reports' if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a diff --git a/packages/cloud_functions/cloud_functions/android/build.gradle b/packages/cloud_functions/cloud_functions/android/build.gradle index 5cc439d6c2b2..d6dcebcbf0a5 100644 --- a/packages/cloud_functions/cloud_functions/android/build.gradle +++ b/packages/cloud_functions/cloud_functions/android/build.gradle @@ -78,6 +78,11 @@ android { implementation 'com.google.firebase:firebase-functions' implementation 'androidx.annotation:annotation:1.7.0' implementation 'org.reactivestreams:reactive-streams:1.0.4' + testImplementation 'junit:junit:4.13.2' + } + + testOptions { + unitTests.returnDefaultValues = true } } diff --git a/packages/cloud_functions/cloud_functions/android/src/main/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPlugin.kt b/packages/cloud_functions/cloud_functions/android/src/main/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPlugin.kt index f2aff1556e6d..a70556caf16a 100644 --- a/packages/cloud_functions/cloud_functions/android/src/main/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPlugin.kt +++ b/packages/cloud_functions/cloud_functions/android/src/main/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPlugin.kt @@ -136,7 +136,7 @@ class FlutterFirebaseFunctionsPlugin : FlutterPlugin, FlutterFirebasePlugin, Clo } } - details["code"] = code.replace("_", "-").lowercase(Locale.getDefault()) + details["code"] = mapFunctionsErrorCode(code) details["message"] = message if (additionalData != null) { @@ -164,6 +164,11 @@ class FlutterFirebaseFunctionsPlugin : FlutterPlugin, FlutterFirebasePlugin, Clo companion object { private const val METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_functions" + + // Locale.ROOT: Turkish/Azerbaijani map 'I' → 'ı' under Locale.getDefault(). + internal fun mapFunctionsErrorCode(code: String): String { + return code.replace("_", "-").lowercase(Locale.ROOT) + } } override fun call(arguments: Map, callback: (Result) -> Unit) { diff --git a/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt b/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt new file mode 100644 index 000000000000..1d5f26597fee --- /dev/null +++ b/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt @@ -0,0 +1,73 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.functions + +import java.util.Locale +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class FlutterFirebaseFunctionsPluginTest { + private lateinit var originalLocale: Locale + + @Before + fun setUp() { + originalLocale = Locale.getDefault() + } + + @After + fun tearDown() { + Locale.setDefault(originalLocale) + } + + @Test + fun mapFunctionsErrorCode_usesRootLocaleUnderTurkishDefault() { + Locale.setDefault(Locale("tr", "TR")) + + // Canary: the JVM's Turkish locale really does map 'I' to 'ı'. + assertEquals( + "faıled-precondıtıon", + "FAILED_PRECONDITION".replace("_", "-").lowercase(Locale.getDefault())) + + assertMappedCodes() + } + + @Test + fun mapFunctionsErrorCode_usesRootLocaleUnderAzerbaijaniDefault() { + Locale.setDefault(Locale("az", "AZ")) + + assertEquals( + "faıled-precondıtıon", + "FAILED_PRECONDITION".replace("_", "-").lowercase(Locale.getDefault())) + + assertMappedCodes() + } + + private fun assertMappedCodes() { + val expected = + mapOf( + "FAILED_PRECONDITION" to "failed-precondition", + "INVALID_ARGUMENT" to "invalid-argument", + "PERMISSION_DENIED" to "permission-denied", + "UNAUTHENTICATED" to "unauthenticated", + "INTERNAL" to "internal", + "UNAVAILABLE" to "unavailable", + "DEADLINE_EXCEEDED" to "deadline-exceeded", + "ALREADY_EXISTS" to "already-exists", + "UNIMPLEMENTED" to "unimplemented", + "UNKNOWN" to "unknown", + "NOT_FOUND" to "not-found", + "ABORTED" to "aborted", + ) + + for ((enumName, canonical) in expected) { + assertEquals( + canonical, FlutterFirebaseFunctionsPlugin.mapFunctionsErrorCode(enumName)) + } + } +} From 567e73a90cc1aa866af9142ab0d7d2b40858b55c Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Mon, 7 Sep 2026 01:47:02 +0000 Subject: [PATCH 2/2] style(cloud_functions): apply ktfmt to Android locale unit test --- .../firebase/functions/FlutterFirebaseFunctionsPluginTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt b/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt index 1d5f26597fee..dc3306864872 100644 --- a/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt +++ b/packages/cloud_functions/cloud_functions/android/src/test/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPluginTest.kt @@ -66,8 +66,7 @@ class FlutterFirebaseFunctionsPluginTest { ) for ((enumName, canonical) in expected) { - assertEquals( - canonical, FlutterFirebaseFunctionsPlugin.mapFunctionsErrorCode(enumName)) + assertEquals(canonical, FlutterFirebaseFunctionsPlugin.mapFunctionsErrorCode(enumName)) } } }