Skip to content

Commit

Permalink
Fix and add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Pururun committed Oct 30, 2024
1 parent 6af69a6 commit 5cbe233
Show file tree
Hide file tree
Showing 13 changed files with 666 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.constant.ContentType
import net.mullvad.mullvadvpn.compose.extensions.dropUnlessResumed
import net.mullvad.mullvadvpn.compose.state.RelayListType
import net.mullvad.mullvadvpn.compose.state.SearchSelectLocationUiState
import net.mullvad.mullvadvpn.compose.state.SearchLocationUiState
import net.mullvad.mullvadvpn.compose.transitions.SearchTransition
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately
Expand All @@ -75,7 +75,7 @@ import org.koin.androidx.compose.koinViewModel
@Preview
@Composable
private fun PreviewSearchLocationScreen() {
AppTheme { SearchLocationScreen(state = SearchSelectLocationUiState.NoQuery("", emptyList())) }
AppTheme { SearchLocationScreen(state = SearchLocationUiState.NoQuery("", emptyList())) }
}

data class SearchLocationNavArgs(val relayListType: RelayListType)
Expand Down Expand Up @@ -190,7 +190,7 @@ fun SearchLocation(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchLocationScreen(
state: SearchSelectLocationUiState,
state: SearchLocationUiState,
snackbarHostState: SnackbarHostState = SnackbarHostState(),
onSelectRelay: (RelayItem) -> Unit = {},
onToggleExpand: (RelayItemId, CustomListId?, Boolean) -> Unit = { _, _, _ -> },
Expand Down Expand Up @@ -262,10 +262,10 @@ fun SearchLocationScreen(
onRemoveProviderFilter = onRemoveProviderFilter,
)
when (state) {
is SearchSelectLocationUiState.NoQuery -> {
is SearchLocationUiState.NoQuery -> {
noQuery()
}
is SearchSelectLocationUiState.Content -> {
is SearchLocationUiState.Content -> {
relayListContent(
backgroundColor = backgroundColor,
customLists = state.customLists,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ package net.mullvad.mullvadvpn.compose.state
import net.mullvad.mullvadvpn.lib.model.RelayItem
import net.mullvad.mullvadvpn.usecase.FilterChip

sealed interface SearchSelectLocationUiState {
sealed interface SearchLocationUiState {
val searchTerm: String
val filterChips: List<FilterChip>

data class NoQuery(
override val searchTerm: String,
override val filterChips: List<FilterChip>,
) : SearchSelectLocationUiState
) : SearchLocationUiState

data class Content(
override val searchTerm: String,
override val filterChips: List<FilterChip>,
val relayListItems: List<RelayListItem>,
val customLists: List<RelayItem.CustomList>,
val relayListType: RelayListType,
) : SearchSelectLocationUiState
) : SearchLocationUiState
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository

typealias ModelOwnership = net.mullvad.mullvadvpn.lib.model.Ownership
typealias ModelOwnership = Ownership

class FilterChipUseCase(
private val relayListFilterRepository: RelayListFilterRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData
import net.mullvad.mullvadvpn.compose.state.RelayListType
import net.mullvad.mullvadvpn.compose.state.SearchSelectLocationUiState
import net.mullvad.mullvadvpn.compose.state.SearchLocationUiState
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.lib.model.RelayItem
Expand Down Expand Up @@ -57,7 +57,7 @@ class SearchLocationViewModel(
private val _searchTerm = MutableStateFlow(EMPTY_SEARCH_TERM)
private val _expandedItems = MutableStateFlow<Set<String>>(emptySet())

val uiState: StateFlow<SearchSelectLocationUiState> =
val uiState: StateFlow<SearchLocationUiState> =
combine(
_searchTerm,
searchRelayListLocations(),
Expand All @@ -75,7 +75,7 @@ class SearchLocationViewModel(
filterChips,
expandedItems ->
if (searchTerm.length >= MIN_SEARCH_LENGTH) {
SearchSelectLocationUiState.Content(
SearchLocationUiState.Content(
searchTerm = searchTerm,
relayListItems =
relayListItems(
Expand All @@ -97,13 +97,13 @@ class SearchLocationViewModel(
filterChips = filterChips,
)
} else {
SearchSelectLocationUiState.NoQuery(searchTerm, filterChips)
SearchLocationUiState.NoQuery(searchTerm, filterChips)
}
}
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
SearchSelectLocationUiState.NoQuery("", emptyList()),
SearchLocationUiState.NoQuery("", emptyList()),
)

private val _uiSideEffect = Channel<SearchLocationSideEffect>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class SelectLocationViewModel(
SelectLocationUiState(
filterChips = emptyList(),
multihopEnabled = false,
relayListType = RelayListType.ENTRY,
relayListType = RelayListType.EXIT,
),
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package net.mullvad.mullvadvpn.usecase

import androidx.compose.material3.FilterChip
import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.assertLists
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.Ownership
import net.mullvad.mullvadvpn.lib.model.Provider
import net.mullvad.mullvadvpn.lib.model.ProviderId
import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.lib.model.Settings
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class FilterChipUseCaseTest {

private val mockRelayListFilterRepository: RelayListFilterRepository = mockk()
private val mockAvailableProvidersUseCase: AvailableProvidersUseCase = mockk()
private val mockSettingRepository: SettingsRepository = mockk()

private val selectedOwnership = MutableStateFlow<Constraint<Ownership>>(Constraint.Any)
private val selectedProviders = MutableStateFlow<Constraint<Providers>>(Constraint.Any)
private val availableProviders = MutableStateFlow<List<Provider>>(emptyList())
private val settings = MutableStateFlow<Settings>(mockk(relaxed = true))

private lateinit var filterChipUseCase: FilterChipUseCase

@BeforeEach
fun setUp() {
every { mockRelayListFilterRepository.selectedOwnership } returns selectedOwnership
every { mockRelayListFilterRepository.selectedProviders } returns selectedProviders
every { mockAvailableProvidersUseCase() } returns availableProviders
every { mockSettingRepository.settingsUpdates } returns settings

filterChipUseCase =
FilterChipUseCase(
relayListFilterRepository = mockRelayListFilterRepository,
availableProvidersUseCase = mockAvailableProvidersUseCase,
settingsRepository = mockSettingRepository,
)
}

@Test
fun `when no filters are applied should return empty list`() = runTest {
filterChipUseCase().test { assertLists(emptyList(), awaitItem()) }
}

@Test
fun `when ownership filter is applied should return correct ownership`() = runTest {
// Arrange
val expectedOwnership = Ownership.MullvadOwned
selectedOwnership.value = Constraint.Only(expectedOwnership)

filterChipUseCase().test {
assertLists(listOf(FilterChip.Ownership(expectedOwnership)), awaitItem())
}
}

@Test
fun `when provider filter is applied should return correct number of providers`() = runTest {
// Arrange
val expectedProviders = Providers(providers = setOf(ProviderId("1"), ProviderId("2")))
selectedProviders.value = Constraint.Only(expectedProviders)
availableProviders.value =
listOf(
Provider(ProviderId("1"), Ownership.MullvadOwned),
Provider(ProviderId("2"), Ownership.Rented),
)

filterChipUseCase().test { assertLists(listOf(FilterChip.Provider(2)), awaitItem()) }
}

@Test
fun `when provider and ownership filter is applied should return correct filter chips`() =
runTest {
// Arrange
val expectedProviders = Providers(providers = setOf(ProviderId("1")))
val expectedOwnership = Ownership.MullvadOwned
selectedProviders.value = Constraint.Only(expectedProviders)
selectedOwnership.value = Constraint.Only(expectedOwnership)
availableProviders.value =
listOf(
Provider(ProviderId("1"), Ownership.MullvadOwned),
Provider(ProviderId("2"), Ownership.Rented),
)

filterChipUseCase().test {
assertLists(
listOf(FilterChip.Ownership(expectedOwnership), FilterChip.Provider(1)),
awaitItem(),
)
}
}

@Test
fun `when Daita is enabled should return Daita filter chip`() = runTest {
// Arrange
settings.value = mockk(relaxed = true) { every { isDaitaEnabled() } returns true }

filterChipUseCase().test { assertLists(listOf(FilterChip.Daita), awaitItem()) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.mullvad.mullvadvpn.usecase

import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.GeoLocationId
import net.mullvad.mullvadvpn.lib.model.RelayItemId
import net.mullvad.mullvadvpn.lib.model.RelayItemSelection
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
import net.mullvad.mullvadvpn.repository.RelayListRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class SelectedLocationUseCaseTest {
private val mockRelayListRepository: RelayListRepository = mockk()
private val mockWireguardConstraintsRepository: WireguardConstraintsRepository = mockk()

private val selectedLocation = MutableStateFlow<Constraint<RelayItemId>>(Constraint.Any)
private val wireguardConstraints = MutableStateFlow<WireguardConstraints>(mockk(relaxed = true))

private lateinit var selectLocationUseCase: SelectedLocationUseCase

@BeforeEach
fun setup() {
every { mockRelayListRepository.selectedLocation } returns selectedLocation
every { mockWireguardConstraintsRepository.wireguardConstraints } returns
wireguardConstraints

selectLocationUseCase =
SelectedLocationUseCase(
relayListRepository = mockRelayListRepository,
wireguardConstraintsRepository = mockWireguardConstraintsRepository,
)
}

@Test
fun `when wireguard constraints is multihop enabled should return Multiple`() = runTest {
// Arrange
val entryLocation: Constraint<RelayItemId> = Constraint.Only(GeoLocationId.Country("se"))
val exitLocation = Constraint.Only(GeoLocationId.Country("us"))
wireguardConstraints.value =
WireguardConstraints(
isMultihopEnabled = true,
entryLocation = entryLocation,
port = Constraint.Any,
)
selectedLocation.value = exitLocation

// Act, Assert
selectLocationUseCase().test {
assertEquals(RelayItemSelection.Multiple(entryLocation, exitLocation), awaitItem())
}
}

@Test
fun `when wireguard constraints is multihop disabled should return Single`() = runTest {
// Arrange
val exitLocation = Constraint.Only(GeoLocationId.Country("us"))
selectedLocation.value = exitLocation

// Act, Assert
selectLocationUseCase().test {
assertEquals(RelayItemSelection.Single(exitLocation), awaitItem())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package net.mullvad.mullvadvpn.viewmodel

import app.cash.turbine.test
import arrow.core.Either
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(TestCoroutineRule::class)
class MultihopViewModelTest {

private val mockWireguardConstraintsRepository: WireguardConstraintsRepository = mockk()

private val wireguardConstraints = MutableStateFlow<WireguardConstraints>(mockk(relaxed = true))

private lateinit var multihopViewModel: MultihopViewModel

@BeforeEach
fun setUp() {
every { mockWireguardConstraintsRepository.wireguardConstraints } returns
wireguardConstraints

multihopViewModel =
MultihopViewModel(wireguardConstraintsRepository = mockWireguardConstraintsRepository)
}

@Test
fun `default state should be multihop disabled`() {
assertEquals(false, multihopViewModel.uiState.value.enable)
}

@Test
fun `when multihop enabled is true state should return multihop enabled true`() = runTest {
// Arrange
wireguardConstraints.value =
WireguardConstraints(
isMultihopEnabled = true,
entryLocation = Constraint.Any,
port = Constraint.Any,
)

// Act, Assert
multihopViewModel.uiState.test { assertEquals(MultihopUiState(true), awaitItem()) }
}

@Test
fun `when set multihop is called should call repository set multihop`() = runTest {
// Arrange
coEvery { mockWireguardConstraintsRepository.setMultihop(any()) } returns Either.Right(Unit)

// Act
multihopViewModel.setMultihop(true)

// Assert
coVerify { mockWireguardConstraintsRepository.setMultihop(true) }
}
}
Loading

0 comments on commit 5cbe233

Please sign in to comment.