diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CredentialStoreClient.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CredentialStoreClient.kt index a4566508f9..beb74df4da 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CredentialStoreClient.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CredentialStoreClient.kt @@ -16,17 +16,20 @@ package com.amplifyframework.auth.cognito import android.content.Context +import androidx.annotation.VisibleForTesting +import com.amplifyframework.AmplifyException import com.amplifyframework.auth.cognito.data.AWSCognitoAuthCredentialStore import com.amplifyframework.auth.cognito.data.AWSCognitoLegacyCredentialStore +import com.amplifyframework.auth.cognito.helpers.collectWhile +import com.amplifyframework.auth.exceptions.InvalidStateException import com.amplifyframework.logging.Logger -import com.amplifyframework.statemachine.StateChangeListenerToken import com.amplifyframework.statemachine.codegen.data.AmplifyCredential import com.amplifyframework.statemachine.codegen.data.CredentialType import com.amplifyframework.statemachine.codegen.events.CredentialStoreEvent import com.amplifyframework.statemachine.codegen.states.CredentialStoreState -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onSubscription internal interface StoreClientBehavior { suspend fun loadCredentials(credentialType: CredentialType): AmplifyCredential @@ -34,109 +37,58 @@ internal interface StoreClientBehavior { suspend fun clearCredentials(credentialType: CredentialType) } -internal class CredentialStoreClient(configuration: AuthConfiguration, context: Context, val logger: Logger) : - StoreClientBehavior { - private val credentialStoreStateMachine = createCredentialStoreStateMachine(configuration, context) +internal class CredentialStoreClient @VisibleForTesting constructor( + private val credentialStoreStateMachine: CredentialStoreStateMachine, + val logger: Logger +) : StoreClientBehavior { - private fun createCredentialStoreStateMachine( - configuration: AuthConfiguration, - context: Context - ): CredentialStoreStateMachine { - val awsCognitoAuthCredentialStore = AWSCognitoAuthCredentialStore(context.applicationContext, configuration) - val legacyCredentialStore = AWSCognitoLegacyCredentialStore(context.applicationContext, configuration) - val credentialStoreEnvironment = - CredentialStoreEnvironment(awsCognitoAuthCredentialStore, legacyCredentialStore, logger) - return CredentialStoreStateMachine(credentialStoreEnvironment) - } + constructor(configuration: AuthConfiguration, context: Context, logger: Logger) : this( + credentialStoreStateMachine = createCredentialStoreStateMachine(configuration, context, logger), + logger = logger + ) - private fun listenForResult( - event: CredentialStoreEvent, - onSuccess: (Result) -> Unit, - onError: (Exception) -> Unit - ) { - val token = StateChangeListenerToken() - val credentialStoreStateListener = OneShotCredentialStoreStateListener( - { - credentialStoreStateMachine.cancel(token) - onSuccess(it) - }, - { - credentialStoreStateMachine.cancel(token) - onError(it) - }, - logger + private suspend fun listenForResult(event: CredentialStoreEvent.EventType): AmplifyCredential { + var result: Result? = null + credentialStoreStateMachine.state + .onSubscription { credentialStoreStateMachine.send(CredentialStoreEvent(event)) } + .drop(1) // skip current state + .onEach { state -> + when (state) { + is CredentialStoreState.Error -> result = result ?: Result.failure(state.error) + is CredentialStoreState.Success -> result = Result.success(state.storedCredentials) + else -> Unit // no-op + } + } + .collectWhile { state -> state !is CredentialStoreState.Idle } + return result?.getOrThrow() ?: throw InvalidStateException( + message = "Credential operation failed", + recoverySuggestion = AmplifyException.REPORT_BUG_TO_AWS_SUGGESTION ) - credentialStoreStateMachine.listen( - token, - credentialStoreStateListener::listen - ) { credentialStoreStateMachine.send(event) } } override suspend fun loadCredentials(credentialType: CredentialType): AmplifyCredential = - suspendCoroutine { continuation -> - listenForResult( - CredentialStoreEvent(CredentialStoreEvent.EventType.LoadCredentialStore(credentialType)), - { continuation.resumeWith(it) }, - { continuation.resumeWithException(it) } - ) - } + listenForResult(CredentialStoreEvent.EventType.LoadCredentialStore(credentialType)) - override suspend fun storeCredentials(credentialType: CredentialType, amplifyCredential: AmplifyCredential) = - suspendCoroutine { continuation -> - listenForResult( - CredentialStoreEvent( - CredentialStoreEvent.EventType.StoreCredentials(credentialType, amplifyCredential) - ), - { continuation.resumeWith(Result.success(Unit)) }, - { continuation.resumeWithException(it) } - ) - } - - override suspend fun clearCredentials(credentialType: CredentialType) = suspendCoroutine { continuation -> - listenForResult( - CredentialStoreEvent(CredentialStoreEvent.EventType.ClearCredentialStore(credentialType)), - { continuation.resumeWith(Result.success(Unit)) }, - { continuation.resumeWithException(it) } - ) + override suspend fun storeCredentials(credentialType: CredentialType, amplifyCredential: AmplifyCredential) { + listenForResult(CredentialStoreEvent.EventType.StoreCredentials(credentialType, amplifyCredential)) } - /* - This class is a necessary workaround due to undesirable threading issues within the Auth State Machine. If state - machine threading is improved, this class should be considered for removal. - */ - internal class OneShotCredentialStoreStateListener( - val onSuccess: (Result) -> Unit, - val onError: (Exception) -> Unit, - val logger: Logger - ) { - private var capturedSuccess: Result? = null - private var capturedError: Exception? = null - private val isActive = AtomicBoolean(true) - fun listen(storeState: CredentialStoreState) { - logger.verbose("Credential Store State Change: $storeState") - when (storeState) { - is CredentialStoreState.Success -> { - capturedSuccess = Result.success(storeState.storedCredentials) - } - - is CredentialStoreState.Error -> { - capturedError = storeState.error - } - - is CredentialStoreState.Idle -> { - val success = capturedSuccess - val error = capturedError + override suspend fun clearCredentials(credentialType: CredentialType) { + listenForResult(CredentialStoreEvent.EventType.ClearCredentialStore(credentialType)) + } - if ((success != null || error != null) && isActive.getAndSet(false)) { - if (success != null) { - onSuccess(success) - } else if (error != null) { - onError(error) - } - } - } - else -> Unit - } + companion object { + private fun createCredentialStoreStateMachine( + configuration: AuthConfiguration, + context: Context, + logger: Logger + ): CredentialStoreStateMachine { + val awsCognitoAuthCredentialStore = + AWSCognitoAuthCredentialStore(context.applicationContext, configuration) + val legacyCredentialStore = AWSCognitoLegacyCredentialStore(context.applicationContext, configuration) + val credentialStoreEnvironment = + CredentialStoreEnvironment(awsCognitoAuthCredentialStore, legacyCredentialStore, logger) + return CredentialStoreStateMachine(credentialStoreEnvironment) } } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt index 690688bf18..95d9ff2353 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt @@ -15,7 +15,6 @@ package com.amplifyframework.statemachine -import java.util.UUID import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -30,14 +29,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.withContext -internal typealias OnSubscribedCallback = () -> Unit - -internal class StateChangeListenerToken private constructor(val uuid: UUID) { - constructor() : this(UUID.randomUUID()) - override fun equals(other: Any?) = other is StateChangeListenerToken && other.uuid == uuid - override fun hashCode() = uuid.hashCode() -} - /** * Model, mutate and process effects of a system as a finite state automaton. It consists of: * State - which represents the current state of the system @@ -49,7 +40,6 @@ internal class StateChangeListenerToken private constructor(val uuid: UUID) { * @param resolver responsible for mutating state based on incoming events * @param environment holds system specific environment info accessible to Effects/Actions * @param executor responsible for invoking effects - * @param concurrentQueue event queue or thread pool for effect executor and subscription callback * @param initialState starting state of the system (resolver default state will be used if omitted) */ internal open class StateMachine( @@ -77,50 +67,6 @@ internal open class StateMachine Unit> = mutableMapOf() - - // atomic value ?? - private val pendingCancellations: MutableSet = mutableSetOf() - - /** - * Start listening to state changes updates. Asynchronously invoke listener on a background queue with the current state. - * Both `listener` and `onSubscribe` will be invoked on a background queue. - * @param listener listener to be invoked on state changes - * @param onSubscribe callback to invoke when subscription is complete - * @return token that can be used to unsubscribe the listener - */ - @Deprecated("Collect from state flow instead") - fun listen(token: StateChangeListenerToken, listener: (StateType) -> Unit, onSubscribe: OnSubscribedCallback?) { - stateMachineScope.launch { - addSubscription(token, listener, onSubscribe) - } - } - - /** - * Stop listening to state changes updates. Register a pending cancellation if a new event comes in between the time - * `cancel` is called and the time the pending cancellation is processed, the event will not be dispatched to the listener. - * @param token identifies the listener to be removed - */ - @Deprecated("Collect from state flow instead") - fun cancel(token: StateChangeListenerToken) { - pendingCancellations.add(token) - stateMachineScope.launch { - removeSubscription(token) - } - } - - /** - * Invoke `completion` with the current state - * @param completion callback to invoke with the current state - */ - @Deprecated("Use suspending version instead") - fun getCurrentState(completion: (StateType) -> Unit) { - stateMachineScope.launch { - completion(getCurrentState()) - } - } - /** * Get the current state, dispatching to the state machine context for the read. */ @@ -130,35 +76,6 @@ internal open class StateMachine Unit, - onSubscribe: OnSubscribedCallback? - ) { - if (pendingCancellations.contains(token)) return - val currentState = getCurrentState() - subscribers[token] = listener - onSubscribe?.invoke() - stateMachineScope.launch(dispatcherQueue) { - listener.invoke(currentState) - } - } - - /** - * Unregister a listener. - * @param token token of the listener to remove - */ - private fun removeSubscription(token: StateChangeListenerToken) { - pendingCancellations.remove(token) - subscribers.remove(token) - } - /** * Send `event` to the StateMachine for resolution, and applies any effects and new states returned from the resolution. * @param event event to send to the system @@ -169,22 +86,6 @@ internal open class StateMachine Unit>, - newState: StateType - ): Boolean { - val token = subscriber.key - if (pendingCancellations.contains(token)) return false - subscriber.value(newState) - return true - } - /** * Resolver mutates the state based on current state and incoming event, and returns resolution with new state and * effects. If the state machine's state after resolving is not equal to the state before the event, update the @@ -197,8 +98,6 @@ internal open class StateMachine { - val getStateLatch = CountDownLatch(1) - var authState: AuthState? = null - authStateMachine.getCurrentState { - authState = it - getStateLatch.countDown() - } - getStateLatch.await(10, TimeUnit.SECONDS) + val authState = runBlocking { authStateMachine.getCurrentState() } assertEquals(getState(validation.expectedState), authState) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt index 5c74d3280b..7839392773 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt @@ -31,7 +31,6 @@ import com.amplifyframework.auth.cognito.usecases.WebUiSignInResponseUseCase import com.amplifyframework.auth.cognito.usecases.WebUiSignInUseCase import com.amplifyframework.auth.exceptions.InvalidStateException import com.amplifyframework.auth.result.AuthSignInResult -import com.amplifyframework.core.Consumer import com.amplifyframework.logging.Logger import com.amplifyframework.statemachine.codegen.data.AmplifyCredential import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens @@ -50,11 +49,9 @@ import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.unmockkAll import java.io.File -import kotlin.coroutines.resume import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertTrue -import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -62,7 +59,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import kotlinx.coroutines.withTimeout @@ -485,29 +481,17 @@ class AuthValidationTest { private fun signOutHostedUi() = signOut() private fun assertSignedOut() { - val result = blockForResult { continuation -> stateMachine.getCurrentState { continuation.accept(it) } } + val result = runBlocking { stateMachine.getCurrentState() } assertTrue(result.authNState is AuthenticationState.SignedOut) } private fun assertSignedInAs(username: String) { - val result = blockForResult { continuation -> stateMachine.getCurrentState { continuation.accept(it) } } + val result = runBlocking { stateMachine.getCurrentState() } val state = result.authNState assertTrue(state is AuthenticationState.SignedIn) assertEquals(username, state.signedInData.username) } - private fun blockForResult(timeoutMillis: Long = 100000, function: (complete: Consumer) -> Unit): T = - runBlockingWithTimeout(timeoutMillis) { continuation -> function { continuation.resume(it) } } - - // Helper that runs the supplied function in a coroutine, blocking the thread until the continuation is invoked or - // the timeout is reached - private fun runBlockingWithTimeout( - timeoutMillis: Long, - function: (continuation: CancellableContinuation) -> Unit - ): T = runBlocking { - withTimeout(timeoutMillis) { suspendCancellableCoroutine(function) } - } - private fun setupMockResponseForInvalidUser() { coEvery { identityProviderClient.initiateAuth(any()) } throws mockk() } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/CredentialStoreClientTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/CredentialStoreClientTest.kt index a7be4b7a15..ed7364bbd4 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/CredentialStoreClientTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/CredentialStoreClientTest.kt @@ -15,56 +15,197 @@ package com.amplifyframework.auth.cognito +import com.amplifyframework.auth.exceptions.InvalidStateException +import com.amplifyframework.logging.Logger +import com.amplifyframework.statemachine.codegen.data.AmplifyCredential +import com.amplifyframework.statemachine.codegen.data.CredentialType +import com.amplifyframework.statemachine.codegen.errors.CredentialStoreError +import com.amplifyframework.statemachine.codegen.events.CredentialStoreEvent import com.amplifyframework.statemachine.codegen.states.CredentialStoreState +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.mockk.every +import io.mockk.justRun import io.mockk.mockk -import java.util.concurrent.atomic.AtomicInteger -import kotlin.random.Random -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import io.mockk.slot +import io.mockk.verify +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test internal class CredentialStoreClientTest { - /** - * This test has been verified to regularly fail if the OneShotCredentialStoreStateListener isActive - * field is non-atomic. - */ + private val stateFlow = MutableSharedFlow(replay = 1) + private val stateMachine: CredentialStoreStateMachine = mockk { + every { state } returns stateFlow + justRun { send(any()) } + } + private val logger: Logger = mockk(relaxed = true) + private lateinit var client: CredentialStoreClient + + @Before + fun setup() { + client = CredentialStoreClient(stateMachine, logger) + } + + private suspend fun emitStateSequence(vararg states: CredentialStoreState) { + for (state in states) { + stateFlow.emit(state) + } + } + + @Test + fun `loadCredentials returns credentials on success`() = runTest { + val expectedCredential = AmplifyCredential.Empty + val credentialType = CredentialType.Amplify + + // Emit initial state so the flow has a replay value + stateFlow.emit(CredentialStoreState.NotConfigured()) + + launch { + emitStateSequence( + CredentialStoreState.LoadingStoredCredentials(), + CredentialStoreState.Success(expectedCredential), + CredentialStoreState.Idle() + ) + } + + val result = client.loadCredentials(credentialType) + + result shouldBe expectedCredential + val eventSlot = slot() + verify { stateMachine.send(capture(eventSlot)) } + eventSlot.captured.eventType.shouldBeInstanceOf() + } + + @Test + fun `storeCredentials sends store event and completes on success`() = runTest { + val credential = AmplifyCredential.Empty + val credentialType = CredentialType.Amplify + + stateFlow.emit(CredentialStoreState.Idle()) + + launch { + emitStateSequence( + CredentialStoreState.StoringCredentials(), + CredentialStoreState.Success(credential), + CredentialStoreState.Idle() + ) + } + + client.storeCredentials(credentialType, credential) + + val eventSlot = slot() + verify { stateMachine.send(capture(eventSlot)) } + eventSlot.captured.eventType.shouldBeInstanceOf() + } + + @Test + fun `clearCredentials sends clear event and completes on success`() = runTest { + val credentialType = CredentialType.Amplify + + stateFlow.emit(CredentialStoreState.Idle()) + + launch { + emitStateSequence( + CredentialStoreState.ClearingCredentials(), + CredentialStoreState.Success(AmplifyCredential.Empty), + CredentialStoreState.Idle() + ) + } + + client.clearCredentials(credentialType) + + val eventSlot = slot() + verify { stateMachine.send(capture(eventSlot)) } + eventSlot.captured.eventType.shouldBeInstanceOf() + } + @Test - fun one_shot_listener_fires_once() { - val attempts = 10_000 - val timesFired = AtomicInteger(0) - var timesFailed = 0 - val listener = CredentialStoreClient.OneShotCredentialStoreStateListener( - { - if (timesFired.incrementAndGet() != 1) { - timesFailed += 1 - } - }, - { - if (timesFired.incrementAndGet() != 1) { - timesFailed += 1 - } - }, - mockk(relaxed = true) - ) - - for (i in 0..attempts) { - for (x in 0..5) { - if (Random.nextBoolean()) { - listener.listen(CredentialStoreState.Success(mockk())) - } else { - listener.listen(CredentialStoreState.Error(mockk())) - } - CoroutineScope(Dispatchers.IO).launch { - for (y in 0..5) { - listener.listen(CredentialStoreState.Idle()) - } - } - } + fun `loadCredentials throws on state machine error`() = runTest { + val error = CredentialStoreError("test error") + val credentialType = CredentialType.Amplify + + stateFlow.emit(CredentialStoreState.NotConfigured()) + + launch { + emitStateSequence( + CredentialStoreState.LoadingStoredCredentials(), + CredentialStoreState.Error(error), + CredentialStoreState.Idle() + ) } - assertEquals(0, timesFailed) + val thrown = shouldThrow { + client.loadCredentials(credentialType) + } + thrown.message shouldBe "test error" + } + + @Test + fun `loadCredentials throws InvalidStateException when no result before idle`() = runTest { + val credentialType = CredentialType.Amplify + + stateFlow.emit(CredentialStoreState.NotConfigured()) + + launch { + // Go straight to Idle without Success or Error + stateFlow.emit(CredentialStoreState.Idle()) + } + + shouldThrow { + client.loadCredentials(credentialType) + } + } + + @Test + fun `loadCredentials with device credential type sends correct event`() = runTest { + val expectedCredential = AmplifyCredential.Empty + val credentialType = CredentialType.Device("testUser") + + stateFlow.emit(CredentialStoreState.Idle()) + + launch { + emitStateSequence( + CredentialStoreState.LoadingStoredCredentials(), + CredentialStoreState.Success(expectedCredential), + CredentialStoreState.Idle() + ) + } + + val result = client.loadCredentials(credentialType) + + result shouldBe expectedCredential + val eventSlot = slot() + verify { stateMachine.send(capture(eventSlot)) } + val eventType = eventSlot.captured.eventType + eventType.shouldBeInstanceOf() + eventType.credentialType shouldBe CredentialType.Device("testUser") + } + + @Test + fun `error result is captured only once when multiple errors emitted`() = runTest { + val firstError = CredentialStoreError("first error") + val secondError = CredentialStoreError("second error") + val credentialType = CredentialType.Amplify + + stateFlow.emit(CredentialStoreState.NotConfigured()) + + launch { + emitStateSequence( + CredentialStoreState.Error(firstError), + CredentialStoreState.Error(secondError), + CredentialStoreState.Idle() + ) + } + + val thrown = shouldThrow { + client.loadCredentials(credentialType) + } + // The first error should be captured (result ?: prevents overwrite) + thrown.message shouldBe "first error" } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineListenerTests.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineListenerTests.kt deleted file mode 100644 index b678427192..0000000000 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineListenerTests.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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.amplifyframework.statemachine - -import com.amplifyframework.statemachine.state.Counter -import com.amplifyframework.statemachine.state.Counter.Event.EventType.AdjustBy -import com.amplifyframework.statemachine.state.Counter.Event.EventType.Increment -import com.amplifyframework.statemachine.state.CounterEnvironment -import com.amplifyframework.statemachine.state.CounterStateMachine -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import org.junit.After -import org.junit.Before -import org.junit.Test - -class StateMachineListenerTests { - private val mainThreadSurrogate = newSingleThreadContext("Main thread") - private lateinit var stateMachine: CounterStateMachine - - @Before - fun setUp() { - stateMachine = CounterStateMachine(Counter.Resolver(), CounterEnvironment.empty) - Dispatchers.setMain(mainThreadSurrogate) - } - - @After - fun tearDown() { - // reset main dispatcher to the original Main dispatcher - Dispatchers.resetMain() - mainThreadSurrogate.close() - } - - @Test - fun testNotifyOnListen() { - stateMachine.send(Counter.Event("1", eventType = Increment)) - val testLatch = CountDownLatch(2) - stateMachine.listen( - StateChangeListenerToken(), - { - assertEquals(1, it.value) - testLatch.countDown() - }, - { - testLatch.countDown() - } - ) - assertTrue { testLatch.await(5, TimeUnit.SECONDS) } - } - - @Test - fun testNotifyStateChange() { - stateMachine.send(Counter.Event("1", eventType = Increment)) - val listenLatch = CountDownLatch(2) - val subscribeLatch = CountDownLatch(1) - stateMachine.listen( - StateChangeListenerToken(), - { - listenLatch.countDown() - }, - { - subscribeLatch.countDown() - } - ) - assertTrue { subscribeLatch.await(5, TimeUnit.SECONDS) } - - stateMachine.send(Counter.Event("2", eventType = Increment)) - assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } - } - - @Test - fun testNoNotifyNoStateChange() { - stateMachine.send(Counter.Event("1", eventType = Increment)) - val listenLatch = CountDownLatch(1) - val subscribeLatch = CountDownLatch(1) - stateMachine.listen( - StateChangeListenerToken(), - { - listenLatch.countDown() - }, - { - subscribeLatch.countDown() - } - ) - assertTrue { subscribeLatch.await(5, TimeUnit.SECONDS) } - - stateMachine.send(Counter.Event("2", eventType = AdjustBy(0))) - assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } - } - - @Test - fun testNoNotifyUnsubscribe() { - stateMachine.send(Counter.Event("1", eventType = Increment)) - val listenLatch = CountDownLatch(1) - val subscribeLatch = CountDownLatch(1) - val token = StateChangeListenerToken() - stateMachine.listen( - token, - { - listenLatch.countDown() - }, - { - subscribeLatch.countDown() - } - ) - assertTrue { subscribeLatch.await(5, TimeUnit.SECONDS) } - - stateMachine.cancel(token) - stateMachine.send(Counter.Event("2", eventType = Increment)) - assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } - } -} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineTests.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineTests.kt index 95c70a4268..d8f978b455 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineTests.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/statemachine/StateMachineTests.kt @@ -51,38 +51,14 @@ class StateMachineTests { } @Test - fun testDefaultState() { - val testLatch = CountDownLatch(1) - val testMachine = CounterStateMachine.logging() - testMachine.getCurrentState { state -> - state.value shouldBe 0 - testLatch.countDown() - } - testLatch.await(timeout) shouldBe true - } - - @Test - fun `test default state suspending`() = runTest { + fun `test default state`() = runTest { val testMachine = CounterStateMachine.logging() val currentState = testMachine.getCurrentState() currentState.value shouldBe 0 } @Test - fun testBasicReceive() = runTest { - val testLatch = CountDownLatch(1) - val testMachine = CounterStateMachine.logging() - val increment = Counter.Event("1", Counter.Event.EventType.Increment) - testMachine.send(increment) - testMachine.getCurrentState { state -> - state.value shouldBe 1 - testLatch.countDown() - } - testLatch.await(timeout) shouldBe true - } - - @Test - fun `test basic receive suspending`() = runTest { + fun `test basic receive`() = runTest { val testMachine = CounterStateMachine.logging() val increment = Counter.Event("1", Counter.Event.EventType.Increment) testMachine.send(increment) @@ -91,22 +67,7 @@ class StateMachineTests { } @Test - fun testConcurrentReceiveAndRead() { - val testLatch = CountDownLatch(10) - val testMachine = CounterStateMachine() - val increment = Counter.Event("1", Counter.Event.EventType.Increment) - for (i in 1..10) { - testMachine.send(increment) - testMachine.getCurrentState { state -> - state.value shouldBe i - testLatch.countDown() - } - } - testLatch.await(timeout) shouldBe true - } - - @Test - fun `test concurrent receive and read suspending`() = runTest { + fun `test concurrent receive and read`() = runTest { val testMachine = CounterStateMachine() val increment = Counter.Event("1", Counter.Event.EventType.Increment) for (i in 1..10) {