Update breaking changes in the FxA/Sync integration

pull/159/head^2
Grisha Kruglov 4 years ago committed by Grisha Kruglov
parent d45e482373
commit 71b51146cb

@ -261,10 +261,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue {
lifecycleScope.launch {
// Make sure accountManager is initialized.
components.backgroundServices.accountManager.initAsync().await()
components.backgroundServices.accountManager.start()
// If we're authenticated, kick-off a sync and a device state refresh.
components.backgroundServices.accountManager.authenticatedAccount()?.let {
components.backgroundServices.accountManager.syncNowAsync(
components.backgroundServices.accountManager.syncNow(
SyncReason.Startup,
debounce = true
)

@ -5,13 +5,11 @@
package org.mozilla.fenix.components
import android.content.Context
import android.content.SharedPreferences
import android.os.StrictMode
import androidx.annotation.GuardedBy
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
@ -77,9 +75,18 @@ class AccountAbnormalities(
private val logger = Logger("AccountAbnormalities")
private val prefs = StrictMode.allowThreadDiskReads().resetPoliciesAfter {
context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE)
}
private val prefs: SharedPreferences
private val hadAccountPrior: Boolean
init {
val prefPair = StrictMode.allowThreadDiskReads().resetPoliciesAfter {
val p = context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE)
val a = p.getBoolean(KEY_HAS_ACCOUNT, false)
Pair(p, a)
}
prefs = prefPair.first
hadAccountPrior = prefPair.second
}
/**
* Once [accountManager] is initialized, queries it to detect abnormal account states.
@ -89,37 +96,28 @@ class AccountAbnormalities(
* @param initResult A deferred result of initializing [accountManager].
* @return A [Unit] deferred, resolved once [initResult] is resolved and state is processed for abnormalities.
*/
fun accountManagerInitializedAsync(
accountManager: FxaAccountManager,
initResult: Deferred<Unit>
): Deferred<Unit> {
fun accountManagerStarted(
accountManager: FxaAccountManager
) {
check(!accountManagerConfigured) { "accountManagerStarted called twice" }
accountManagerConfigured = true
return CoroutineScope(coroutineContext).async {
// Wait for the account manager to finish initializing. If it's queried before the
// "init" deferred returns, we'll get inaccurate results.
initResult.await()
// Account manager finished initialization, we can now query it for the account state
// and see if it doesn't match our expectations.
// Behaviour considered abnormal:
// - we had an account before, and it's no longer present during startup
// We use a flag in prefs to keep track of the fact that we have an authenticated
// account. This works because our account state is persisted in the application's
// directory, same as SharedPreferences. If user clears application data, both the
// fxa state and our flag will be removed.
val hadAccountBefore = prefs.getBoolean(KEY_HAS_ACCOUNT, false)
val hasAccountNow = accountManager.authenticatedAccount() != null
if (hadAccountBefore && !hasAccountNow) {
prefs.edit().putBoolean(KEY_HAS_ACCOUNT, false).apply()
logger.warn("Missing expected account on startup")
crashReporter.submitCaughtException(
AbnormalFxaEvent.MissingExpectedAccountAfterStartup()
)
}
// Behaviour considered abnormal:
// - we had an account before, and it's no longer present during startup
// We use a flag in prefs to keep track of the fact that we have an authenticated
// account. This works because our account state is persisted in the application's
// directory, same as SharedPreferences. If user clears application data, both the
// fxa state and our flag will be removed.
val hasAccountNow = accountManager.authenticatedAccount() != null
if (hadAccountPrior && !hasAccountNow) {
prefs.edit().putBoolean(KEY_HAS_ACCOUNT, false).apply()
logger.warn("Missing expected account on startup")
crashReporter.submitCaughtException(
AbnormalFxaEvent.MissingExpectedAccountAfterStartup()
)
}
}
@ -152,8 +150,7 @@ class AccountAbnormalities(
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
check(accountManagerConfigured) { "onAuthenticated before account manager was configured" }
// Not checking state of accountManagerConfigured because we'll race against account manager's start.
onAuthenticatedCalled = true
// We don't check if KEY_HAS_ACCOUNT was already true: we will see onAuthenticated on every

@ -8,19 +8,22 @@ import android.content.Context
import android.os.Build
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.browser.storage.sync.RemoteTabsStorage
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.DeviceCapability
import mozilla.components.concept.sync.DeviceConfig
import mozilla.components.concept.sync.DeviceType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.feature.accounts.push.FxaPushSupportFeature
import mozilla.components.feature.accounts.push.SendTabFeature
import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.service.fxa.DeviceConfig
import mozilla.components.service.fxa.PeriodicSyncConfig
import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.SyncConfig
import mozilla.components.service.fxa.SyncEngine
@ -86,7 +89,7 @@ class BackgroundServices(
@VisibleForTesting
val supportedEngines =
setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords, SyncEngine.Tabs)
private val syncConfig = SyncConfig(supportedEngines, syncPeriodInMinutes = 240L) // four hours
private val syncConfig = SyncConfig(supportedEngines, PeriodicSyncConfig(periodMinutes = 240)) // four hours
init {
/* Make the "history", "bookmark", "passwords", and "tabs" stores accessible to workers
@ -156,10 +159,10 @@ class BackgroundServices(
SyncedTabsIntegration(context, accountManager).launch()
accountAbnormalities.accountManagerInitializedAsync(
accountManager,
accountManager.initAsync()
)
MainScope().launch {
accountManager.start()
accountAbnormalities.accountManagerStarted(accountManager)
}
}.also {
accountManagerAvailableQueue.ready()
}
@ -180,31 +183,33 @@ internal class TelemetryAccountObserver(
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
when (authType) {
// User signed-in into an existing FxA account.
AuthType.Signin ->
metricController.track(Event.SyncAuthSignIn)
AuthType.Signin -> Event.SyncAuthSignIn
// User created a new FxA account.
AuthType.Signup ->
metricController.track(Event.SyncAuthSignUp)
AuthType.Signup -> Event.SyncAuthSignUp
// User paired to an existing account via QR code scanning.
AuthType.Pairing ->
metricController.track(Event.SyncAuthPaired)
AuthType.Pairing -> Event.SyncAuthPaired
// User signed-in into an FxA account shared from another locally installed app using the reuse flow.
AuthType.MigratedReuse -> Event.SyncAuthFromSharedReuse
// User signed-in into an FxA account shared from another locally installed app
// (e.g. Fennec).
AuthType.Shared ->
metricController.track(Event.SyncAuthFromShared)
// User signed-in into an FxA account shared from another locally installed app using the copy flow.
AuthType.MigratedCopy -> Event.SyncAuthFromSharedCopy
// Account Manager recovered a broken FxA auth state, without direct user involvement.
AuthType.Recovered ->
metricController.track(Event.SyncAuthRecovered)
AuthType.Recovered -> Event.SyncAuthRecovered
// User signed-in into an FxA account via unknown means.
// Exact mechanism identified by the 'action' param.
is AuthType.OtherExternal ->
metricController.track(Event.SyncAuthOtherExternal)
is AuthType.OtherExternal -> Event.SyncAuthOtherExternal
// Account restored from a hydrated state on disk (e.g. during startup).
AuthType.Existing -> null
}?.let {
metricController.track(it)
}
// Used by Leanplum as a context variable.
settings.fxaSignedIn = true
}

@ -67,7 +67,8 @@ sealed class Event {
object SyncAuthPaired : Event()
object SyncAuthRecovered : Event()
object SyncAuthOtherExternal : Event()
object SyncAuthFromShared : Event()
object SyncAuthFromSharedReuse : Event()
object SyncAuthFromSharedCopy : Event()
object SyncAccountOpened : Event()
object SyncAccountClosed : Event()
object SyncAccountSyncNow : Event()

@ -269,7 +269,7 @@ private val Event.wrapper: EventWrapper<*>?
is Event.SyncAuthOtherExternal -> EventWrapper<NoExtraKeys>(
{ SyncAuth.otherExternal.record(it) }
)
is Event.SyncAuthFromShared -> EventWrapper<NoExtraKeys>(
is Event.SyncAuthFromSharedReuse, Event.SyncAuthFromSharedCopy -> EventWrapper<NoExtraKeys>(
{ SyncAuth.autoLogin.record(it) }
)
is Event.SyncAuthRecovered -> EventWrapper<NoExtraKeys>(

@ -42,7 +42,7 @@ private val Event.name: String?
is Event.CollectionTabRestored -> "E_Collection_Tab_Opened"
is Event.SyncAuthSignUp -> "E_FxA_New_Signup"
is Event.SyncAuthSignIn, Event.SyncAuthPaired, Event.SyncAuthOtherExternal -> "E_Sign_In_FxA"
is Event.SyncAuthFromShared -> "E_Sign_In_FxA_Fennec_to_Fenix"
is Event.SyncAuthFromSharedCopy, Event.SyncAuthFromSharedReuse -> "E_Sign_In_FxA_Fennec_to_Fenix"
is Event.SyncAuthSignOut -> "E_Sign_Out_FxA"
is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"

@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult
import mozilla.components.service.fxa.manager.MigrationResult
import mozilla.components.service.fxa.sharing.ShareableAccount
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
@ -56,8 +56,12 @@ class OnboardingAutomaticSignInViewHolder(
button.isEnabled = false
val accountManager = context.components.backgroundServices.accountManager
when (accountManager.signInWithShareableAccountAsync(shareableAccount).await()) {
SignInWithShareableAccountResult.Failure -> {
when (accountManager.migrateFromAccount(shareableAccount)) {
MigrationResult.WillRetry,
MigrationResult.Success -> {
// We consider both of these as a 'success'.
}
MigrationResult.Failure -> {
// Failed to sign-in (e.g. bad credentials). Allow to try again.
button.text = context.getString(R.string.onboarding_firefox_account_auto_signin_confirm)
button.isEnabled = true
@ -69,9 +73,6 @@ class OnboardingAutomaticSignInViewHolder(
context.getString(R.string.onboarding_firefox_account_automatic_signin_failed)
).show()
}
SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> {
// We consider both of these as a 'success'.
}
}
}

@ -143,7 +143,7 @@ class DefaultBookmarkController(
scope.launch {
store.dispatch(BookmarkFragmentAction.StartSync)
invokePendingDeletion()
activity.components.backgroundServices.accountManager.syncNowAsync(SyncReason.User).await()
activity.components.backgroundServices.accountManager.syncNow(SyncReason.User)
// The current bookmark node we are viewing may be made invalid after syncing so we
// check if the current node is valid and if it isn't we find the nearest valid ancestor
// and open it

@ -344,7 +344,7 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), UserInteractionHandl
private suspend fun syncHistory() {
val accountManager = requireComponents.backgroundServices.accountManager
accountManager.syncNowAsync(SyncReason.User).await()
accountManager.syncNow(SyncReason.User)
viewModel.invalidate()
}
}

@ -122,7 +122,7 @@ internal class OneTimeMessageDeliveryObserver(
authType: AuthType
) {
lazyAccount.value.withConstellation {
processRawEventAsync(String(message))
MainScope().launch { processRawEvent(String(message)) }
}
MainScope().launch {

@ -169,25 +169,32 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
updateSyncEngineStates()
setDisabledWhileSyncing(accountManager.isSyncActive())
requirePreference<CheckBoxPreference>(R.string.pref_key_sync_history).apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
fun updateSyncEngineState(context: Context, engine: SyncEngine, newState: Boolean) {
SyncEnginesStorage(context).setStatus(engine, newState)
viewLifecycleOwner.lifecycleScope.launch {
context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange)
}
}
requirePreference<CheckBoxPreference>(R.string.pref_key_sync_bookmarks).apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.Bookmarks, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
fun SyncEngine.prefId(): Int = when (this) {
SyncEngine.History -> R.string.pref_key_sync_history
SyncEngine.Bookmarks -> R.string.pref_key_sync_bookmarks
SyncEngine.Passwords -> R.string.pref_key_sync_logins
SyncEngine.Tabs -> R.string.pref_key_sync_tabs
else -> throw IllegalStateException("Accessing internal sync engines")
}
listOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Tabs).forEach {
requirePreference<CheckBoxPreference>(it.prefId()).apply {
setOnPreferenceChangeListener { _, newValue ->
updateSyncEngineState(context, it, newValue as Boolean)
true
}
}
}
requirePreference<CheckBoxPreference>(R.string.pref_key_sync_logins).apply {
// 'Passwords' listener is special, since we also display a pin protection warning.
requirePreference<CheckBoxPreference>(SyncEngine.Passwords.prefId()).apply {
setOnPreferenceChangeListener { _, newValue ->
val manager =
activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
@ -195,9 +202,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
newValue == false ||
!context.settings().shouldShowSecurityPinWarningSync
) {
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
updateSyncEngineState(context, SyncEngine.Passwords, newValue as Boolean)
} else {
showPinDialogWarning(newValue as Boolean)
}
@ -205,15 +210,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
}
}
requirePreference<CheckBoxPreference>(R.string.pref_key_sync_tabs).apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.Tabs, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
}
}
deviceConstellation?.registerDeviceObserver(
deviceConstellationObserver,
owner = this,
@ -237,8 +233,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ ->
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
viewLifecycleOwner.lifecycleScope.launch {
context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange)
}
}
setPositiveButton(getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ ->
@ -278,13 +275,12 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
viewLifecycleOwner.lifecycleScope.launch {
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
// Trigger a sync.
requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User)
.await()
requireComponents.backgroundServices.accountManager.syncNow(SyncReason.User)
// Poll for device events & update devices.
accountManager.authenticatedAccount()
?.deviceConstellation()?.run {
refreshDevicesAsync().await()
pollForCommandsAsync().await()
refreshDevices()
pollForCommands()
}
}
}
@ -298,8 +294,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
context?.let {
accountManager.authenticatedAccount()
?.deviceConstellation()
?.setDeviceNameAsync(newValue, it)
?.await()
?.setDeviceName(newValue, it)
}
}
return true

@ -64,7 +64,7 @@ class SignOutFragment : BottomSheetDialogFragment() {
viewLifecycleOwner.lifecycleScope.launch {
requireComponents
.backgroundServices.accountAbnormalities.userRequestedLogout()
accountManager.logoutAsync().await()
accountManager.logout()
}.invokeOnCompletion {
if (!findNavController().popBackStack(R.id.settingsFragment, false)) {
dismiss()

@ -70,6 +70,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireComponents.backgroundServices.accountManager.register(this, owner = this)
requireComponents.analytics.metrics.track(Event.SyncAuthOpened)
// App can be installed on devices with no camera modules. Like Android TV boxes.
@ -139,23 +140,13 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
// Since the snackbar can be presented in BrowserFragment or in SettingsFragment we must
// base our display method on the padSnackbar argument
if (args.padSnackbar) {
FenixSnackbar.make(
view = requireView(),
duration = snackbarLength,
isDisplayedWithBrowserToolbar = true
)
.setText(snackbarText)
.show()
} else {
FenixSnackbar.make(
view = requireView(),
duration = snackbarLength,
isDisplayedWithBrowserToolbar = false
)
.setText(snackbarText)
.show()
}
FenixSnackbar.make(
view = requireView(),
duration = snackbarLength,
isDisplayedWithBrowserToolbar = args.padSnackbar
)
.setText(snackbarText)
.show()
}
private fun navigateToPairWithEmail() {

@ -54,8 +54,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch(ioDispatcher) {
fxaAccountManager.authenticatedAccount()
?.deviceConstellation()
?.refreshDevicesAsync()
?.await()
?.refreshDevices()
val devicesShareOptions = buildDeviceList(fxaAccountManager, network)
devicesListLiveData.postValue(devicesShareOptions)

@ -9,7 +9,6 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.service.fxa.manager.FxaAccountManager
@ -35,12 +34,8 @@ class AccountAbnormalitiesTest {
assertEquals("userRequestedLogout before account manager was configured", e.message)
}
try {
accountAbnormalities.onAuthenticated(mockk(), mockk())
fail()
} catch (e: IllegalStateException) {
assertEquals("onAuthenticated before account manager was configured", e.message)
}
// This doesn't throw, see method for details.
accountAbnormalities.onAuthenticated(mockk(), mockk())
try {
accountAbnormalities.onLoggedOut()
@ -58,10 +53,7 @@ class AccountAbnormalitiesTest {
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
accountManager,
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.accountManagerStarted(accountManager)
// Logout action must be preceded by auth.
accountAbnormalities.userRequestedLogout()
@ -74,10 +66,7 @@ class AccountAbnormalitiesTest {
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
accountManager,
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.accountManagerStarted(accountManager)
accountAbnormalities.onAuthenticated(mockk(), mockk())
// So far, so good. A regular logout request while being authenticated.
@ -95,10 +84,7 @@ class AccountAbnormalitiesTest {
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
accountManager,
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.accountManagerStarted(accountManager)
// User didn't request this logout.
accountAbnormalities.onLoggedOut()
@ -111,10 +97,7 @@ class AccountAbnormalitiesTest {
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
accountManager,
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.accountManagerStarted(accountManager)
accountAbnormalities.onAuthenticated(mockk(), mockk())
verify { crashReporter wasNot Called }
@ -124,10 +107,7 @@ class AccountAbnormalitiesTest {
val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
// mock accountManager doesn't have an account, but we expect it to have one since we
// were authenticated before our "restart".
accountAbnormalities2.accountManagerInitializedAsync(
accountManager,
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities2.accountManagerStarted(accountManager)
assertCaughtException<AbnormalFxaEvent.MissingExpectedAccountAfterStartup>(crashReporter)
}
@ -138,10 +118,7 @@ class AccountAbnormalitiesTest {
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
accountManager,
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.accountManagerStarted(accountManager)
// We saw an auth event, then user requested a logout.
accountAbnormalities.onAuthenticated(mockk(), mockk())

@ -78,10 +78,13 @@ class BackgroundServicesTest {
fun `telemetry account observer tracks shared event`() {
val account = mockk<OAuthAccount>()
registry.notifyObservers { onAuthenticated(account, AuthType.Shared) }
verify { metrics.track(Event.SyncAuthFromShared) }
registry.notifyObservers { onAuthenticated(account, AuthType.MigratedReuse) }
verify { metrics.track(Event.SyncAuthFromSharedReuse) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
registry.notifyObservers { onAuthenticated(account, AuthType.MigratedCopy) }
verify { metrics.track(Event.SyncAuthFromSharedCopy) }
}
@Test

@ -7,16 +7,16 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import android.view.View
import io.mockk.every
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import io.mockk.verify
import io.mockk.unmockkObject
import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult
import mozilla.components.service.fxa.manager.MigrationResult
import mozilla.components.service.fxa.sharing.ShareableAccount
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
@ -69,13 +69,30 @@ class OnboardingAutomaticSignInViewHolderTest {
}
@Test
fun `sign in on click`() = runBlocking {
fun `sign in on click - MigrationResult Success`() = runBlocking {
val account = mockk<ShareableAccount> {
every { email } returns "email@example.com"
}
coEvery {
backgroundServices.accountManager.migrateFromAccount(account)
} returns MigrationResult.Success
val holder = OnboardingAutomaticSignInViewHolder(view, scope = this)
holder.bind(account)
holder.onClick(view.fxa_sign_in_button)
assertEquals("Signing in…", view.fxa_sign_in_button.text)
assertFalse(view.fxa_sign_in_button.isEnabled)
}
@Test
fun `sign in on click - MigrationResult WillRetry treated the same as Success`() = runBlocking {
val account = mockk<ShareableAccount> {
every { email } returns "email@example.com"
}
every {
backgroundServices.accountManager.signInWithShareableAccountAsync(account)
} returns CompletableDeferred(SignInWithShareableAccountResult.Success)
coEvery {
backgroundServices.accountManager.migrateFromAccount(account)
} returns MigrationResult.WillRetry
val holder = OnboardingAutomaticSignInViewHolder(view, scope = this)
holder.bind(account)
@ -90,9 +107,9 @@ class OnboardingAutomaticSignInViewHolderTest {
val account = mockk<ShareableAccount> {
every { email } returns "email@example.com"
}
every {
backgroundServices.accountManager.signInWithShareableAccountAsync(account)
} returns CompletableDeferred(SignInWithShareableAccountResult.Failure)
coEvery {
backgroundServices.accountManager.migrateFromAccount(account)
} returns MigrationResult.Failure
val holder = OnboardingAutomaticSignInViewHolder(view, scope = this)
holder.bind(account)

Loading…
Cancel
Save