From dccb035a0219ca696d5f28cc718c06065cfd7820 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:19:57 +0000 Subject: [PATCH 1/6] Initial plan From 081d03a7531b64bdc26f328fad1171aa2e23d038 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:29:07 +0000 Subject: [PATCH 2/6] Remove back button event handling for SilentWebViewAuthorizationFragment - Skip super.onViewCreated() call to prevent OnBackPressedCallback registration - Add Robolectric unit test verifying no back press callback is registered Co-authored-by: mohitc1 <22034758+mohitc1@users.noreply.github.com> --- .../SilentWebViewAuthorizationFragment.kt | 5 +- .../SilentWebViewAuthorizationFragmentTest.kt | 83 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt index 3575b41c88..30b18b0ba9 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt @@ -63,7 +63,10 @@ class SilentWebViewAuthorizationFragment : WebViewAuthorizationFragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + // Do not call super.onViewCreated() here. The parent AuthorizationFragment.onViewCreated() + // registers an OnBackPressedCallback that would intercept device back button presses. + // SilentWebViewAuthorizationFragment is designed for silent, invisible flows and must not + // intercept back button events so that host app navigation is unaffected. cancelAuthorizationOnTimeOut(webViewSilentAuthorizationFlowTimeOut) } diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt new file mode 100644 index 0000000000..14526a8179 --- /dev/null +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.internal.providers.oauth2 + +import android.content.Intent +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTHORIZATION_AGENT +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REDIRECT_URI +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_URL +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT +import com.microsoft.identity.common.java.ui.AuthorizationAgent +import org.junit.Assert.assertFalse +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner + +/** + * Tests for [SilentWebViewAuthorizationFragment]. + */ +@RunWith(RobolectricTestRunner::class) +class SilentWebViewAuthorizationFragmentTest { + + /** + * Verifies that [SilentWebViewAuthorizationFragment.onViewCreated] does NOT register an + * [androidx.activity.OnBackPressedCallback] on the host activity's + * [androidx.activity.OnBackPressedDispatcher]. Silent flows are invisible to the user and + * must never intercept the device back button, so the callback registered by + * [AuthorizationFragment.onViewCreated] must be skipped. + */ + @Test + fun onViewCreated_doesNotRegisterOnBackPressedCallback() { + val intent = Intent().apply { + putExtra(AUTHORIZATION_AGENT, AuthorizationAgent.WEBVIEW) + putExtra(WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT, 5000L) + putExtra(REDIRECT_URI, "msauth://com.test.package/redirect") + putExtra(REQUEST_URL, "https://login.microsoftonline.com/common/oauth2/v2.0/authorize") + } + + val activity = Robolectric.buildActivity(SilentAuthorizationActivity::class.java, intent) + .create() + .start() + .resume() + .get() + + // Confirm the SilentWebViewAuthorizationFragment was actually added to the activity and + // that onViewCreated() was called, so the assertion below is meaningful. + val fragment = activity.supportFragmentManager.fragments + .filterIsInstance() + .firstOrNull() + assert(fragment != null) { + "SilentWebViewAuthorizationFragment was not added to the activity; cannot verify back-press behavior." + } + assert(fragment?.view != null) { + "SilentWebViewAuthorizationFragment view is null; onViewCreated() may not have been called." + } + + // No back-pressed callback should have been registered by the silent fragment. + assertFalse( + "SilentWebViewAuthorizationFragment must not register an OnBackPressedCallback", + activity.onBackPressedDispatcher.hasEnabledCallbacks() + ) + } +} From 22ca1de6d9cf7fb35ddba84e07d45d76131fbcb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:40:50 +0000 Subject: [PATCH 3/6] Add changelog entry for #3030 Co-authored-by: mohitc1 <22034758+mohitc1@users.noreply.github.com> --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 992e7e3193..9dc78c9466 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ vNext ---------- +- [PATCH] Remove back button interception from SilentWebViewAuthorizationFragment (#3030) - [MINOR] Add AIDL interface for device registration service.(#2926) - [MINOR] Move debugIntuneCE and prodIntuneCE from BrokerData to AppRegistry as App instances (#3012) - [MINOR] Remove LruCache from SharedPreferencesFileManager (#2910) From 63af79e461f3611c6c03a3e8b7e589ae8f35bc4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:23:28 +0000 Subject: [PATCH 4/6] Replace Robolectric test with mockito-kotlin spy test for SilentWebViewAuthorizationFragment Co-authored-by: mohitc1 <22034758+mohitc1@users.noreply.github.com> --- .../SilentWebViewAuthorizationFragment.kt | 4 +- .../SilentWebViewAuthorizationFragmentTest.kt | 70 +++++++------------ 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt index 30b18b0ba9..677da5e03b 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt @@ -26,6 +26,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.VisibleForTesting import androidx.lifecycle.lifecycleScope import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT import com.microsoft.identity.common.java.providers.RawAuthorizationResult @@ -87,7 +88,8 @@ class SilentWebViewAuthorizationFragment : WebViewAuthorizationFragment() { * * @param timeOutInMs The timeout duration in milliseconds. */ - private fun cancelAuthorizationOnTimeOut(timeOutInMs : Long) = viewLifecycleOwner.lifecycleScope.launch{ + @VisibleForTesting + internal fun cancelAuthorizationOnTimeOut(timeOutInMs : Long) = viewLifecycleOwner.lifecycleScope.launch{ val methodTag = "$TAG:cancelAuthorizationOnTimeOut" delay(timeOutInMs) Logger.info(methodTag, "Received Authorization flow cancel request from SDK") diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt index 14526a8179..0669e7776f 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt @@ -22,62 +22,44 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.providers.oauth2 -import android.content.Intent -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTHORIZATION_AGENT -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REDIRECT_URI -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_URL -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT -import com.microsoft.identity.common.java.ui.AuthorizationAgent -import org.junit.Assert.assertFalse +import android.view.View import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever /** * Tests for [SilentWebViewAuthorizationFragment]. */ -@RunWith(RobolectricTestRunner::class) class SilentWebViewAuthorizationFragmentTest { /** - * Verifies that [SilentWebViewAuthorizationFragment.onViewCreated] does NOT register an - * [androidx.activity.OnBackPressedCallback] on the host activity's - * [androidx.activity.OnBackPressedDispatcher]. Silent flows are invisible to the user and - * must never intercept the device back button, so the callback registered by - * [AuthorizationFragment.onViewCreated] must be skipped. + * Verifies that [SilentWebViewAuthorizationFragment.onViewCreated] does NOT call + * [AuthorizationFragment.onViewCreated], which would register an enabled + * [androidx.activity.OnBackPressedCallback] on the host activity's dispatcher. + * + * Silent flows are invisible to the user and must never intercept the device back button. + * If [AuthorizationFragment.onViewCreated] were called, it would invoke [requireActivity], + * which throws [IllegalStateException] when the fragment is not attached to an activity, + * failing this test. The test passing confirms [super.onViewCreated] is skipped. */ @Test - fun onViewCreated_doesNotRegisterOnBackPressedCallback() { - val intent = Intent().apply { - putExtra(AUTHORIZATION_AGENT, AuthorizationAgent.WEBVIEW) - putExtra(WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT, 5000L) - putExtra(REDIRECT_URI, "msauth://com.test.package/redirect") - putExtra(REQUEST_URL, "https://login.microsoftonline.com/common/oauth2/v2.0/authorize") - } + fun onViewCreated_skipsSuperOnViewCreated_andTriggersCancelAuthorizationOnTimeOut() { + val fragment = spy(SilentWebViewAuthorizationFragment()) + val mockView = mock() - val activity = Robolectric.buildActivity(SilentAuthorizationActivity::class.java, intent) - .create() - .start() - .resume() - .get() + // Stub out the internal timeout call so no Fragment lifecycle (viewLifecycleOwner) is needed. + doNothing().whenever(fragment).cancelAuthorizationOnTimeOut(any()) - // Confirm the SilentWebViewAuthorizationFragment was actually added to the activity and - // that onViewCreated() was called, so the assertion below is meaningful. - val fragment = activity.supportFragmentManager.fragments - .filterIsInstance() - .firstOrNull() - assert(fragment != null) { - "SilentWebViewAuthorizationFragment was not added to the activity; cannot verify back-press behavior." - } - assert(fragment?.view != null) { - "SilentWebViewAuthorizationFragment view is null; onViewCreated() may not have been called." - } + // If super.onViewCreated() were invoked it would call requireActivity(), throwing + // IllegalStateException because the fragment is not attached. The call succeeding + // proves super is skipped. + fragment.onViewCreated(mockView, null) - // No back-pressed callback should have been registered by the silent fragment. - assertFalse( - "SilentWebViewAuthorizationFragment must not register an OnBackPressedCallback", - activity.onBackPressedDispatcher.hasEnabledCallbacks() - ) + // Confirm the only side-effect — the timeout cancellation — was triggered. + verify(fragment).cancelAuthorizationOnTimeOut(any()) } } From ac4d00285dd8e032763f6141e473404f2e3a6135 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:28:58 +0000 Subject: [PATCH 5/6] Revert mockito-kotlin spy test changes (restoring Robolectric test) Co-authored-by: mohitc1 <22034758+mohitc1@users.noreply.github.com> --- .../SilentWebViewAuthorizationFragment.kt | 4 +- .../SilentWebViewAuthorizationFragmentTest.kt | 70 ++++++++++++------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt index 677da5e03b..30b18b0ba9 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragment.kt @@ -26,7 +26,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.VisibleForTesting import androidx.lifecycle.lifecycleScope import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT import com.microsoft.identity.common.java.providers.RawAuthorizationResult @@ -88,8 +87,7 @@ class SilentWebViewAuthorizationFragment : WebViewAuthorizationFragment() { * * @param timeOutInMs The timeout duration in milliseconds. */ - @VisibleForTesting - internal fun cancelAuthorizationOnTimeOut(timeOutInMs : Long) = viewLifecycleOwner.lifecycleScope.launch{ + private fun cancelAuthorizationOnTimeOut(timeOutInMs : Long) = viewLifecycleOwner.lifecycleScope.launch{ val methodTag = "$TAG:cancelAuthorizationOnTimeOut" delay(timeOutInMs) Logger.info(methodTag, "Received Authorization flow cancel request from SDK") diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt index 0669e7776f..14526a8179 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt @@ -22,44 +22,62 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.providers.oauth2 -import android.view.View +import android.content.Intent +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTHORIZATION_AGENT +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REDIRECT_URI +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_URL +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT +import com.microsoft.identity.common.java.ui.AuthorizationAgent +import org.junit.Assert.assertFalse import org.junit.Test -import org.mockito.kotlin.any -import org.mockito.kotlin.doNothing -import org.mockito.kotlin.mock -import org.mockito.kotlin.spy -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner /** * Tests for [SilentWebViewAuthorizationFragment]. */ +@RunWith(RobolectricTestRunner::class) class SilentWebViewAuthorizationFragmentTest { /** - * Verifies that [SilentWebViewAuthorizationFragment.onViewCreated] does NOT call - * [AuthorizationFragment.onViewCreated], which would register an enabled - * [androidx.activity.OnBackPressedCallback] on the host activity's dispatcher. - * - * Silent flows are invisible to the user and must never intercept the device back button. - * If [AuthorizationFragment.onViewCreated] were called, it would invoke [requireActivity], - * which throws [IllegalStateException] when the fragment is not attached to an activity, - * failing this test. The test passing confirms [super.onViewCreated] is skipped. + * Verifies that [SilentWebViewAuthorizationFragment.onViewCreated] does NOT register an + * [androidx.activity.OnBackPressedCallback] on the host activity's + * [androidx.activity.OnBackPressedDispatcher]. Silent flows are invisible to the user and + * must never intercept the device back button, so the callback registered by + * [AuthorizationFragment.onViewCreated] must be skipped. */ @Test - fun onViewCreated_skipsSuperOnViewCreated_andTriggersCancelAuthorizationOnTimeOut() { - val fragment = spy(SilentWebViewAuthorizationFragment()) - val mockView = mock() + fun onViewCreated_doesNotRegisterOnBackPressedCallback() { + val intent = Intent().apply { + putExtra(AUTHORIZATION_AGENT, AuthorizationAgent.WEBVIEW) + putExtra(WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT, 5000L) + putExtra(REDIRECT_URI, "msauth://com.test.package/redirect") + putExtra(REQUEST_URL, "https://login.microsoftonline.com/common/oauth2/v2.0/authorize") + } - // Stub out the internal timeout call so no Fragment lifecycle (viewLifecycleOwner) is needed. - doNothing().whenever(fragment).cancelAuthorizationOnTimeOut(any()) + val activity = Robolectric.buildActivity(SilentAuthorizationActivity::class.java, intent) + .create() + .start() + .resume() + .get() - // If super.onViewCreated() were invoked it would call requireActivity(), throwing - // IllegalStateException because the fragment is not attached. The call succeeding - // proves super is skipped. - fragment.onViewCreated(mockView, null) + // Confirm the SilentWebViewAuthorizationFragment was actually added to the activity and + // that onViewCreated() was called, so the assertion below is meaningful. + val fragment = activity.supportFragmentManager.fragments + .filterIsInstance() + .firstOrNull() + assert(fragment != null) { + "SilentWebViewAuthorizationFragment was not added to the activity; cannot verify back-press behavior." + } + assert(fragment?.view != null) { + "SilentWebViewAuthorizationFragment view is null; onViewCreated() may not have been called." + } - // Confirm the only side-effect — the timeout cancellation — was triggered. - verify(fragment).cancelAuthorizationOnTimeOut(any()) + // No back-pressed callback should have been registered by the silent fragment. + assertFalse( + "SilentWebViewAuthorizationFragment must not register an OnBackPressedCallback", + activity.onBackPressedDispatcher.hasEnabledCallbacks() + ) } } From b6f1f3d4ef1f65677f3007784315372466174db5 Mon Sep 17 00:00:00 2001 From: Mohit Date: Wed, 18 Mar 2026 17:29:49 -0700 Subject: [PATCH 6/6] fix test --- .../SilentWebViewAuthorizationFragmentTest.kt | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt index 14526a8179..9886349779 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SilentWebViewAuthorizationFragmentTest.kt @@ -22,16 +22,19 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.providers.oauth2 -import android.content.Intent -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTHORIZATION_AGENT -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REDIRECT_URI -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.REQUEST_URL -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT -import com.microsoft.identity.common.java.ui.AuthorizationAgent +import android.view.View +import androidx.activity.OnBackPressedDispatcher +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry import org.junit.Assert.assertFalse import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.Robolectric +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner /** @@ -49,35 +52,32 @@ class SilentWebViewAuthorizationFragmentTest { */ @Test fun onViewCreated_doesNotRegisterOnBackPressedCallback() { - val intent = Intent().apply { - putExtra(AUTHORIZATION_AGENT, AuthorizationAgent.WEBVIEW) - putExtra(WEB_VIEW_SILENT_AUTHORIZATION_FLOW_TIMEOUT, 5000L) - putExtra(REDIRECT_URI, "msauth://com.test.package/redirect") - putExtra(REQUEST_URL, "https://login.microsoftonline.com/common/oauth2/v2.0/authorize") + // Real dispatcher to track callback registration. + val dispatcher = OnBackPressedDispatcher() + + // Mock activity that returns our dispatcher. + val mockActivity = mock { + on { onBackPressedDispatcher } doReturn dispatcher } - val activity = Robolectric.buildActivity(SilentAuthorizationActivity::class.java, intent) - .create() - .start() - .resume() - .get() + // Mock lifecycle owner in RESUMED state for lifecycleScope.launch. + val lifecycleOwner = mock() + val lifecycleRegistry = LifecycleRegistry.createUnsafe(lifecycleOwner) + lifecycleRegistry.currentState = Lifecycle.State.RESUMED + whenever(lifecycleOwner.lifecycle).thenReturn(lifecycleRegistry) - // Confirm the SilentWebViewAuthorizationFragment was actually added to the activity and - // that onViewCreated() was called, so the assertion below is meaningful. - val fragment = activity.supportFragmentManager.fragments - .filterIsInstance() - .firstOrNull() - assert(fragment != null) { - "SilentWebViewAuthorizationFragment was not added to the activity; cannot verify back-press behavior." - } - assert(fragment?.view != null) { - "SilentWebViewAuthorizationFragment view is null; onViewCreated() may not have been called." - } + // Spy the fragment and stub only what onViewCreated needs. + val fragment = spy(SilentWebViewAuthorizationFragment()) + doReturn(mockActivity).whenever(fragment).requireActivity() + doReturn(lifecycleOwner).whenever(fragment).viewLifecycleOwner + + // Call onViewCreated directly with a mock view. + fragment.onViewCreated(mock(), null) // No back-pressed callback should have been registered by the silent fragment. assertFalse( "SilentWebViewAuthorizationFragment must not register an OnBackPressedCallback", - activity.onBackPressedDispatcher.hasEnabledCallbacks() + dispatcher.hasEnabledCallbacks() ) } }