Test for Oidc
This commit is contained in:
parent
b08021f1d9
commit
e8c24b65d1
9 changed files with 365 additions and 24 deletions
|
|
@ -51,25 +51,27 @@ class OidcPresenter @AssistedInject constructor(
|
||||||
fun handleCancel() {
|
fun handleCancel() {
|
||||||
requestState = Async.Loading()
|
requestState = Async.Loading()
|
||||||
localCoroutineScope.launch {
|
localCoroutineScope.launch {
|
||||||
requestState = try {
|
authenticationService.cancelOidcLogin()
|
||||||
authenticationService.cancelOidcLogin()
|
.fold(
|
||||||
// Then go back
|
onSuccess = {
|
||||||
Async.Success(Unit)
|
// Then go back
|
||||||
} catch (throwable: Throwable) {
|
requestState = Async.Success(Unit)
|
||||||
Async.Failure(throwable)
|
},
|
||||||
}
|
onFailure = {
|
||||||
|
requestState = Async.Failure(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleSuccess(url: String) {
|
fun handleSuccess(url: String) {
|
||||||
requestState = Async.Loading()
|
requestState = Async.Loading()
|
||||||
localCoroutineScope.launch {
|
localCoroutineScope.launch {
|
||||||
try {
|
authenticationService.loginWithOidc(url)
|
||||||
authenticationService.loginWithOidc(url)
|
.onFailure {
|
||||||
// Then the node tree will be updated, there is nothing to do
|
requestState = Async.Failure(it)
|
||||||
} catch (throwable: Throwable) {
|
}
|
||||||
requestState = Async.Failure(throwable)
|
// On success, the node tree will be updated, there is nothing to do
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class OidcUrlParser {
|
||||||
* Return a OidcAction, or null if the url is not a OidcUrl
|
* Return a OidcAction, or null if the url is not a OidcUrl
|
||||||
*/
|
*/
|
||||||
fun parse(url: String): OidcAction? {
|
fun parse(url: String): OidcAction? {
|
||||||
if (!url.startsWith(OidcConfig.redirectUri)) return null
|
if (url.startsWith(OidcConfig.redirectUri).not()) return null
|
||||||
if (url.contains("error=access_denied")) return OidcAction.GoBack
|
if (url.contains("error=access_denied")) return OidcAction.GoBack
|
||||||
if (url.contains("code=")) return OidcAction.Success(url)
|
if (url.contains("code=")) return OidcAction.Success(url)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,10 +97,12 @@ class LoginRootPresenter @Inject constructor(
|
||||||
homeserver: String,
|
homeserver: String,
|
||||||
state: MutableState<Async<MatrixHomeServerDetails>>,
|
state: MutableState<Async<MatrixHomeServerDetails>>,
|
||||||
) = launch {
|
) = launch {
|
||||||
state.value = Async.Loading()
|
|
||||||
suspend {
|
suspend {
|
||||||
authenticationService.setHomeserver(homeserver)
|
authenticationService.setHomeserver(homeserver)
|
||||||
authenticationService.getHomeserverDetails().value!!
|
.map {
|
||||||
|
authenticationService.getHomeserverDetails().value!!
|
||||||
|
}
|
||||||
|
.getOrThrow()
|
||||||
}.execute(state)
|
}.execute(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ import app.cash.molecule.RecompositionClock
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.login.impl.util.LoginConstants
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||||
|
|
@ -39,7 +39,7 @@ class ChangeServerPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER_URL)
|
assertThat(initialState.homeserver).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL)
|
||||||
assertThat(initialState.submitEnabled).isTrue()
|
assertThat(initialState.submitEnabled).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.oidc
|
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionClock
|
||||||
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
|
import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA
|
||||||
|
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class OidcPresenterTest {
|
||||||
|
@Test
|
||||||
|
fun `present - initial state`() = runTest {
|
||||||
|
val presenter = OidcPresenter(
|
||||||
|
A_OIDC_DATA,
|
||||||
|
FakeAuthenticationService(),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.oidcDetails).isEqualTo(A_OIDC_DATA)
|
||||||
|
assertThat(initialState.requestState).isEqualTo(Async.Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - go back`() = runTest {
|
||||||
|
val presenter = OidcPresenter(
|
||||||
|
A_OIDC_DATA,
|
||||||
|
FakeAuthenticationService(),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink.invoke(OidcEvents.Cancel)
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.requestState).isEqualTo(Async.Loading<Unit>())
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.requestState).isEqualTo(Async.Success(Unit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - go back with failure`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
|
val presenter = OidcPresenter(
|
||||||
|
A_OIDC_DATA,
|
||||||
|
authenticationService,
|
||||||
|
)
|
||||||
|
authenticationService.givenOidcCancelError(A_THROWABLE)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink.invoke(OidcEvents.Cancel)
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.requestState).isEqualTo(Async.Loading<Unit>())
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.requestState).isEqualTo(Async.Failure<Unit>(A_THROWABLE))
|
||||||
|
// Note: in real life I do not think this can happen, and the app should not block the user.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - user cancels from webview`() = runTest {
|
||||||
|
val presenter = OidcPresenter(
|
||||||
|
A_OIDC_DATA,
|
||||||
|
FakeAuthenticationService(),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.GoBack))
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.requestState).isEqualTo(Async.Loading<Unit>())
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.requestState).isEqualTo(Async.Success(Unit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - login success`() = runTest {
|
||||||
|
val presenter = OidcPresenter(
|
||||||
|
A_OIDC_DATA,
|
||||||
|
FakeAuthenticationService(),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL")))
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.requestState).isEqualTo(Async.Loading<Unit>())
|
||||||
|
// In this case, no success, the session is created and the node get destroyed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - login error`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
|
val presenter = OidcPresenter(
|
||||||
|
A_OIDC_DATA,
|
||||||
|
authenticationService,
|
||||||
|
)
|
||||||
|
authenticationService.givenLoginError(A_THROWABLE)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL")))
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.requestState).isEqualTo(Async.Loading<Unit>())
|
||||||
|
val errorState = awaitItem()
|
||||||
|
assertThat(errorState.requestState).isEqualTo(Async.Failure<Unit>(A_THROWABLE))
|
||||||
|
errorState.eventSink.invoke(OidcEvents.ClearError)
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.requestState).isEqualTo(Async.Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.login.impl.oidc
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class OidcUrlParserTest {
|
||||||
|
@Test
|
||||||
|
fun `test empty url`() {
|
||||||
|
val sut = OidcUrlParser()
|
||||||
|
assertThat(sut.parse("")).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test regular url`() {
|
||||||
|
val sut = OidcUrlParser()
|
||||||
|
assertThat(sut.parse("https://matrix.org")).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test cancel url`() {
|
||||||
|
val sut = OidcUrlParser()
|
||||||
|
val aCancelUrl = OidcConfig.redirectUri + "?error=access_denied&state=IFF1UETGye2ZA8pO"
|
||||||
|
assertThat(sut.parse(aCancelUrl)).isEqualTo(OidcAction.GoBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test success url`() {
|
||||||
|
val sut = OidcUrlParser()
|
||||||
|
val aSuccessUrl = OidcConfig.redirectUri + "?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||||
|
assertThat(sut.parse(aSuccessUrl)).isEqualTo(OidcAction.Success(aSuccessUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test unknown url`() {
|
||||||
|
val sut = OidcUrlParser()
|
||||||
|
val anUnknownUrl = OidcConfig.redirectUri + "?state=IFF1UETGye2ZA8pO&goat=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||||
|
Assert.assertThrows(IllegalStateException::class.java) {
|
||||||
|
assertThat(sut.parse(anUnknownUrl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,11 +20,16 @@ import app.cash.molecule.RecompositionClock
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.login.impl.util.LoginConstants
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||||
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC
|
||||||
import io.element.android.libraries.matrix.test.A_PASSWORD
|
import io.element.android.libraries.matrix.test.A_PASSWORD
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -39,18 +44,79 @@ class LoginRootPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.homeserverDetails).isEqualTo(A_HOMESERVER)
|
assertThat(initialState.homeserverUrl).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL)
|
||||||
|
assertThat(initialState.homeserverDetails).isEqualTo(Async.Uninitialized)
|
||||||
assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn)
|
assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn)
|
||||||
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
||||||
assertThat(initialState.submitEnabled).isFalse()
|
assertThat(initialState.submitEnabled).isFalse()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - initial state server load`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
|
val presenter = LoginRootPresenter(
|
||||||
|
authenticationService,
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.homeserverUrl).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL)
|
||||||
|
assertThat(initialState.homeserverDetails).isEqualTo(Async.Uninitialized)
|
||||||
|
assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn)
|
||||||
|
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
||||||
|
assertThat(initialState.submitEnabled).isFalse()
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.homeserverDetails).isEqualTo(Async.Loading<MatrixHomeServerDetails>())
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||||
|
skipItems(1)
|
||||||
|
val loadedState = awaitItem()
|
||||||
|
assertThat(loadedState.homeserverDetails).isEqualTo(Async.Success(A_HOMESERVER))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - initial state server load error and retry`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
|
val presenter = LoginRootPresenter(
|
||||||
|
authenticationService,
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.homeserverUrl).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL)
|
||||||
|
assertThat(initialState.homeserverDetails).isEqualTo(Async.Uninitialized)
|
||||||
|
assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn)
|
||||||
|
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
||||||
|
assertThat(initialState.submitEnabled).isFalse()
|
||||||
|
val loadingState = awaitItem()
|
||||||
|
assertThat(loadingState.homeserverDetails).isEqualTo(Async.Loading<MatrixHomeServerDetails>())
|
||||||
|
val aThrowable = Throwable("Error")
|
||||||
|
authenticationService.givenChangeServerError(aThrowable)
|
||||||
|
val errorState = awaitItem()
|
||||||
|
assertThat(errorState.homeserverDetails).isEqualTo(Async.Failure<MatrixHomeServerDetails>(aThrowable))
|
||||||
|
// Retry
|
||||||
|
errorState.eventSink.invoke(LoginRootEvents.RetryFetchServerInfo)
|
||||||
|
val loadingState2 = awaitItem()
|
||||||
|
assertThat(loadingState2.homeserverDetails).isEqualTo(Async.Loading<MatrixHomeServerDetails>())
|
||||||
|
authenticationService.givenChangeServerError(null)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||||
|
skipItems(1)
|
||||||
|
val loadedState = awaitItem()
|
||||||
|
assertThat(loadedState.homeserverDetails).isEqualTo(Async.Success(A_HOMESERVER))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enter login and password`() = runTest {
|
fun `present - enter login and password`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
val presenter = LoginRootPresenter(
|
val presenter = LoginRootPresenter(
|
||||||
FakeAuthenticationService(),
|
authenticationService,
|
||||||
)
|
)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -67,10 +133,49 @@ class LoginRootPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - submit`() = runTest {
|
fun `present - oidc login`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
val presenter = LoginRootPresenter(
|
val presenter = LoginRootPresenter(
|
||||||
FakeAuthenticationService(),
|
authenticationService,
|
||||||
)
|
)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.submitEnabled).isTrue()
|
||||||
|
initialState.eventSink.invoke(LoginRootEvents.Submit)
|
||||||
|
val oidcState = awaitItem()
|
||||||
|
assertThat(oidcState.loggedInState).isEqualTo(LoggedInState.OidcStarted(A_OIDC_DATA))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - oidc login error`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
|
val presenter = LoginRootPresenter(
|
||||||
|
authenticationService,
|
||||||
|
)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||||
|
authenticationService.givenOidcError(A_THROWABLE)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.submitEnabled).isTrue()
|
||||||
|
initialState.eventSink.invoke(LoginRootEvents.Submit)
|
||||||
|
val oidcState = awaitItem()
|
||||||
|
assertThat(oidcState.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - submit`() = runTest {
|
||||||
|
val authenticationService = FakeAuthenticationService()
|
||||||
|
val presenter = LoginRootPresenter(
|
||||||
|
authenticationService,
|
||||||
|
)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -93,6 +198,7 @@ class LoginRootPresenterTest {
|
||||||
val presenter = LoginRootPresenter(
|
val presenter = LoginRootPresenter(
|
||||||
authenticationService,
|
authenticationService,
|
||||||
)
|
)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -116,11 +222,11 @@ class LoginRootPresenterTest {
|
||||||
val presenter = LoginRootPresenter(
|
val presenter = LoginRootPresenter(
|
||||||
authenticationService,
|
authenticationService,
|
||||||
)
|
)
|
||||||
|
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
// Submit will return an error
|
// Submit will return an error
|
||||||
authenticationService.givenLoginError(A_THROWABLE)
|
authenticationService.givenLoginError(A_THROWABLE)
|
||||||
initialState.eventSink(LoginRootEvents.Submit)
|
initialState.eventSink(LoginRootEvents.Submit)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ const val ANOTHER_MESSAGE = "Hello universe!"
|
||||||
const val A_HOMESERVER_URL = "matrix.org"
|
const val A_HOMESERVER_URL = "matrix.org"
|
||||||
const val A_HOMESERVER_URL_2 = "matrix-client.org"
|
const val A_HOMESERVER_URL_2 = "matrix-client.org"
|
||||||
|
|
||||||
val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, true, null)
|
val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = true, supportsOidc = false)
|
||||||
|
val A_HOMESERVER_OIDC = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = false, supportsOidc = true)
|
||||||
|
|
||||||
const val AN_AVATAR_URL = "mxc://data"
|
const val AN_AVATAR_URL = "mxc://data"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test.auth
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
@ -27,8 +28,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
|
val A_OIDC_DATA = OidcDetails(url = "a-url")
|
||||||
|
|
||||||
class FakeAuthenticationService : MatrixAuthenticationService {
|
class FakeAuthenticationService : MatrixAuthenticationService {
|
||||||
private var homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
private var homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||||
|
private var oidcError: Throwable? = null
|
||||||
|
private var oidcCancelError: Throwable? = null
|
||||||
private var loginError: Throwable? = null
|
private var loginError: Throwable? = null
|
||||||
private var changeServerError: Throwable? = null
|
private var changeServerError: Throwable? = null
|
||||||
|
|
||||||
|
|
@ -62,6 +67,27 @@ class FakeAuthenticationService : MatrixAuthenticationService {
|
||||||
return loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID)
|
return loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getOidcUrl(): Result<OidcDetails> {
|
||||||
|
return oidcError?.let { Result.failure(it) } ?: Result.success(A_OIDC_DATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun cancelOidcLogin(): Result<Unit> {
|
||||||
|
return oidcCancelError?.let { Result.failure(it) } ?: Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loginWithOidc(callbackUrl: String): Result<SessionId> {
|
||||||
|
delay(100)
|
||||||
|
return loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenOidcError(throwable: Throwable?) {
|
||||||
|
oidcError = throwable
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenOidcCancelError(throwable: Throwable?) {
|
||||||
|
oidcCancelError = throwable
|
||||||
|
}
|
||||||
|
|
||||||
fun givenLoginError(throwable: Throwable?) {
|
fun givenLoginError(throwable: Throwable?) {
|
||||||
loginError = throwable
|
loginError = throwable
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue