Skip to content

Commit f57cb82

Browse files
Merge pull request #8171 from woocommerce/issue/8110-unit-tests
[REST API] unit tests for site credentials
2 parents bad1d31 + a2e4088 commit f57cb82

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package com.woocommerce.android.ui.login.sitecredentials
2+
3+
import androidx.lifecycle.SavedStateHandle
4+
import com.woocommerce.android.OnChangedException
5+
import com.woocommerce.android.R
6+
import com.woocommerce.android.applicationpasswords.ApplicationPasswordsNotifier
7+
import com.woocommerce.android.tools.SelectedSite
8+
import com.woocommerce.android.ui.common.UserEligibilityFetcher
9+
import com.woocommerce.android.ui.login.WPApiSiteRepository
10+
import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.LoggedIn
11+
import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.ShowApplicationPasswordsUnavailableScreen
12+
import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.ShowNonWooErrorScreen
13+
import com.woocommerce.android.util.observeForTesting
14+
import com.woocommerce.android.util.runAndCaptureValues
15+
import com.woocommerce.android.viewmodel.BaseUnitTest
16+
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar
17+
import com.woocommerce.android.viewmodel.ResourceProvider
18+
import kotlinx.coroutines.ExperimentalCoroutinesApi
19+
import kotlinx.coroutines.flow.MutableSharedFlow
20+
import org.assertj.core.api.Assertions.assertThat
21+
import org.junit.Test
22+
import org.mockito.kotlin.any
23+
import org.mockito.kotlin.doReturn
24+
import org.mockito.kotlin.eq
25+
import org.mockito.kotlin.mock
26+
import org.mockito.kotlin.verify
27+
import org.mockito.kotlin.whenever
28+
import org.wordpress.android.fluxc.model.SiteModel
29+
import org.wordpress.android.fluxc.model.user.WCUserModel
30+
import org.wordpress.android.fluxc.network.rest.wpapi.WPAPINetworkError
31+
import org.wordpress.android.fluxc.store.AccountStore.AuthenticationError
32+
import org.wordpress.android.fluxc.store.AccountStore.AuthenticationErrorType.INCORRECT_USERNAME_OR_PASSWORD
33+
import org.wordpress.android.login.LoginAnalyticsListener
34+
35+
@ExperimentalCoroutinesApi
36+
class LoginSiteCredentialsViewModelTest : BaseUnitTest() {
37+
private val testUsername = "username"
38+
private val testPassword = "password"
39+
private val siteAddress: String = "http://site.com"
40+
private val testSite = SiteModel()
41+
private val testUser = WCUserModel()
42+
private val applicationPasswordsUnavailableEvents = MutableSharedFlow<WPAPINetworkError>(extraBufferCapacity = 1)
43+
44+
private val wpApiSiteRepository: WPApiSiteRepository = mock {
45+
onBlocking { login(eq(siteAddress), any(), any()) } doReturn Result.success(testSite)
46+
onBlocking { checkWooStatus(testSite) } doReturn Result.success(true)
47+
onBlocking { getSiteByUrl(siteAddress) } doReturn testSite
48+
}
49+
private val selectedSite: SelectedSite = mock {
50+
on { exists() } doReturn false
51+
}
52+
private val applicationPasswordsNotifier: ApplicationPasswordsNotifier = mock {
53+
on { featureUnavailableEvents } doReturn applicationPasswordsUnavailableEvents
54+
}
55+
private val userEligibilityFetcher: UserEligibilityFetcher = mock {
56+
onBlocking { fetchUserInfo() } doReturn testUser
57+
}
58+
private val loginAnalyticsListener: LoginAnalyticsListener = mock()
59+
private val resourceProvider: ResourceProvider = mock()
60+
61+
private lateinit var viewModel: LoginSiteCredentialsViewModel
62+
63+
suspend fun setup(prepareMocks: suspend () -> Unit = {}) {
64+
prepareMocks()
65+
66+
viewModel = LoginSiteCredentialsViewModel(
67+
savedStateHandle = SavedStateHandle(mapOf(LoginSiteCredentialsViewModel.SITE_ADDRESS_KEY to siteAddress)),
68+
wpApiSiteRepository = wpApiSiteRepository,
69+
selectedSite = selectedSite,
70+
loginAnalyticsListener = loginAnalyticsListener,
71+
resourceProvider = resourceProvider,
72+
applicationPasswordsNotifier = applicationPasswordsNotifier,
73+
userEligibilityFetcher = userEligibilityFetcher
74+
)
75+
}
76+
77+
@Test
78+
fun `when changing username, then update state`() = testBlocking {
79+
setup()
80+
81+
val state = viewModel.state.runAndCaptureValues {
82+
viewModel.onUsernameChanged(testUsername)
83+
}.last()
84+
85+
assertThat(state.username).isEqualTo(testUsername)
86+
}
87+
88+
@Test
89+
fun `when changing password, then update state`() = testBlocking {
90+
setup()
91+
92+
val state = viewModel.state.runAndCaptureValues {
93+
viewModel.onUsernameChanged(testPassword)
94+
}.last()
95+
96+
assertThat(state.username).isEqualTo(testPassword)
97+
}
98+
99+
@Test
100+
fun `when username is empty, then mark input as invalid`() = testBlocking {
101+
setup()
102+
103+
val state = viewModel.state.runAndCaptureValues {
104+
viewModel.onUsernameChanged("")
105+
}.last()
106+
107+
assertThat(state.isValid).isFalse()
108+
}
109+
110+
@Test
111+
fun `when password is empty, then mark input as invalid`() = testBlocking {
112+
setup()
113+
114+
val state = viewModel.state.runAndCaptureValues {
115+
viewModel.onPasswordChanged("")
116+
}.last()
117+
118+
assertThat(state.isValid).isFalse()
119+
}
120+
121+
@Test
122+
fun `given login successful, when submitting login, then log the user successfully`() = testBlocking {
123+
setup()
124+
125+
viewModel.state.observeForTesting {
126+
viewModel.onUsernameChanged(testUsername)
127+
viewModel.onPasswordChanged(testPassword)
128+
viewModel.onContinueClick()
129+
}
130+
131+
assertThat(viewModel.event.value).isEqualTo(LoggedIn(testSite.id))
132+
}
133+
134+
@Test
135+
fun `given incorrect credentials, when submitting, then show error`() = testBlocking {
136+
setup {
137+
whenever(wpApiSiteRepository.login(siteAddress, testUsername, testPassword)).thenReturn(
138+
Result.failure(OnChangedException(AuthenticationError(INCORRECT_USERNAME_OR_PASSWORD, "")))
139+
)
140+
}
141+
142+
val state = viewModel.state.runAndCaptureValues {
143+
viewModel.onUsernameChanged(testUsername)
144+
viewModel.onPasswordChanged(testPassword)
145+
viewModel.onContinueClick()
146+
}.last()
147+
148+
assertThat(state.errorMessage).isEqualTo(R.string.username_or_password_incorrect)
149+
}
150+
151+
@Test
152+
fun `given site without Woo, when submitting, then show error screen`() = testBlocking {
153+
setup {
154+
whenever(wpApiSiteRepository.checkWooStatus(testSite)).thenReturn(Result.success(false))
155+
}
156+
157+
viewModel.state.observeForTesting {
158+
viewModel.onUsernameChanged(testUsername)
159+
viewModel.onPasswordChanged(testPassword)
160+
viewModel.onContinueClick()
161+
}
162+
163+
assertThat(viewModel.event.value).isEqualTo(ShowNonWooErrorScreen(siteAddress))
164+
}
165+
166+
@Test
167+
fun `given Woo check fails, when submitting, then show snackbar`() = testBlocking {
168+
setup {
169+
whenever(wpApiSiteRepository.checkWooStatus(testSite)).thenReturn(Result.failure(Exception()))
170+
}
171+
172+
viewModel.state.observeForTesting {
173+
viewModel.onUsernameChanged(testUsername)
174+
viewModel.onPasswordChanged(testPassword)
175+
viewModel.onContinueClick()
176+
}
177+
178+
assertThat(viewModel.event.value).isEqualTo(ShowSnackbar(R.string.error_generic))
179+
}
180+
181+
@Test
182+
fun `given site without Woo, when attempting Woo installation, then recheck woo status`() = testBlocking {
183+
setup {
184+
whenever(wpApiSiteRepository.getSiteByUrl(siteAddress)).thenReturn(testSite)
185+
}
186+
187+
viewModel.state.observeForTesting {
188+
viewModel.onWooInstallationAttempted()
189+
}
190+
191+
verify(wpApiSiteRepository).checkWooStatus(testSite)
192+
}
193+
194+
@Test
195+
fun `given application passwords disabled, when submitting login, then show error screen`() = testBlocking {
196+
setup {
197+
whenever(wpApiSiteRepository.checkWooStatus(testSite)).thenReturn(Result.failure(Exception()))
198+
}
199+
200+
viewModel.state.observeForTesting {
201+
viewModel.onUsernameChanged(testUsername)
202+
viewModel.onPasswordChanged(testPassword)
203+
viewModel.onContinueClick()
204+
applicationPasswordsUnavailableEvents.tryEmit(mock())
205+
}
206+
207+
assertThat(viewModel.event.value).isEqualTo(ShowApplicationPasswordsUnavailableScreen(siteAddress))
208+
}
209+
210+
@Test
211+
fun `give user role fetch fails, when submitting login, then show a snackbar`() = testBlocking {
212+
setup {
213+
whenever(userEligibilityFetcher.fetchUserInfo()).thenReturn(null)
214+
}
215+
216+
viewModel.state.observeForTesting {
217+
viewModel.onUsernameChanged(testUsername)
218+
viewModel.onPasswordChanged(testPassword)
219+
viewModel.onContinueClick()
220+
}
221+
222+
assertThat(viewModel.event.value).isEqualTo(ShowSnackbar(R.string.error_generic))
223+
}
224+
}

0 commit comments

Comments
 (0)