adding tests around login reducer
This commit is contained in:
parent
821c317916
commit
27e29ebd34
|
@ -16,6 +16,9 @@ dependencies {
|
||||||
implementation project(":design-library")
|
implementation project(":design-library")
|
||||||
implementation project(":core")
|
implementation project(":core")
|
||||||
|
|
||||||
|
kotlinTest(it)
|
||||||
|
|
||||||
testImplementation 'screen-state:state-test'
|
testImplementation 'screen-state:state-test'
|
||||||
testImplementation 'chat-engine:chat-engine-test'
|
testImplementation 'chat-engine:chat-engine-test'
|
||||||
|
androidImportFixturesWorkaround(project, project(":core"))
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ import app.dapk.st.core.ProvidableModule
|
||||||
import app.dapk.st.core.extensions.ErrorTracker
|
import app.dapk.st.core.extensions.ErrorTracker
|
||||||
import app.dapk.st.engine.ChatEngine
|
import app.dapk.st.engine.ChatEngine
|
||||||
import app.dapk.st.login.state.LoginState
|
import app.dapk.st.login.state.LoginState
|
||||||
|
import app.dapk.st.login.state.LoginUseCase
|
||||||
import app.dapk.st.login.state.loginReducer
|
import app.dapk.st.login.state.loginReducer
|
||||||
import app.dapk.st.push.PushModule
|
import app.dapk.st.push.PushModule
|
||||||
import app.dapk.st.state.createStateViewModel
|
import app.dapk.st.state.createStateViewModel
|
||||||
|
@ -16,7 +17,8 @@ class LoginModule(
|
||||||
|
|
||||||
fun loginState(): LoginState {
|
fun loginState(): LoginState {
|
||||||
return createStateViewModel {
|
return createStateViewModel {
|
||||||
loginReducer(chatEngine, pushModule.pushTokenRegistrar(), errorTracker, it)
|
val loginUseCase = LoginUseCase(chatEngine, pushModule.pushTokenRegistrars(), errorTracker)
|
||||||
|
loginReducer(loginUseCase, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,22 +1,15 @@
|
||||||
package app.dapk.st.login.state
|
package app.dapk.st.login.state
|
||||||
|
|
||||||
import app.dapk.st.core.extensions.ErrorTracker
|
|
||||||
import app.dapk.st.core.logP
|
import app.dapk.st.core.logP
|
||||||
import app.dapk.st.engine.ChatEngine
|
|
||||||
import app.dapk.st.engine.LoginRequest
|
import app.dapk.st.engine.LoginRequest
|
||||||
import app.dapk.st.engine.LoginResult
|
import app.dapk.st.engine.LoginResult
|
||||||
import app.dapk.st.push.PushTokenRegistrar
|
|
||||||
import app.dapk.state.async
|
import app.dapk.state.async
|
||||||
import app.dapk.state.change
|
import app.dapk.state.change
|
||||||
import app.dapk.state.createReducer
|
import app.dapk.state.createReducer
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
fun loginReducer(
|
fun loginReducer(
|
||||||
chatEngine: ChatEngine,
|
loginUseCase: LoginUseCase,
|
||||||
pushTokenRegistrar: PushTokenRegistrar,
|
|
||||||
errorTracker: ErrorTracker,
|
|
||||||
eventEmitter: suspend (LoginEvent) -> Unit,
|
eventEmitter: suspend (LoginEvent) -> Unit,
|
||||||
) = createReducer(
|
) = createReducer(
|
||||||
initialState = LoginScreenState(showServerUrl = false, content = LoginScreenState.Content.Idle),
|
initialState = LoginScreenState(showServerUrl = false, content = LoginScreenState.Content.Idle),
|
||||||
|
@ -25,45 +18,29 @@ fun loginReducer(
|
||||||
LoginScreenState(state.showServerUrl, content = LoginScreenState.Content.Idle)
|
LoginScreenState(state.showServerUrl, content = LoginScreenState.Content.Idle)
|
||||||
},
|
},
|
||||||
|
|
||||||
change(LoginAction.UpdateContent::class) { action, state ->
|
change(LoginAction.UpdateContent::class) { action, state -> state.copy(content = action.content) },
|
||||||
state.copy(content = action.content)
|
|
||||||
},
|
|
||||||
|
|
||||||
change(LoginAction.UpdateState::class) { action, _ ->
|
change(LoginAction.UpdateState::class) { action, _ -> action.state },
|
||||||
action.state
|
|
||||||
},
|
|
||||||
|
|
||||||
async(LoginAction.Login::class) { action ->
|
async(LoginAction.Login::class) { action ->
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
logP("login") {
|
logP("login") {
|
||||||
dispatch(LoginAction.UpdateContent(LoginScreenState.Content.Loading))
|
dispatch(LoginAction.UpdateContent(LoginScreenState.Content.Loading))
|
||||||
val request = LoginRequest(action.userName, action.password, action.serverUrl.takeIfNotEmpty())
|
val request = LoginRequest(action.userName, action.password, action.serverUrl.takeIfNotEmpty())
|
||||||
when (val result = chatEngine.login(request)) {
|
|
||||||
is LoginResult.Success -> {
|
|
||||||
runCatching {
|
|
||||||
listOf(
|
|
||||||
async { pushTokenRegistrar.registerCurrentToken() },
|
|
||||||
async { chatEngine.preloadMe() },
|
|
||||||
).awaitAll()
|
|
||||||
}
|
|
||||||
eventEmitter.invoke(LoginEvent.LoginComplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
is LoginResult.Error -> {
|
when (val result = loginUseCase.run(request)) {
|
||||||
errorTracker.track(result.cause)
|
is LoginResult.Error -> dispatch(LoginAction.UpdateContent(LoginScreenState.Content.Error(result.cause)))
|
||||||
dispatch(LoginAction.UpdateContent(LoginScreenState.Content.Error(result.cause)))
|
|
||||||
}
|
|
||||||
|
|
||||||
LoginResult.MissingWellKnown -> {
|
LoginResult.MissingWellKnown -> {
|
||||||
eventEmitter.invoke(LoginEvent.WellKnownMissing)
|
eventEmitter.invoke(LoginEvent.WellKnownMissing)
|
||||||
dispatch(LoginAction.UpdateState(LoginScreenState(showServerUrl = true, content = LoginScreenState.Content.Idle)))
|
dispatch(LoginAction.UpdateState(LoginScreenState(showServerUrl = true, content = LoginScreenState.Content.Idle)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is LoginResult.Success -> eventEmitter.invoke(LoginEvent.LoginComplete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun ChatEngine.preloadMe() = this.me(forceRefresh = false)
|
|
||||||
|
|
||||||
private fun String?.takeIfNotEmpty() = this?.takeIf { it.isNotEmpty() }
|
private fun String?.takeIfNotEmpty() = this?.takeIf { it.isNotEmpty() }
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package app.dapk.st.login.state
|
||||||
|
|
||||||
|
import app.dapk.st.core.extensions.ErrorTracker
|
||||||
|
import app.dapk.st.core.logP
|
||||||
|
import app.dapk.st.engine.ChatEngine
|
||||||
|
import app.dapk.st.engine.LoginRequest
|
||||||
|
import app.dapk.st.engine.LoginResult
|
||||||
|
import app.dapk.st.push.PushTokenRegistrar
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
||||||
|
class LoginUseCase(
|
||||||
|
private val chatEngine: ChatEngine,
|
||||||
|
private val pushTokenRegistrar: PushTokenRegistrar,
|
||||||
|
private val errorTracker: ErrorTracker,
|
||||||
|
) {
|
||||||
|
suspend fun run(request: LoginRequest): LoginResult {
|
||||||
|
return logP("login") {
|
||||||
|
when (val result = chatEngine.login(request)) {
|
||||||
|
is LoginResult.Success -> {
|
||||||
|
coroutineScope {
|
||||||
|
runCatching {
|
||||||
|
listOf(
|
||||||
|
async { pushTokenRegistrar.registerCurrentToken() },
|
||||||
|
async { chatEngine.preloadMe() },
|
||||||
|
).awaitAll()
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is LoginResult.Error -> {
|
||||||
|
errorTracker.track(result.cause)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginResult.MissingWellKnown -> result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun ChatEngine.preloadMe() = this.me(forceRefresh = false)
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package app.dapk.st.login.state
|
||||||
|
|
||||||
|
import app.dapk.st.engine.LoginRequest
|
||||||
|
import app.dapk.st.engine.LoginResult
|
||||||
|
import app.dapk.st.login.state.fakes.FakeLoginUseCase
|
||||||
|
import app.dapk.st.matrix.common.DeviceId
|
||||||
|
import app.dapk.st.matrix.common.HomeServerUrl
|
||||||
|
import app.dapk.st.matrix.common.UserCredentials
|
||||||
|
import fixture.aUserId
|
||||||
|
import org.junit.Test
|
||||||
|
import test.assertDispatches
|
||||||
|
import test.assertEvents
|
||||||
|
import test.assertOnlyDispatches
|
||||||
|
import test.testReducer
|
||||||
|
|
||||||
|
private val A_LOGIN_ACTION = LoginAction.Login(
|
||||||
|
userName = "a-username",
|
||||||
|
password = "a-password",
|
||||||
|
serverUrl = "a-server-url",
|
||||||
|
)
|
||||||
|
|
||||||
|
private val AN_ERROR_CAUSE = RuntimeException()
|
||||||
|
|
||||||
|
class LoginReducerTest {
|
||||||
|
|
||||||
|
private val fakeLoginUseCase = FakeLoginUseCase()
|
||||||
|
|
||||||
|
private val runReducerTest = testReducer { events: (LoginEvent) -> Unit ->
|
||||||
|
loginReducer(fakeLoginUseCase.instance, events)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state is idle without server url`() = runReducerTest {
|
||||||
|
assertInitialState(LoginScreenState(showServerUrl = false, content = LoginScreenState.Content.Idle))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given non initial state, when Visible, then updates state to Idle with previous showServerUrl`() = runReducerTest {
|
||||||
|
setState(LoginScreenState(showServerUrl = true, LoginScreenState.Content.Loading))
|
||||||
|
|
||||||
|
reduce(LoginAction.ComponentLifecycle.Visible)
|
||||||
|
|
||||||
|
assertOnlyStateChange(LoginScreenState(showServerUrl = true, LoginScreenState.Content.Idle))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when UpdateContent, then only updates content state`() = runReducerTest {
|
||||||
|
reduce(LoginAction.UpdateContent(LoginScreenState.Content.Loading))
|
||||||
|
|
||||||
|
assertOnlyStateChange {
|
||||||
|
it.copy(content = LoginScreenState.Content.Loading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when UpdateState, then only updates state`() = runReducerTest {
|
||||||
|
reduce(LoginAction.UpdateState(LoginScreenState(showServerUrl = true, LoginScreenState.Content.Loading)))
|
||||||
|
|
||||||
|
assertOnlyStateChange(LoginScreenState(showServerUrl = true, LoginScreenState.Content.Loading))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given login errors, when Login, then updates content with loading and error`() = runReducerTest {
|
||||||
|
fakeLoginUseCase.given(A_LOGIN_ACTION.toRequest()).returns(LoginResult.Error(AN_ERROR_CAUSE))
|
||||||
|
|
||||||
|
reduce(A_LOGIN_ACTION)
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
LoginAction.UpdateContent(LoginScreenState.Content.Loading),
|
||||||
|
LoginAction.UpdateContent(LoginScreenState.Content.Error(AN_ERROR_CAUSE)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given login fails with WellKnownMissing, when Login, then emits WellKnownMissing event and updates content with loading and Idle showing server url`() =
|
||||||
|
runReducerTest {
|
||||||
|
fakeLoginUseCase.given(A_LOGIN_ACTION.toRequest()).returns(LoginResult.MissingWellKnown)
|
||||||
|
|
||||||
|
reduce(A_LOGIN_ACTION)
|
||||||
|
|
||||||
|
assertDispatches(
|
||||||
|
LoginAction.UpdateContent(LoginScreenState.Content.Loading),
|
||||||
|
LoginAction.UpdateState(LoginScreenState(showServerUrl = true, LoginScreenState.Content.Idle)),
|
||||||
|
)
|
||||||
|
assertEvents(LoginEvent.WellKnownMissing)
|
||||||
|
assertNoStateChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given login success, when Login, then emits LoginComplete event`() = runReducerTest {
|
||||||
|
fakeLoginUseCase.given(A_LOGIN_ACTION.toRequest()).returns(LoginResult.Success(aUserCredentials()))
|
||||||
|
|
||||||
|
reduce(A_LOGIN_ACTION)
|
||||||
|
|
||||||
|
assertDispatches(LoginAction.UpdateContent(LoginScreenState.Content.Loading))
|
||||||
|
assertEvents(LoginEvent.LoginComplete)
|
||||||
|
assertNoStateChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LoginAction.Login.toRequest() = LoginRequest(this.userName, this.password, this.serverUrl)
|
||||||
|
|
||||||
|
private fun aUserCredentials() = UserCredentials("ignored", HomeServerUrl("ignored"), aUserId(), DeviceId("ignored"))
|
|
@ -0,0 +1,15 @@
|
||||||
|
package app.dapk.st.login.state.fakes
|
||||||
|
|
||||||
|
import app.dapk.st.engine.LoginRequest
|
||||||
|
import app.dapk.st.login.state.LoginUseCase
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.mockk
|
||||||
|
import test.delegateReturn
|
||||||
|
|
||||||
|
class FakeLoginUseCase {
|
||||||
|
|
||||||
|
val instance = mockk<LoginUseCase>()
|
||||||
|
|
||||||
|
fun given(loginRequest: LoginRequest) = coEvery { instance.run(loginRequest) }.delegateReturn()
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue