diff options
author | 2024-01-23 12:21:43 -0500 | |
---|---|---|
committer | 2024-01-25 19:56:19 -0500 | |
commit | f30cb97a784ba508a82863ef74ea0135355aad0c (patch) | |
tree | fee5089bc3b3bf2b33e5caa9851303e7b0c52b07 /tests | |
parent | c5d725eec15d2cff2aab08437948d6d0f2d01a63 (diff) |
UserInteractor and Profile
A domain component which applies business logic to User data.
This component maps Users to Profiles, the set of which is
defined by Profile.Type
Bug: 309960444
Test: atest IntentResolver-tests-unit:UserInteractorTest \
FakeUserRepositoryTest
Change-Id: I9832836ae019ba1b0ae45366f9fc0e26bc9b23ce
Diffstat (limited to 'tests')
4 files changed, 362 insertions, 12 deletions
diff --git a/tests/shared/src/com/android/intentresolver/v2/data/repository/FakeUserRepository.kt b/tests/shared/src/com/android/intentresolver/v2/data/repository/FakeUserRepository.kt new file mode 100644 index 00000000..5ed6f506 --- /dev/null +++ b/tests/shared/src/com/android/intentresolver/v2/data/repository/FakeUserRepository.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 com.android.intentresolver.v2.data.repository + +import com.android.intentresolver.v2.shared.model.User +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update + +/** A simple repository which can be initialized from a list and updated. */ +class FakeUserRepository(vararg userList: User) : UserRepository { + internal data class UserState(val user: User, val available: Boolean) + + private val userState = MutableStateFlow(userList.map { UserState(it, available = true) }) + + // Expose a List<User> from List<UserState> + override val users = userState.map { userList -> userList.map { it.user } } + + fun addUser(user: User, available: Boolean) { + require(userState.value.none { it.user.id == user.id }) { + "A User with ${user.id} already exists!" + } + userState.update { it + UserState(user, available) } + } + + fun removeUser(user: User) { + require(userState.value.any { it.user.id == user.id }) { + "A User with ${user.id} does not exist!" + } + userState.update { it.filterNot { state -> state.user.id == user.id } } + } + + override val availability = + userState.map { userStateList -> userStateList.associate { it.user to it.available } } + + override suspend fun requestState(user: User, available: Boolean) { + userState.update { userStateList -> + userStateList.map { userState -> + if (userState.user.id == user.id) { + UserState(user, available) + } else { + userState + } + } + } + } +} diff --git a/tests/unit/src/com/android/intentresolver/v2/data/repository/FakeUserRepositoryTest.kt b/tests/unit/src/com/android/intentresolver/v2/data/repository/FakeUserRepositoryTest.kt new file mode 100644 index 00000000..334f31ad --- /dev/null +++ b/tests/unit/src/com/android/intentresolver/v2/data/repository/FakeUserRepositoryTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 com.android.intentresolver.v2.data.repository + +import com.android.intentresolver.v2.coroutines.collectLastValue +import com.android.intentresolver.v2.shared.model.User +import com.google.common.truth.Truth.assertThat +import kotlin.random.Random +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class FakeUserRepositoryTest { + private val baseId = Random.nextInt(1000, 2000) + + private val personalUser = User(id = baseId, role = User.Role.PERSONAL) + private val cloneUser = User(id = baseId + 1, role = User.Role.CLONE) + private val workUser = User(id = baseId + 2, role = User.Role.WORK) + private val privateUser = User(id = baseId + 3, role = User.Role.PRIVATE) + + @Test + fun init() = runTest { + val repo = FakeUserRepository(personalUser, workUser, privateUser) + + val users by collectLastValue(repo.users) + assertThat(users).containsExactly(personalUser, workUser, privateUser) + } + + @Test + fun addUser() = runTest { + val repo = FakeUserRepository() + + val users by collectLastValue(repo.users) + assertThat(users).isEmpty() + + repo.addUser(personalUser, true) + assertThat(users).containsExactly(personalUser) + + repo.addUser(workUser, false) + assertThat(users).containsExactly(personalUser, workUser) + } + + @Test + fun removeUser() = runTest { + val repo = FakeUserRepository(personalUser, workUser) + + val users by collectLastValue(repo.users) + repo.removeUser(workUser) + assertThat(users).containsExactly(personalUser) + + repo.removeUser(personalUser) + assertThat(users).isEmpty() + } + + @Test + fun isAvailable_defaultValue() = runTest { + val repo = FakeUserRepository(personalUser, workUser) + + val available by collectLastValue(repo.availability) + + repo.requestState(workUser, false) + assertThat(available!![workUser]).isFalse() + + repo.requestState(workUser, true) + assertThat(available!![workUser]).isTrue() + } + + @Test + fun isAvailable() = runTest { + val repo = FakeUserRepository(personalUser, workUser) + + val available by collectLastValue(repo.availability) + assertThat(available!![workUser]).isTrue() + + repo.requestState(workUser, false) + assertThat(available!![workUser]).isFalse() + + repo.requestState(workUser, true) + assertThat(available!![workUser]).isTrue() + } + + @Test + fun isAvailable_addRemove() = runTest { + val repo = FakeUserRepository(personalUser, workUser) + + val available by collectLastValue(repo.availability) + assertThat(available!![workUser]).isTrue() + + repo.removeUser(workUser) + assertThat(available!![workUser]).isNull() + + repo.addUser(workUser, true) + assertThat(available!![workUser]).isTrue() + } +} diff --git a/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt b/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt index 77f47285..6c61dfd6 100644 --- a/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt +++ b/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt @@ -8,10 +8,10 @@ import android.os.UserHandle.USER_SYSTEM import android.os.UserManager import com.android.intentresolver.mock import com.android.intentresolver.v2.coroutines.collectLastValue -import com.android.intentresolver.v2.data.model.User -import com.android.intentresolver.v2.data.model.User.Role import com.android.intentresolver.v2.platform.FakeUserManager import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType +import com.android.intentresolver.v2.shared.model.User +import com.android.intentresolver.v2.shared.model.User.Role import com.android.intentresolver.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage @@ -66,30 +66,32 @@ internal class UserRepositoryImplTest { fun isAvailable() = runTest { val repo = createUserRepository(userManager) val work = userState.createProfile(ProfileType.WORK) + val workUser = User(work.identifier, Role.WORK) - val available by collectLastValue(repo.isAvailable(work)) - assertThat(available).isTrue() + val available by collectLastValue(repo.availability) + assertThat(available?.get(workUser)).isTrue() userState.setQuietMode(work, true) - assertThat(available).isFalse() + assertThat(available?.get(workUser)).isFalse() userState.setQuietMode(work, false) - assertThat(available).isTrue() + assertThat(available?.get(workUser)).isTrue() } @Test fun requestState() = runTest { val repo = createUserRepository(userManager) val work = userState.createProfile(ProfileType.WORK) + val workUser = User(work.identifier, Role.WORK) - val available by collectLastValue(repo.isAvailable(work)) - assertThat(available).isTrue() + val available by collectLastValue(repo.availability) + assertThat(available?.get(workUser)).isTrue() - repo.requestState(work, false) - assertThat(available).isFalse() + repo.requestState(workUser, false) + assertThat(available?.get(workUser)).isFalse() - repo.requestState(work, true) - assertThat(available).isTrue() + repo.requestState(workUser, true) + assertThat(available?.get(workUser)).isTrue() } @Test(expected = IllegalArgumentException::class) diff --git a/tests/unit/src/com/android/intentresolver/v2/domain/interactor/UserInteractorTest.kt b/tests/unit/src/com/android/intentresolver/v2/domain/interactor/UserInteractorTest.kt new file mode 100644 index 00000000..6fa055ef --- /dev/null +++ b/tests/unit/src/com/android/intentresolver/v2/domain/interactor/UserInteractorTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 com.android.intentresolver.v2.domain.interactor + +import com.android.intentresolver.v2.coroutines.collectLastValue +import com.android.intentresolver.v2.data.repository.FakeUserRepository +import com.android.intentresolver.v2.domain.model.Profile +import com.android.intentresolver.v2.domain.model.Profile.Type.PERSONAL +import com.android.intentresolver.v2.domain.model.Profile.Type.PRIVATE +import com.android.intentresolver.v2.domain.model.Profile.Type.WORK +import com.android.intentresolver.v2.shared.model.User +import com.android.intentresolver.v2.shared.model.User.Role +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlin.random.Random +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class UserInteractorTest { + private val baseId = Random.nextInt(1000, 2000) + + private val personalUser = User(id = baseId, role = Role.PERSONAL) + private val cloneUser = User(id = baseId + 1, role = Role.CLONE) + private val workUser = User(id = baseId + 2, role = Role.WORK) + private val privateUser = User(id = baseId + 3, role = Role.PRIVATE) + + @Test + fun launchedByProfile(): Unit = runTest { + val profileInteractor = + UserInteractor( + userRepository = FakeUserRepository(personalUser, cloneUser), + launchedAs = personalUser.handle + ) + + val launchedAsProfile by collectLastValue(profileInteractor.launchedAsProfile) + + assertThat(launchedAsProfile).isEqualTo(Profile(PERSONAL, personalUser, cloneUser)) + } + + @Test + fun launchedByProfile_asClone(): Unit = runTest { + val profileInteractor = + UserInteractor( + userRepository = FakeUserRepository(personalUser, cloneUser), + launchedAs = cloneUser.handle + ) + val profiles by collectLastValue(profileInteractor.launchedAsProfile) + + assertThat(profiles).isEqualTo(Profile(PERSONAL, personalUser, cloneUser)) + } + + @Test + fun profiles_withPersonal(): Unit = runTest { + val profileInteractor = + UserInteractor( + userRepository = FakeUserRepository(personalUser), + launchedAs = personalUser.handle + ) + + val profiles by collectLastValue(profileInteractor.profiles) + + assertThat(profiles).containsExactly(Profile(PERSONAL, personalUser)) + } + + @Test + fun profiles_addClone(): Unit = runTest { + val fakeUserRepo = FakeUserRepository(personalUser) + val profileInteractor = + UserInteractor(userRepository = fakeUserRepo, launchedAs = personalUser.handle) + + val profiles by collectLastValue(profileInteractor.profiles) + assertThat(profiles).containsExactly(Profile(PERSONAL, personalUser)) + + fakeUserRepo.addUser(cloneUser, available = true) + assertThat(profiles).containsExactly(Profile(PERSONAL, personalUser, cloneUser)) + } + + @Test + fun profiles_withPersonalAndClone(): Unit = runTest { + val profileInteractor = + UserInteractor( + userRepository = FakeUserRepository(personalUser, cloneUser), + launchedAs = personalUser.handle + ) + val profiles by collectLastValue(profileInteractor.profiles) + + assertThat(profiles).containsExactly(Profile(PERSONAL, personalUser, cloneUser)) + } + + @Test + fun profiles_withAllSupportedTypes(): Unit = runTest { + val profileInteractor = + UserInteractor( + userRepository = FakeUserRepository(personalUser, cloneUser, workUser, privateUser), + launchedAs = personalUser.handle + ) + val profiles by collectLastValue(profileInteractor.profiles) + + assertThat(profiles) + .containsExactly( + Profile(PERSONAL, personalUser, cloneUser), + Profile(WORK, workUser), + Profile(PRIVATE, privateUser) + ) + } + + @Test + fun profiles_preservesIterationOrder(): Unit = runTest { + val profileInteractor = + UserInteractor( + userRepository = FakeUserRepository(workUser, cloneUser, privateUser, personalUser), + launchedAs = personalUser.handle + ) + + val profiles by collectLastValue(profileInteractor.profiles) + + assertThat(profiles) + .containsExactly( + Profile(WORK, workUser), + Profile(PRIVATE, privateUser), + Profile(PERSONAL, personalUser, cloneUser), + ) + } + + @Test + fun isAvailable_defaultValue() = runTest { + val userRepo = FakeUserRepository(personalUser) + userRepo.addUser(workUser, false) + + val profileInteractor = + UserInteractor(userRepository = userRepo, launchedAs = personalUser.handle) + val personalAvailable by collectLastValue(profileInteractor.isAvailable(PERSONAL)) + val workAvailable by collectLastValue(profileInteractor.isAvailable(WORK)) + + assertWithMessage("personalAvailable").that(personalAvailable!!).isTrue() + + assertWithMessage("workAvailable").that(workAvailable!!).isFalse() + } + + @Test + fun isAvailable() = runTest { + val userRepo = FakeUserRepository(workUser, personalUser) + val profileInteractor = + UserInteractor(userRepository = userRepo, launchedAs = personalUser.handle) + val workAvailable by collectLastValue(profileInteractor.isAvailable(WORK)) + + // Default state is enabled in FakeUserManager + assertWithMessage("workAvailable").that(workAvailable).isTrue() + + // Making user unavailable makes profile unavailable + userRepo.requestState(workUser, false) + assertWithMessage("workAvailable").that(workAvailable).isFalse() + + // Making user available makes profile available again + userRepo.requestState(workUser, true) + assertWithMessage("workAvailable").that(workAvailable).isTrue() + + // When a user is removed availability should update to false + userRepo.removeUser(workUser) + assertWithMessage("workAvailable").that(workAvailable).isFalse() + } +} |