From e9b854ba64e3c48216e8a2a4648eb454e1704623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 7 May 2024 11:29:41 +0200 Subject: [PATCH 1/4] Enforce mandatory session verification only for new logins - Creates `AppMigration` base interface as a way to isolate migration logic, app migrations must implement this interface. - Creates `AppMigration01` with the existing logs removal migration and `AppMigration02` with the logic to allow existing sessions to skip verification. - Add `DefaultSessionPreferencesStoreFactory.remove(sessionId)` to allow a ephemeral session store access to exist outside the `SessionScope` for this new migration. --- changelog.d/2810.bugfix | 1 + features/migration/impl/build.gradle.kts | 3 ++ .../migration/impl/MigrationPresenter.kt | 38 +++++++++------- .../migration/impl/migrations/AppMigration.kt | 22 +++++++++ .../impl/migrations/AppMigration01.kt | 33 ++++++++++++++ .../impl/migrations/AppMigration02.kt | 45 +++++++++++++++++++ .../DefaultSessionPreferencesStoreFactory.kt | 4 ++ 7 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 changelog.d/2810.bugfix create mode 100644 features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt create mode 100644 features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt create mode 100644 features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt diff --git a/changelog.d/2810.bugfix b/changelog.d/2810.bugfix new file mode 100644 index 0000000000..d6683a221a --- /dev/null +++ b/changelog.d/2810.bugfix @@ -0,0 +1 @@ +Enforce mandatory session verification only for new logins. diff --git a/features/migration/impl/build.gradle.kts b/features/migration/impl/build.gradle.kts index 5ae18e8791..8d70f12f83 100644 --- a/features/migration/impl/build.gradle.kts +++ b/features/migration/impl/build.gradle.kts @@ -27,9 +27,12 @@ android { dependencies { implementation(projects.features.migration.api) implementation(projects.libraries.architecture) + implementation(projects.libraries.preferences.impl) implementation(libs.androidx.datastore.preferences) implementation(projects.features.rageshake.api) implementation(projects.libraries.designsystem) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.sessionStorage.api) implementation(projects.libraries.uiStrings) ksp(libs.showkase.processor) diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt index 48f904ea67..c70a5dba0f 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt @@ -24,40 +24,46 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.features.api.MigrationState -import io.element.android.features.rageshake.api.logs.LogFilesRemover +import io.element.android.features.migration.impl.migrations.AppMigration import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import timber.log.Timber import javax.inject.Inject +@SingleIn(AppScope::class) class MigrationPresenter @Inject constructor( private val migrationStore: MigrationStore, - private val logFilesRemover: LogFilesRemover, + private val migrations: Set<@JvmSuppressWildcards AppMigration>, ) : Presenter { @Composable override fun present(): MigrationState { - val migrationStoreVersion = migrationStore.applicationMigrationVersion().collectAsState(initial = null) + val migrationStoreVersion by migrationStore.applicationMigrationVersion().collectAsState(initial = null) var migrationAction: AsyncData by remember { mutableStateOf(AsyncData.Uninitialized) } - /* // Uncomment this block to run the migration everytime - LaunchedEffect(Unit) { - migrationStore.setApplicationMigrationVersion(0) - } - */ +// LaunchedEffect(Unit) { +// Timber.d("Resetting migration version to 0") +// migrationStore.setApplicationMigrationVersion(0) +// } + + val orderedMigrations = migrations.sortedBy { it.order } - LaunchedEffect(migrationStoreVersion.value) { - val migrationValue = migrationStoreVersion.value ?: return@LaunchedEffect + LaunchedEffect(migrationStoreVersion) { + val migrationValue = migrationStoreVersion ?: return@LaunchedEffect if (migrationValue == MIGRATION_VERSION) { + Timber.d("Current app migration version: $migrationValue. No migration needed.") migrationAction = AsyncData.Success(Unit) return@LaunchedEffect } migrationAction = AsyncData.Loading(Unit) - if (migrationValue < 1) { - logFilesRemover.perform() + val nextMigration = orderedMigrations.firstOrNull { it.order > migrationValue } + if (nextMigration != null) { + Timber.d("Current app migration version: $migrationValue. Applying migration: ${nextMigration.order}") + nextMigration.migrate() + migrationStore.setApplicationMigrationVersion(nextMigration.order) } - // Add new step here - - migrationStore.setApplicationMigrationVersion(MIGRATION_VERSION) } return MigrationState( @@ -68,6 +74,6 @@ class MigrationPresenter @Inject constructor( companion object { // Increment this value when you need to run the migration again, and // add step in the LaunchedEffect above - const val MIGRATION_VERSION = 1 + const val MIGRATION_VERSION = 2 } } diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt new file mode 100644 index 0000000000..4227bbc717 --- /dev/null +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.migration.impl.migrations + +interface AppMigration { + val order: Int + suspend fun migrate() +} diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt new file mode 100644 index 0000000000..91a89d7aa4 --- /dev/null +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.migration.impl.migrations + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.features.rageshake.api.logs.LogFilesRemover +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class AppMigration01 @Inject constructor( + private val logFilesRemover: LogFilesRemover, +) : AppMigration { + override val order: Int = 1 + + override suspend fun migrate() { + logFilesRemover.perform() + } +} diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt new file mode 100644 index 0000000000..42084e0d97 --- /dev/null +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.migration.impl.migrations + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStoreFactory +import io.element.android.libraries.sessionstorage.api.SessionStore +import kotlinx.coroutines.coroutineScope +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class AppMigration02 @Inject constructor( + private val sessionStore: SessionStore, + private val sessionPreferenceStoreFactory: DefaultSessionPreferencesStoreFactory, +) : AppMigration { + override val order: Int = 2 + + override suspend fun migrate() { + coroutineScope { + for (session in sessionStore.getAllSessions()) { + val sessionId = SessionId(session.userId) + val preferences = sessionPreferenceStoreFactory.get(sessionId, this) + preferences.setSkipSessionVerification(true) + // This session preference store must be ephemeral since it's not created with the right coroutine scope + sessionPreferenceStoreFactory.remove(sessionId) + } + } + } +} diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt index 745dd42615..b496947099 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt @@ -47,4 +47,8 @@ class DefaultSessionPreferencesStoreFactory @Inject constructor( fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): DefaultSessionPreferencesStore = cache.getOrPut(sessionId) { DefaultSessionPreferencesStore(context, sessionId, sessionCoroutineScope) } + + fun remove(sessionId: SessionId) { + cache.remove(sessionId) + } } From 6368cc30b8e7ffb5e9a326baa1d0466d7d48f563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 7 May 2024 11:41:45 +0200 Subject: [PATCH 2/4] Fix tests --- .../migration/impl/MigrationPresenterTest.kt | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt index 01b2d847a7..2a9079279e 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt @@ -20,10 +20,11 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.rageshake.api.logs.LogFilesRemover -import io.element.android.features.rageshake.test.logs.FakeLogFilesRemover +import io.element.android.features.migration.impl.migrations.AppMigration import io.element.android.libraries.architecture.AsyncData import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.lambda.LambdaNoParamRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest @@ -54,10 +55,10 @@ class MigrationPresenterTest { @Test fun `present - testing all migrations`() = runTest { val store = InMemoryMigrationStore(0) - val logFilesRemoverLambda = lambdaRecorder { -> } + val migrations = (1..MigrationPresenter.MIGRATION_VERSION).map { FakeMigration(it) } val presenter = createPresenter( migrationStore = store, - logFilesRemover = FakeLogFilesRemover(logFilesRemoverLambda), + migrations = migrations.toSet(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -67,19 +68,28 @@ class MigrationPresenterTest { awaitItem().also { state -> assertThat(state.migrationAction).isEqualTo(AsyncData.Loading(Unit)) } - awaitItem().also { state -> - assertThat(state.migrationAction).isEqualTo(AsyncData.Success(Unit)) - } - logFilesRemoverLambda.assertions().isCalledExactly(1) + consumeItemsUntilPredicate { it.migrationAction is AsyncData.Success } assertThat(store.applicationMigrationVersion().first()).isEqualTo(MigrationPresenter.MIGRATION_VERSION) + for (migration in migrations) { + migration.migrateLambda.assertions().isCalledOnce() + } } } } private fun createPresenter( migrationStore: MigrationStore = InMemoryMigrationStore(0), - logFilesRemover: LogFilesRemover = FakeLogFilesRemover(lambdaRecorder(ensureNeverCalled = true) { -> }), + migrations: Set = setOf(FakeMigration(1)), ) = MigrationPresenter( migrationStore = migrationStore, - logFilesRemover = logFilesRemover, + migrations = migrations, ) + +private class FakeMigration( + override val order: Int, + var migrateLambda: LambdaNoParamRecorder = lambdaRecorder { -> }, +) : AppMigration { + override suspend fun migrate() { + migrateLambda() + } +} From de0bc14913c66daea3d013b55b8f78698c8b8c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 7 May 2024 12:40:16 +0200 Subject: [PATCH 3/4] Add more tests. This also includes creating several abstractions. --- .../utils/DefaultCallWidgetProviderTest.kt | 2 +- .../ftue/impl/DefaultFtueServiceTests.kt | 2 +- .../messages/impl/MessagesPresenterTest.kt | 4 +- .../actionlist/ActionListPresenterTest.kt | 2 +- .../MessageComposerPresenterTest.kt | 2 +- .../impl/timeline/TimelinePresenterTest.kt | 2 +- .../typing/TypingNotificationPresenterTest.kt | 2 +- features/migration/impl/build.gradle.kts | 3 ++ .../impl/migrations/AppMigration02.kt | 4 +- .../impl/migrations/AppMigration01Test.kt | 33 ++++++++++++ .../impl/migrations/AppMigration02Test.kt | 50 +++++++++++++++++++ .../advanced/AdvancedSettingsPresenterTest.kt | 4 +- .../DeveloperSettingsPresenterTest.kt | 2 +- .../test/logs/FakeLogFilesRemover.kt | 3 +- .../roomlist/impl/RoomListPresenterTests.kt | 2 +- .../impl/VerifySelfSessionPresenterTests.kt | 2 +- libraries/preferences/api/build.gradle.kts | 1 + .../store/SessionPreferencesStoreFactory.kt | 25 ++++++++++ .../DefaultSessionPreferencesStoreFactory.kt | 10 ++-- libraries/preferences/test/build.gradle.kts | 2 + .../test/FakeSessionPreferenceStoreFactory.kt | 38 ++++++++++++++ .../test/InMemoryAppPreferencesStore.kt | 2 +- .../test/InMemorySessionPreferencesStore.kt | 2 +- .../session-storage/test/build.gradle.kts | 1 + .../sessionstorage/test/SessionData.kt | 40 +++++++++++++++ 25 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt create mode 100644 features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt create mode 100644 libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStoreFactory.kt create mode 100644 libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferenceStoreFactory.kt rename libraries/preferences/test/src/main/kotlin/io/element/android/libraries/{featureflag => preferences}/test/InMemoryAppPreferencesStore.kt (97%) rename libraries/preferences/test/src/main/kotlin/io/element/android/libraries/{featureflag => preferences}/test/InMemorySessionPreferencesStore.kt (98%) create mode 100644 libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt diff --git a/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt index 31f6327d2f..368db19fcc 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt @@ -18,7 +18,6 @@ package io.element.android.features.call.utils import com.google.common.truth.Truth.assertThat import io.element.android.features.preferences.api.store.AppPreferencesStore -import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -28,6 +27,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.widget.FakeCallWidgetSettingsProvider import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver +import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt index 9cc3db8d29..e346e3a1dc 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt @@ -24,10 +24,10 @@ import io.element.android.features.ftue.impl.state.DefaultFtueService import io.element.android.features.ftue.impl.state.FtueStep import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.test.FakeLockScreenService -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.permissions.impl.FakePermissionStateProvider +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 2774b71aef..8ef9d3d268 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -63,8 +63,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService -import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -93,6 +91,8 @@ import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory +import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder import io.element.android.services.analytics.test.FakeAnalyticsService diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index c1062625f7..39ffda04b2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -30,8 +30,8 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList -import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore import io.element.android.libraries.matrix.test.A_MESSAGE +import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index 0e83aa84ec..7274027059 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -39,7 +39,6 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.media.ImageInfo @@ -77,6 +76,7 @@ import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.textcomposer.model.Message import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 093306db09..bfca79d714 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -34,7 +34,6 @@ import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.features.poll.test.actions.FakeEndPollAction import io.element.android.features.poll.test.actions.FakeSendPollResponseAction -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem @@ -53,6 +52,7 @@ import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.ui.components.aMatrixUserList +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 540d9f67c4..b136053b40 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -22,7 +22,6 @@ import app.cash.turbine.Event import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.preferences.api.store.SessionPreferencesStore -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState @@ -32,6 +31,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_4 import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.runTest diff --git a/features/migration/impl/build.gradle.kts b/features/migration/impl/build.gradle.kts index 8d70f12f83..2c8ffa58b3 100644 --- a/features/migration/impl/build.gradle.kts +++ b/features/migration/impl/build.gradle.kts @@ -42,6 +42,9 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.libraries.sessionStorage.implMemory) + testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.libraries.preferences.test) testImplementation(projects.tests.testutils) testImplementation(projects.features.rageshake.test) } diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt index 42084e0d97..410a382ff3 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt @@ -17,9 +17,9 @@ package io.element.android.features.migration.impl.migrations import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStoreFactory import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.coroutineScope import javax.inject.Inject @@ -27,7 +27,7 @@ import javax.inject.Inject @ContributesMultibinding(AppScope::class) class AppMigration02 @Inject constructor( private val sessionStore: SessionStore, - private val sessionPreferenceStoreFactory: DefaultSessionPreferencesStoreFactory, + private val sessionPreferenceStoreFactory: SessionPreferencesStoreFactory, ) : AppMigration { override val order: Int = 2 diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt new file mode 100644 index 0000000000..91f50a81b3 --- /dev/null +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.migration.impl.migrations + +import io.element.android.features.rageshake.test.logs.FakeLogFilesRemover +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AppMigration01Test { + @Test + fun `test migration`() = runTest { + val logsFileRemover = FakeLogFilesRemover() + val migration = AppMigration01(logsFileRemover) + + migration.migrate() + + logsFileRemover.performLambda.assertions().isCalledOnce() + } +} diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt new file mode 100644 index 0000000000..1a077fda2e --- /dev/null +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.migration.impl.migrations + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.preferences.test.FakeSessionPreferenceStoreFactory +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore +import io.element.android.libraries.sessionstorage.test.aSessionData +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AppMigration02Test { + @Test + fun `test migration`() = runTest { + val sessionStore = InMemorySessionStore().apply { + updateData(aSessionData()) + } + val sessionPreferencesStore = InMemorySessionPreferencesStore(isSessionVerificationSkipped = false) + val sessionPreferencesStoreFactory = FakeSessionPreferenceStoreFactory( + getLambda = lambdaRecorder { _, _, -> sessionPreferencesStore }, + ) + val migration = AppMigration02(sessionStore = sessionStore, sessionPreferenceStoreFactory = sessionPreferencesStoreFactory) + + migration.migrate() + + // We got the session preferences store + sessionPreferencesStoreFactory.getLambda.assertions().isCalledOnce() + // We changed the settings for the skipping the session verification + assertThat(sessionPreferencesStore.isSessionVerificationSkipped().first()).isTrue() + // We removed the session preferences store from cache + sessionPreferencesStoreFactory.removeLambda.assertions().isCalledOnce() + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index 1745d57396..8f6c45c51d 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -21,8 +21,8 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.compound.theme.Theme -import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore +import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import kotlinx.coroutines.test.runTest diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 19b1614500..6908ee3c9d 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -29,7 +29,7 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService -import io.element.android.libraries.featureflag.test.InMemoryAppPreferencesStore +import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import kotlinx.coroutines.test.runTest diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt index a3c927fe41..a416f2eeb4 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt +++ b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt @@ -18,9 +18,10 @@ package io.element.android.features.rageshake.test.logs import io.element.android.features.rageshake.api.logs.LogFilesRemover import io.element.android.tests.testutils.lambda.LambdaNoParamRecorder +import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeLogFilesRemover( - private val performLambda: LambdaNoParamRecorder, + var performLambda: LambdaNoParamRecorder = lambdaRecorder { -> }, ) : LogFilesRemover { override suspend fun perform() { performLambda() diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index bc511d0270..053b4e51a8 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -48,7 +48,6 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.test.FakeFeatureFlagService -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.indicator.impl.DefaultIndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupState @@ -73,6 +72,7 @@ import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.EventsRecorder diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index f75dd2ff79..3dd391da16 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -24,7 +24,6 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationData @@ -35,6 +34,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationFlowStat import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest diff --git a/libraries/preferences/api/build.gradle.kts b/libraries/preferences/api/build.gradle.kts index f782dd328b..f220aacd71 100644 --- a/libraries/preferences/api/build.gradle.kts +++ b/libraries/preferences/api/build.gradle.kts @@ -24,4 +24,5 @@ android { dependencies { implementation(libs.coroutines.core) + implementation(projects.libraries.matrix.api) } diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStoreFactory.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStoreFactory.kt new file mode 100644 index 0000000000..8726f568dd --- /dev/null +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStoreFactory.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.api.store + +import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.CoroutineScope + +interface SessionPreferencesStoreFactory { + fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): SessionPreferencesStore + fun remove(sessionId: SessionId) +} diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt index b496947099..1465473402 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt @@ -17,6 +17,9 @@ package io.element.android.libraries.preferences.impl.store import android.content.Context +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.preferences.api.store.SessionPreferencesStore +import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn @@ -28,10 +31,11 @@ import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) class DefaultSessionPreferencesStoreFactory @Inject constructor( @ApplicationContext private val context: Context, sessionObserver: SessionObserver, -) { +) : SessionPreferencesStoreFactory { private val cache = ConcurrentHashMap() init { @@ -44,11 +48,11 @@ class DefaultSessionPreferencesStoreFactory @Inject constructor( }) } - fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): DefaultSessionPreferencesStore = cache.getOrPut(sessionId) { + override fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): SessionPreferencesStore = cache.getOrPut(sessionId) { DefaultSessionPreferencesStore(context, sessionId, sessionCoroutineScope) } - fun remove(sessionId: SessionId) { + override fun remove(sessionId: SessionId) { cache.remove(sessionId) } } diff --git a/libraries/preferences/test/build.gradle.kts b/libraries/preferences/test/build.gradle.kts index 86b891b21e..eaf8c66821 100644 --- a/libraries/preferences/test/build.gradle.kts +++ b/libraries/preferences/test/build.gradle.kts @@ -24,5 +24,7 @@ android { dependencies { api(projects.libraries.preferences.api) implementation(libs.coroutines.core) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) } } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferenceStoreFactory.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferenceStoreFactory.kt new file mode 100644 index 0000000000..264ac4ec3a --- /dev/null +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferenceStoreFactory.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.preferences.test + +import io.element.android.features.preferences.api.store.SessionPreferencesStore +import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder +import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.CoroutineScope + +class FakeSessionPreferenceStoreFactory( + var getLambda: LambdaTwoParamsRecorder = lambdaRecorder { _, _ -> throw NotImplementedError() }, + var removeLambda: LambdaOneParamRecorder = lambdaRecorder { _ -> }, +) : SessionPreferencesStoreFactory { + override fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): SessionPreferencesStore { + return getLambda(sessionId, sessionCoroutineScope) + } + + override fun remove(sessionId: SessionId) { + removeLambda(sessionId) + } +} diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryAppPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt similarity index 97% rename from libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryAppPreferencesStore.kt rename to libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt index c065622f3f..e29c4758ca 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryAppPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.featureflag.test +package io.element.android.libraries.preferences.test import io.element.android.features.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.Flow diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt similarity index 98% rename from libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt rename to libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt index 16f98d0870..916b2ff16f 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.featureflag.test +package io.element.android.libraries.preferences.test import io.element.android.features.preferences.api.store.SessionPreferencesStore import kotlinx.coroutines.flow.Flow diff --git a/libraries/session-storage/test/build.gradle.kts b/libraries/session-storage/test/build.gradle.kts index 0c8de84669..cfdddd5e59 100644 --- a/libraries/session-storage/test/build.gradle.kts +++ b/libraries/session-storage/test/build.gradle.kts @@ -22,5 +22,6 @@ android { } dependencies { + implementation(projects.libraries.matrix.api) implementation(projects.libraries.sessionStorage.api) } diff --git a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt new file mode 100644 index 0000000000..84c1142193 --- /dev/null +++ b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.sessionstorage.test + +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.sessionstorage.api.LoginType +import io.element.android.libraries.sessionstorage.api.SessionData + +fun aSessionData( + sessionId: SessionId = SessionId("@alice:server.org"), + isTokenValid: Boolean = false, +): SessionData { + return SessionData( + userId = sessionId.value, + deviceId = "aDeviceId", + accessToken = "anAccessToken", + refreshToken = "aRefreshToken", + homeserverUrl = "aHomeserverUrl", + oidcData = null, + slidingSyncProxy = null, + loginTimestamp = null, + isTokenValid = isTokenValid, + loginType = LoginType.UNKNOWN, + passphrase = null, + ) +} From fc6937dd9e8be0056b5d6272fd9d7406a21fb858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 7 May 2024 15:31:06 +0200 Subject: [PATCH 4/4] Review changes. - Make `orderedMigrations` a class property, `migrations` just a constructor parameter to avoid incorrect usages. - Create `lastMigration` property too, use it instead of `MIGRATION_VERSION`. --- .../features/migration/impl/MigrationPresenter.kt | 15 +++++---------- .../migration/impl/MigrationPresenterTest.kt | 8 +++++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt index c70a5dba0f..d157566e24 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt @@ -35,8 +35,11 @@ import javax.inject.Inject @SingleIn(AppScope::class) class MigrationPresenter @Inject constructor( private val migrationStore: MigrationStore, - private val migrations: Set<@JvmSuppressWildcards AppMigration>, + migrations: Set<@JvmSuppressWildcards AppMigration>, ) : Presenter { + private val orderedMigrations = migrations.sortedBy { it.order } + private val lastMigration: Int = orderedMigrations.lastOrNull()?.order ?: 0 + @Composable override fun present(): MigrationState { val migrationStoreVersion by migrationStore.applicationMigrationVersion().collectAsState(initial = null) @@ -48,11 +51,9 @@ class MigrationPresenter @Inject constructor( // migrationStore.setApplicationMigrationVersion(0) // } - val orderedMigrations = migrations.sortedBy { it.order } - LaunchedEffect(migrationStoreVersion) { val migrationValue = migrationStoreVersion ?: return@LaunchedEffect - if (migrationValue == MIGRATION_VERSION) { + if (migrationValue == lastMigration) { Timber.d("Current app migration version: $migrationValue. No migration needed.") migrationAction = AsyncData.Success(Unit) return@LaunchedEffect @@ -70,10 +71,4 @@ class MigrationPresenter @Inject constructor( migrationAction = migrationAction, ) } - - companion object { - // Increment this value when you need to run the migration again, and - // add step in the LaunchedEffect above - const val MIGRATION_VERSION = 2 - } } diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt index 2a9079279e..3ea0625f76 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt @@ -37,9 +37,11 @@ class MigrationPresenterTest { @Test fun `present - no migration should occurs if ApplicationMigrationVersion is the last one`() = runTest { - val store = InMemoryMigrationStore(MigrationPresenter.MIGRATION_VERSION) + val migrations = (1..10).map { FakeMigration(it) } + val store = InMemoryMigrationStore(migrations.maxOf { it.order }) val presenter = createPresenter( migrationStore = store, + migrations = migrations.toSet(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -55,7 +57,7 @@ class MigrationPresenterTest { @Test fun `present - testing all migrations`() = runTest { val store = InMemoryMigrationStore(0) - val migrations = (1..MigrationPresenter.MIGRATION_VERSION).map { FakeMigration(it) } + val migrations = (1..10).map { FakeMigration(it) } val presenter = createPresenter( migrationStore = store, migrations = migrations.toSet(), @@ -69,7 +71,7 @@ class MigrationPresenterTest { assertThat(state.migrationAction).isEqualTo(AsyncData.Loading(Unit)) } consumeItemsUntilPredicate { it.migrationAction is AsyncData.Success } - assertThat(store.applicationMigrationVersion().first()).isEqualTo(MigrationPresenter.MIGRATION_VERSION) + assertThat(store.applicationMigrationVersion().first()).isEqualTo(migrations.maxOf { it.order }) for (migration in migrations) { migration.migrateLambda.assertions().isCalledOnce() }