From aa4e0411953f1cc84d43c3df4aaa63965d33e946 Mon Sep 17 00:00:00 2001 From: Christos Kaitatzis Date: Thu, 6 Feb 2025 13:51:44 +0200 Subject: [PATCH] change: Corrections and TestOptionsSelectionViewModel unit testing Call modifieed in OptionsSelectionInteractor to only retrieve selected QTSP and not both file and qtsp as it is not needed at that step. TestOptionsSelectionViewModel created with tests targeting high coverage and test cases commenting, previous test classes removed. Signed-off-by: Christos Kaitatzis --- .../interactor/OptionsSelectionInteractor.kt | 51 +- .../OptionsSelectionViewModel.kt | 68 +- .../TestOptionsSelectionInteractor.kt | 25 +- .../TestOptionsSelectionViewModel.kt | 1322 +++++++++++++++++ .../TestSelectCertificateViewModel.kt | 330 ---- .../ui/select_qtsp/TestSelectQtspViewModel.kt | 447 ------ 6 files changed, 1374 insertions(+), 869 deletions(-) create mode 100644 rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/TestOptionsSelectionViewModel.kt delete mode 100644 rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_certificate/TestSelectCertificateViewModel.kt delete mode 100644 rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_qtsp/TestSelectQtspViewModel.kt diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/domain/interactor/OptionsSelectionInteractor.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/domain/interactor/OptionsSelectionInteractor.kt index ce997bf..72fc1ce 100644 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/domain/interactor/OptionsSelectionInteractor.kt +++ b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/domain/interactor/OptionsSelectionInteractor.kt @@ -28,32 +28,30 @@ import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesSetSelectedQtspPartial import eu.europa.ec.eudi.rqesui.domain.controller.RqesController import eu.europa.ec.eudi.rqesui.domain.entities.error.EudiRQESUiError import eu.europa.ec.eudi.rqesui.infrastructure.config.data.CertificateData -import eu.europa.ec.eudi.rqesui.infrastructure.config.data.DocumentData import eu.europa.ec.eudi.rqesui.infrastructure.config.data.QtspData import eu.europa.ec.eudi.rqesui.infrastructure.provider.ResourceProvider import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -internal sealed class OptionsSelectionInteractorGetSelectedFileAndQtspPartialState { +internal sealed class OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState { data class Success( - val selectedFile: DocumentData, - val selectedQtsp: QtspData, - ) : OptionsSelectionInteractorGetSelectedFileAndQtspPartialState() + val certificates: List, + ) : OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState() data class Failure( val error: EudiRQESUiError - ) : OptionsSelectionInteractorGetSelectedFileAndQtspPartialState() + ) : OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState() } -internal sealed class OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState { +internal sealed class OptionsSelectionInteractorGetSelectedQtspPartialState { data class Success( - val certificates: List, - ) : OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState() + val selectedQtsp: QtspData, + ) : OptionsSelectionInteractorGetSelectedQtspPartialState() data class Failure( val error: EudiRQESUiError - ) : OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState() + ) : OptionsSelectionInteractorGetSelectedQtspPartialState() } internal interface OptionsSelectionInteractor { @@ -63,7 +61,7 @@ internal interface OptionsSelectionInteractor { fun updateQtspUserSelection(qtspData: QtspData): EudiRqesSetSelectedQtspPartialState - fun getSelectedFileAndQtsp(): OptionsSelectionInteractorGetSelectedFileAndQtspPartialState + fun getSelectedQtsp(): OptionsSelectionInteractorGetSelectedQtspPartialState suspend fun getServiceAuthorizationUrl(rqesService: RQESService): EudiRqesGetServiceAuthorizationUrlPartialState @@ -173,34 +171,23 @@ internal class OptionsSelectionInteractorImpl( } } - override fun getSelectedFileAndQtsp(): OptionsSelectionInteractorGetSelectedFileAndQtspPartialState { + override fun getSelectedQtsp(): OptionsSelectionInteractorGetSelectedQtspPartialState { return runCatching { - when (val getSelectedFileResponse = eudiRqesController.getSelectedFile()) { - is EudiRqesGetSelectedFilePartialState.Failure -> { - return@runCatching OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure( - error = getSelectedFileResponse.error + when (val getSelectedQtspResponse = eudiRqesController.getSelectedQtsp()) { + is EudiRqesGetSelectedQtspPartialState.Failure -> { + return@runCatching OptionsSelectionInteractorGetSelectedQtspPartialState.Failure( + error = getSelectedQtspResponse.error ) } - is EudiRqesGetSelectedFilePartialState.Success -> { - when (val getSelectedQtspResponse = eudiRqesController.getSelectedQtsp()) { - is EudiRqesGetSelectedQtspPartialState.Failure -> { - return@runCatching OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure( - error = getSelectedQtspResponse.error - ) - } - - is EudiRqesGetSelectedQtspPartialState.Success -> { - return@runCatching OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Success( - selectedFile = getSelectedFileResponse.file, - selectedQtsp = getSelectedQtspResponse.qtsp, - ) - } - } + is EudiRqesGetSelectedQtspPartialState.Success -> { + return@runCatching OptionsSelectionInteractorGetSelectedQtspPartialState.Success( + selectedQtsp = getSelectedQtspResponse.qtsp, + ) } } }.getOrElse { - OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure( + OptionsSelectionInteractorGetSelectedQtspPartialState.Failure( error = EudiRQESUiError( message = it.localizedMessage ?: genericErrorMsg ) diff --git a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionViewModel.kt b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionViewModel.kt index 2e7e8aa..4acb763 100644 --- a/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionViewModel.kt +++ b/rqes-ui-sdk/src/main/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/OptionsSelectionViewModel.kt @@ -27,7 +27,7 @@ import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesSetSelectedQtspPartial import eu.europa.ec.eudi.rqesui.domain.entities.localization.LocalizableKey import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractor import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState -import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractorGetSelectedFileAndQtspPartialState +import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractorGetSelectedQtspPartialState import eu.europa.ec.eudi.rqesui.domain.serializer.UiSerializer import eu.europa.ec.eudi.rqesui.infrastructure.config.data.CertificateData import eu.europa.ec.eudi.rqesui.infrastructure.config.data.DocumentData @@ -182,7 +182,7 @@ internal class OptionsSelectionViewModel( CERTIFICATE_SELECTION_STATE -> { createQTSPSelectionItemOnSelectCertificateStep(event = event) - createCertificateSelectionItemOnSelectCertificateStep(event = event) + createCertificateSelectionItemOnSelectCertificateStep() } } } @@ -333,7 +333,6 @@ internal class OptionsSelectionViewModel( is EudiRqesGetSelectedFilePartialState.Failure -> { setState { copy( - documentSelectionItem = null, error = ContentErrorConfig( onRetry = { setEvent(event) }, errorSubTitle = response.error.message, @@ -373,7 +372,6 @@ internal class OptionsSelectionViewModel( is EudiRqesGetSelectedFilePartialState.Failure -> { setState { copy( - qtspServiceSelectionItem = null, error = ContentErrorConfig( onRetry = { setEvent(event) }, errorSubTitle = response.error.message, @@ -406,11 +404,10 @@ internal class OptionsSelectionViewModel( } private fun createQTSPSelectionItemOnSelectCertificateStep(event: Event) { - when (val response = optionsSelectionInteractor.getSelectedFileAndQtsp()) { - is OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure -> { + when (val response = optionsSelectionInteractor.getSelectedQtsp()) { + is OptionsSelectionInteractorGetSelectedQtspPartialState.Failure -> { setState { copy( - qtspServiceSelectionItem = null, error = ContentErrorConfig( onRetry = { setEvent(event) }, errorSubTitle = response.error.message, @@ -423,7 +420,7 @@ internal class OptionsSelectionViewModel( } } - is OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Success -> { + is OptionsSelectionInteractorGetSelectedQtspPartialState.Success -> { setState { copy( qtspServiceSelectionItem = SelectionOptionUi( @@ -466,45 +463,22 @@ internal class OptionsSelectionViewModel( } } - private fun createCertificateSelectionItemOnSelectCertificateStep(event: Event) { - when (val response = optionsSelectionInteractor.getSelectedFile()) { - is EudiRqesGetSelectedFilePartialState.Failure -> { - setState { - copy( - certificateSelectionItem = null, - error = ContentErrorConfig( - onRetry = { - setEvent(Event.DismissError) - setEvent(event) - }, - errorSubTitle = response.error.message, - onCancel = { - setEvent(Event.DismissError) - setEffect { Effect.Navigation.Finish } - } - ) - ) - } - } - - is EudiRqesGetSelectedFilePartialState.Success -> { - setState { - copy( - certificateSelectionItem = SelectionOptionUi( - overlineText = null, - mainText = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningCertificateTitle), - subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectCertificateSubtitle), - leadingIcon = AppIcons.StepThree, - trailingIcon = AppIcons.KeyboardArrowRight, - enabled = true, - event = Event.CertificateSelectionItemPressed - ) - ) - } - setEffect { - Effect.OnCertificateSelectionItemCreated - } - } + private fun createCertificateSelectionItemOnSelectCertificateStep() { + setState { + copy( + certificateSelectionItem = SelectionOptionUi( + overlineText = null, + mainText = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningCertificateTitle), + subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectCertificateSubtitle), + leadingIcon = AppIcons.StepThree, + trailingIcon = AppIcons.KeyboardArrowRight, + enabled = true, + event = Event.CertificateSelectionItemPressed + ) + ) + } + setEffect { + Effect.OnCertificateSelectionItemCreated } } diff --git a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/domain/interactor/TestOptionsSelectionInteractor.kt b/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/domain/interactor/TestOptionsSelectionInteractor.kt index a316246..6fe4b37 100644 --- a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/domain/interactor/TestOptionsSelectionInteractor.kt +++ b/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/domain/interactor/TestOptionsSelectionInteractor.kt @@ -233,7 +233,7 @@ class TestOptionsSelectionInteractor { } //endregion - //region getSelectedFileAndQtsp + //region getSelectedQtsp @Test fun `Given both file and qtsp are selected, When getSelectedFileAndQtsp is called, Then return Success`() { // Arrange @@ -243,12 +243,11 @@ class TestOptionsSelectionInteractor { .thenReturn(EudiRqesGetSelectedQtspPartialState.Success(qtsp = qtspData)) // Act - val result = interactor.getSelectedFileAndQtsp() + val result = interactor.getSelectedQtsp() // Assert - assertTrue(result is OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Success) - with(result as OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Success) { - assertEquals(documentData, selectedFile) + assertTrue(result is OptionsSelectionInteractorGetSelectedQtspPartialState.Success) + with(result as OptionsSelectionInteractorGetSelectedQtspPartialState.Success) { assertEquals(qtspData, selectedQtsp) } } @@ -256,18 +255,18 @@ class TestOptionsSelectionInteractor { @Test fun `Given selected file retrieval fails, When getSelectedFileAndQtsp is called, Then return Failure`() { // Arrange - val error = EudiRQESUiError(message = mockedPlainFailureMessage) + val error = EudiRQESUiError(message = mockedGenericErrorMessage) whenever(eudiController.getSelectedFile()) .thenReturn(EudiRqesGetSelectedFilePartialState.Failure(error)) // Act - val result = interactor.getSelectedFileAndQtsp() + val result = interactor.getSelectedQtsp() // Assert - assertTrue(result is OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure) + assertTrue(result is OptionsSelectionInteractorGetSelectedQtspPartialState.Failure) assertEquals( - error, - (result as OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure).error + error.message, + (result as OptionsSelectionInteractorGetSelectedQtspPartialState.Failure).error.message ) } @@ -281,13 +280,13 @@ class TestOptionsSelectionInteractor { .thenReturn(EudiRqesGetSelectedQtspPartialState.Failure(error = failureError)) // Act - val result = interactor.getSelectedFileAndQtsp() + val result = interactor.getSelectedQtsp() // Assert - assertTrue(result is OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure) + assertTrue(result is OptionsSelectionInteractorGetSelectedQtspPartialState.Failure) assertEquals( failureError, - (result as OptionsSelectionInteractorGetSelectedFileAndQtspPartialState.Failure).error + (result as OptionsSelectionInteractorGetSelectedQtspPartialState.Failure).error ) } //endregion diff --git a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/TestOptionsSelectionViewModel.kt b/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/TestOptionsSelectionViewModel.kt new file mode 100644 index 0000000..0952e5d --- /dev/null +++ b/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/options_selection/TestOptionsSelectionViewModel.kt @@ -0,0 +1,1322 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work + * except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific language + * governing permissions and limitations under the Licence. + */ + +package eu.europa.ec.eudi.rqesui.presentation.ui.options_selection + +import android.net.Uri +import eu.europa.ec.eudi.rqes.CredentialInfo +import eu.europa.ec.eudi.rqes.core.RQESService +import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetCredentialAuthorizationUrlPartialState +import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetQtspsPartialState +import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetSelectedFilePartialState +import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetServiceAuthorizationUrlPartialState +import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesSetSelectedQtspPartialState +import eu.europa.ec.eudi.rqesui.domain.entities.error.EudiRQESUiError +import eu.europa.ec.eudi.rqesui.domain.entities.localization.LocalizableKey +import eu.europa.ec.eudi.rqesui.domain.extension.toUri +import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractor +import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState +import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractorGetSelectedQtspPartialState +import eu.europa.ec.eudi.rqesui.domain.serializer.UiSerializer +import eu.europa.ec.eudi.rqesui.infrastructure.config.data.CertificateData +import eu.europa.ec.eudi.rqesui.infrastructure.config.data.DocumentData +import eu.europa.ec.eudi.rqesui.infrastructure.config.data.QtspData +import eu.europa.ec.eudi.rqesui.infrastructure.provider.ResourceProvider +import eu.europa.ec.eudi.rqesui.infrastructure.theme.values.ThemeColors +import eu.europa.ec.eudi.rqesui.presentation.entities.ModalOptionUi +import eu.europa.ec.eudi.rqesui.presentation.entities.SelectionOptionUi +import eu.europa.ec.eudi.rqesui.presentation.entities.config.OptionsSelectionUiConfig +import eu.europa.ec.eudi.rqesui.presentation.entities.config.ViewDocumentUiConfig +import eu.europa.ec.eudi.rqesui.presentation.navigation.SdkScreens +import eu.europa.ec.eudi.rqesui.presentation.navigation.helper.generateComposableArguments +import eu.europa.ec.eudi.rqesui.presentation.navigation.helper.generateComposableNavigationLink +import eu.europa.ec.eudi.rqesui.presentation.ui.component.AppIcons +import eu.europa.ec.eudi.rqesui.presentation.ui.component.wrap.BottomSheetTextData +import eu.europa.ec.eudi.rqesui.util.CoroutineTestRule +import eu.europa.ec.eudi.rqesui.util.mockedAuthorizationUrl +import eu.europa.ec.eudi.rqesui.util.mockedCertificateName +import eu.europa.ec.eudi.rqesui.util.mockedDocumentName +import eu.europa.ec.eudi.rqesui.util.mockedFetchCertificatesFailureMessage +import eu.europa.ec.eudi.rqesui.util.mockedLocalFileUri +import eu.europa.ec.eudi.rqesui.util.mockedPlainFailureMessage +import eu.europa.ec.eudi.rqesui.util.mockedQtspName +import eu.europa.ec.eudi.rqesui.util.mockedSerializedConfig +import eu.europa.ec.eudi.rqesui.util.mockedUri +import eu.europa.ec.eudi.rqesui.util.runFlowTest +import eu.europa.ec.eudi.rqesui.util.runTest +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertNull +import junit.framework.TestCase.assertTrue +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.test.Test +import kotlin.test.assertNotNull + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class TestSelectQtspViewModel { + + @get:Rule + val coroutineRule = CoroutineTestRule() + + @Mock + private lateinit var optionsSelectionInteractor: OptionsSelectionInteractor + + @Mock + private lateinit var resourceProvider: ResourceProvider + + @Mock + private lateinit var uiSerializer: UiSerializer + + @Mock + private lateinit var qtspData: QtspData + + @Mock + private lateinit var rqesService: RQESService + + @Mock + private lateinit var documentData: DocumentData + + @Mock + private lateinit var authorizationUri: Uri + + @Mock + private lateinit var certificateData: CertificateData + + @Mock + private lateinit var credentialInfo: CredentialInfo + + private val documentFileUri = Uri.parse(mockedLocalFileUri) + + private lateinit var viewModel: OptionsSelectionViewModel + + private lateinit var autoCloseable: AutoCloseable + + private val deserializedConfig = OptionsSelectionUiConfig( + optionsSelectionScreenState = QTSP_SELECTION_STATE + ) + + @Before + fun setUp() { + autoCloseable = MockitoAnnotations.openMocks(this) + + whenever( + uiSerializer.fromBase64( + payload = mockedSerializedConfig, + model = OptionsSelectionUiConfig::class.java, + parser = OptionsSelectionUiConfig.Parser + ) + ).thenReturn(deserializedConfig) + + viewModel = OptionsSelectionViewModel( + optionsSelectionInteractor, + resourceProvider, + uiSerializer, + mockedSerializedConfig + ) + mockLocalizedStrings(resourceProvider) + } + + @After + fun after() { + autoCloseable.close() + } + + //region setInitialState + // Case 1 + // The function setInitialState() is called to initialize the ViewModel's state with default values. + // Case 1 Expected Result: + // 1. The title is set to the localized string for "SignDocument". + // 2. The bottomBarButtonAction is null indicating that it is not shown initially. + // 3. The bottom sheet content is set with a "ConfirmCancellation" structure containing localized + // strings for CancelSignProcessTitle, CancelSignProcessSubtitle, CancelSignProcessPrimaryText, + // CancelSignProcessSecondaryText. + @Test + fun `Given Case 1, When setInitialState is called, Then the expected result is returned`() { + // Act + viewModel.setInitialState() + + // Assert + val expectedState = State( + isLoading = false, + error = null, + isBottomSheetOpen = false, + title = resourceProvider.getLocalizedString(LocalizableKey.SignDocument), + documentSelectionItem = null, + bottomBarButtonAction = null, + sheetContent = OptionsSelectionBottomSheetContent.ConfirmCancellation( + bottomSheetTextData = BottomSheetTextData( + title = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessTitle), + message = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSubtitle), + positiveButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessPrimaryText), + negativeButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSecondaryText) + ) + ), + selectedQtspIndex = 0, + selectedCertificateIndex = 0, + config = deserializedConfig + ) + assertEquals(expectedState, viewModel.viewState.value) + } + //endregion + + //region setEvent, QTSP options related + // Case 1 + // setEvent with argument: Event.Initialize for state QTSP_SELECTION_STATE + // the ViewModel updates the `documentSelectionItem` in the state correctly. + // Case 1 Expected Result: + // 1. The `selectQtspInteractor.getSelectedFile()` function returns a successful state with `documentData`. + // 2. The `viewModel.setEvent(Event.Initialize)` function is called, triggering the creation of a `SelectionOptionUi`. + // 3. The `qtspServiceSelectionItem` in the ViewModel's state is populated with info to select a Qtsp + // 4. The `certificateSelectionItem` is null initially. + // containing the expected `documentData` and `View` action string. + @Test + fun `Given Case 1, When setEvent is called, Then the expected result is returned`() { + // Arrange + whenever(optionsSelectionInteractor.getSelectedFile()) + .thenReturn(EudiRqesGetSelectedFilePartialState.Success(file = documentData)) + + // Act + viewModel.setEvent( + Event.Initialize( + screenSelectionState = QTSP_SELECTION_STATE + ) + ) + + // Assert + assertEquals( + SelectionOptionUi( + overlineText = resourceProvider.getLocalizedString(LocalizableKey.SelectDocumentTitle), + mainText = documentData.documentName, + subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectDocumentSubtitle), + actionText = resourceProvider.getLocalizedString(LocalizableKey.View), + leadingIcon = AppIcons.StepOne, + leadingIconTint = ThemeColors.success, + trailingIcon = AppIcons.KeyboardArrowRight, + enabled = true, + event = Event.ViewDocumentItemPressed( + documentData = documentData + ) + ), + viewModel.viewState.value.documentSelectionItem + ) + assertEquals( + SelectionOptionUi( + overlineText = null, + mainText = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningService), + subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningServiceSubtitle), + actionText = null, + leadingIcon = AppIcons.StepTwo, + trailingIcon = AppIcons.KeyboardArrowRight, + enabled = true, + event = Event.RqesServiceSelectionItemPressed + ), + viewModel.viewState.value.qtspServiceSelectionItem + ) + assertEquals( + null, + viewModel.viewState.value.certificateSelectionItem + ) + } + + // Case 2 + // setEvent with argument: Event.Initialize for state CERTIFICATE_SELECTION_STATE + // The function `createSelectionItem` is tested to ensure that when it sets the success state, + // the ViewModel updates the `documentSelectionItem` in the state correctly. + // Case 2 Expected Result: + // 1. The `selectQtspInteractor.getSelectedFile()` function returns a successful state with `documentData`. + // 2. The `viewModel.setEvent(Event.Initialize)` function is called, triggering the creation of a `SelectionOptionUi`. + // 3. The `certificateSelectionItem` in the ViewModel's state is updated. + @Test + fun `Given Case 2, When setEvent is called, Then the expected result is returned`() { + // Arrange + whenever(optionsSelectionInteractor.getSelectedFile()) + .thenReturn(EudiRqesGetSelectedFilePartialState.Success(file = documentData)) + whenever(optionsSelectionInteractor.getSelectedQtsp()) + .thenReturn( + OptionsSelectionInteractorGetSelectedQtspPartialState.Success( + selectedQtsp = qtspData + ) + ) + + // Act + viewModel.setEvent( + Event.Initialize( + screenSelectionState = CERTIFICATE_SELECTION_STATE + ) + ) + + // Assert + assertEquals( + SelectionOptionUi( + overlineText = resourceProvider.getLocalizedString(LocalizableKey.SelectDocumentTitle), + mainText = documentData.documentName, + subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectDocumentSubtitle), + actionText = resourceProvider.getLocalizedString(LocalizableKey.View), + leadingIcon = AppIcons.StepOne, + leadingIconTint = ThemeColors.success, + trailingIcon = AppIcons.KeyboardArrowRight, + enabled = true, + event = Event.ViewDocumentItemPressed( + documentData = documentData + ) + ), + viewModel.viewState.value.documentSelectionItem + ) + assertEquals( + SelectionOptionUi( + overlineText = resourceProvider.getLocalizedString( + LocalizableKey.SigningService + ), + mainText = qtspData.name, + subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningServiceSubtitle), + actionText = null, + leadingIcon = AppIcons.StepTwo, + leadingIconTint = ThemeColors.success, + trailingIcon = AppIcons.KeyboardArrowRight, + enabled = false, + event = Event.RqesServiceSelectionItemPressed + ), + viewModel.viewState.value.qtspServiceSelectionItem + ) + assertEquals( + SelectionOptionUi( + overlineText = null, + mainText = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningCertificateTitle), + subtitle = resourceProvider.getLocalizedString(LocalizableKey.SelectCertificateSubtitle), + leadingIcon = AppIcons.StepThree, + trailingIcon = AppIcons.KeyboardArrowRight, + enabled = true, + event = Event.CertificateSelectionItemPressed + ), + viewModel.viewState.value.certificateSelectionItem + ) + } + + // Case 3 + // The function `setEvent` is tested to ensure that when the event `Event.BottomBarButtonPressed` is triggered + // Case 3 Expected Result: + // 1. The given uri should be opened and the activity should finish + @Test + fun `Given Case 3, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent(Event.BottomBarButtonPressed(uri = mockedUri.toUri())) + + // Assert + viewModel.effect.runFlowTest { + assertEquals(Effect.OpenUrl(mockedUri.toUri()), awaitItem()) + assertEquals(Effect.Navigation.Finish, awaitItem()) + } + } + + // Case 4 + // Function setEvent() is called with an Event.DismissError event. + // Case 4 Expected Result: + // 1. The view state should have its error field set to null, indicating that the error has been cleared. + @Test + fun `Given Case 4, When setEvent is called, Then the expected result is returned`() { + // Act + viewModel.setEvent(Event.DismissError) + + // Assert + assertNull(viewModel.viewState.value.error) + } + + // Case 5 + // Function setEvent() is called with an Event.BottomSheet.UpdateBottomSheetState event, + // setting isOpen to true. + // Case 5 Expected Result: + // 1. The bottom sheet should be opened and the view state's `isBottomSheetOpen` field should be true. + @Test + fun `Given Case 5, When setEvent is called, Then the expected result is returned`() { + // Act + viewModel.setEvent(Event.BottomSheet.UpdateBottomSheetState(isOpen = true)) + + // Assert + assertTrue(viewModel.viewState.value.isBottomSheetOpen) + } + + // Case 6 + // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed event. + // Case 6 Expected Result: + // 1. The bottom sheet should be closed and the view state's `isBottomSheetOpen` field should be false. + @Test + fun `Given Case 6, When setEvent is called, Then the expected result is returned`() { + // Act + viewModel.setEvent( + Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed + ) + + // Assert + assertFalse(viewModel.viewState.value.isBottomSheetOpen) + } + + // Case 7 + // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed event. + // Case 7 Expected Result: + // 1. The navigation effect should trigger a finish action. + @Test + fun `Given Case 7, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent( + Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed + ) + + // Assert + viewModel.effect.runFlowTest { + assertEquals(Effect.Navigation.Finish, awaitItem()) + } + } + + // Case 8 + // Function setEvent() is called with an Event.ViewDocumentItemPressed event, with + // documentData object as argument. + // Case 8 Expected Result: + // 1. The navigation effect should trigger a screen switch and the expected screen route + // based on the document data should be provided. + @Test + fun `Given Case 8, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val documentData = + DocumentData(documentName = mockedDocumentName, uri = documentFileUri) + + // Act + viewModel.setEvent( + Event.ViewDocumentItemPressed(documentData) + ) + + // Assert + viewModel.effect.runFlowTest { + val expectedScreenRoute = mockScreenRouteGeneration(documentData) + val expectedEffect = + Effect.Navigation.SwitchScreen(screenRoute = expectedScreenRoute) + assertEquals(expectedEffect, awaitItem()) + } + } + + // Case 9 + // Function setEvent() is called with an Event.BottomSheet.QtspSelectedOnDoneButtonPressed event, + // with qtstData as argument. + // Case 9 Expected Result: + // 1. The BottomSheet should close, emitting Effect.CloseBottomSheet. + // 2. The Qtsp selection should update, emitting Effect.OnSelectedQtspUpdated. + @Test + fun `Given Case 9, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + whenever(optionsSelectionInteractor.updateQtspUserSelection(qtspData)).thenReturn( + EudiRqesSetSelectedQtspPartialState.Success(service = rqesService) + ) + + // Act + viewModel.setEvent(Event.BottomSheet.QtspSelectedOnDoneButtonPressed(qtspData)) + + // Assert + viewModel.effect.runFlowTest { + assertEquals(Effect.CloseBottomSheet, awaitItem()) + assertEquals(Effect.OnSelectedQtspUpdated(service = rqesService), awaitItem()) + } + } + + // Case 10 + // Function setEvent() is called with an Event.FetchServiceAuthorizationUrl event and an + // rqesService object as argument. + // Case 10 Expected Result: + // 1. The service authorization URL should be fetched, triggering Effect.OpenUrl with the authorization Uri. + @Test + fun `Given Case 10, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val response = EudiRqesGetServiceAuthorizationUrlPartialState.Success( + authorizationUrl = mockedAuthorizationUrl.toUri() + ) + mockGetServiceAuthorizationUrlCall(response = response) + + // Act + viewModel.setEvent(Event.FetchServiceAuthorizationUrl(rqesService)) + + // Assert + viewModel.effect.runFlowTest { + assertTrue(awaitItem() is Effect.OpenUrl) + } + } + + // Case 11 + // Function setEvent(Event.Pop) is called to trigger the event of popping the current screen or action. + // Case 11 Expected Result: + // 1. The sheet content should be updated to the expected "ConfirmCancellation" content. + // 2. The bottom sheet should be shown (Effect.ShowBottomSheet). + @Test + fun `Given Case 11, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val sheetContent = OptionsSelectionBottomSheetContent.ConfirmCancellation( + bottomSheetTextData = mockConfirmCancellationTextData() + ) + + // Act + viewModel.setEvent(Event.Pop) + + // Assert + assertEquals(sheetContent, viewModel.viewState.value.sheetContent) + viewModel.effect.runFlowTest { + val expectedEffect = Effect.ShowBottomSheet + assertEquals(expectedEffect, awaitItem()) + } + } + //endregion + + + //region setEvent, Certificate step related + // Case 1 + // Event CertificateSelected is triggered by calling setEvent() with a selected certificate index. + // Case 1 Expected Result: + // 1. The ViewModel's state is updated with the selectedCertificateIndex set to the value passed in the event. + @Test + fun `Given Case 1 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val selectedIndex = 0 + + // Act + viewModel.setEvent( + Event.BottomSheet.CertificateIndexSelectedOnRadioButtonPressed(index = selectedIndex) + ) + + // Assert + assertEquals(selectedIndex, viewModel.viewState.value.selectedCertificateIndex) + } + + // Case 2 + // Events AuthorizeServiceAndFetchCertificates and BottomBarButtonPressed are triggered, simulating a + // scenario where certificates are successfully fetched and an authorization URL is returned. + // Case 2 Expected Result: + // 1. When BottomBarButtonPressed is triggered after successful certificate fetching, an OpenUrl effect is emitted. + // 2. The emitted URL matches the mocked authorization URI. + @Test + fun `Given Case 2 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val mockedCertificates = listOf(certificateData) + val fetchCertificatesResponse = + OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState.Success( + certificates = mockedCertificates + ) + val authResponse = EudiRqesGetCredentialAuthorizationUrlPartialState.Success( + authorizationUrl = authorizationUri + ) + mockAuthorizeServiceAndFetchCertificatesCall(response = fetchCertificatesResponse) + mockGetCredentialAuthorizationUrlCall(response = authResponse) + + // Act + viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) + viewModel.setEvent( + Event.BottomBarButtonPressed( + uri = authorizationUri + ) + ) + + // Assert + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.OpenUrl) + assertEquals(authorizationUri, (effect as Effect.OpenUrl).uri) + } + } + + // Case 3 + // Event AuthorizeServiceAndFetchCertificates is triggered, simulating a failure scenario where + // the interactor returns a Failure state with an error message. + // Case 3 Expected Result: + // 1. The ViewModel's state contains a non-null error object. + // 2. The error's subtitle matches the mocked failure message. + @Test + fun `Given Case 3 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val response = + OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState.Failure( + EudiRQESUiError(message = mockedFetchCertificatesFailureMessage) + ) + mockAuthorizeServiceAndFetchCertificatesCall(response = response) + + // Act + viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) + + // Assert + val viewState = viewModel.viewState.value + assertTrue(viewState.error != null) + assertEquals(mockedFetchCertificatesFailureMessage, viewState.error?.errorSubTitle) + } + + // Case 4 + // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed event. + // Case 4 Expected Result: + // 1. The effect should trigger a CloseBottomSheet action, indicating that the bottom sheet will be closed. + @Test + fun `Given Case 4 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent( + Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed + ) + + // Assert + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.CloseBottomSheet) + } + } + + // Case 5 + // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed event. + // Case 5 Expected Result: + // 1. The effect should trigger a Finish navigation action, indicating that the current screen will be finished. + @Test + fun `Given Case 5 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent( + Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed + ) + + // Assert + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.Navigation.Finish) + } + } + + // Case 6 + // Function setEvent() is called with an Event.Initialize for QTSP_SELECTION_STATE. + // Case 6 Expected Result: + // 1. The view state should reflect a non-null selectionItem with the correct documentData. + // 2. The effect should trigger Effect.OnSelectionItemCreated, indicating that the selection item + // was successfully created and set. + @Test + fun `Given Case 6 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val mockSuccessState = EudiRqesGetSelectedFilePartialState.Success(file = documentData) + whenever(optionsSelectionInteractor.getSelectedFile()).thenReturn(mockSuccessState) + + // Act + viewModel.setEvent(Event.Initialize(screenSelectionState = QTSP_SELECTION_STATE)) + + // Assert + viewModel.viewStateHistory.runFlowTest { + val state = awaitItem() + assertEquals(documentData, state.documentSelectionItem?.event?.documentData) + } + } + + // Case 7 + // Function setEvent() is called with an Event.Pop argument. + // Case 7 Expected Result: + // 1. The effect should trigger Effect.ShowBottomSheet, indicating that a bottom sheet is + // displayed to the user. + @Test + fun `Given Case 7 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent(Event.Pop) + + // Assert + viewModel.effect.runFlowTest { + assertEquals(Effect.ShowBottomSheet, awaitItem()) + } + } + + // Case 8 + // Function setEvent() is called with an Event.BottomSheet.UpdateBottomSheetState event, + // and argument isOpen with value of true. + // Case 8 Expected Result: + // 1. The view state should reflect that the bottom sheet is open, + // correspondingly isBottomSheetOpen state value should be true. + @Test + fun `Given Case 8 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent( + Event.BottomSheet.UpdateBottomSheetState(isOpen = true) + ) + + // Assert + assertTrue(viewModel.viewState.value.isBottomSheetOpen) + } + + // Case 9 + // Function setEvent() is called with an Event.DismissError event. + // Case 9 Expected Result: + // 1. The view state should have its error field set to null, indicating that the error has been cleared. + @Test + fun `Given Case 9 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Act + viewModel.setEvent(Event.DismissError) + + // Assert + assertTrue(viewModel.viewState.value.error == null) + } + + // Case 10 + // Function setEvent() is called with an RqesServiceSelectionItemPressed event. + // Scenario: User is on the Certificate selection step and triggers QTSP selection + // User is on Certificate selection step, QTSP selection is triggered + // Then the bottom sheet with QTSP options should be displayed + // Case 10 Expected Result: + // Bottom sheet content is properly populated with QTSP options + // Bottom sheet text data is correctly set + // Bottom sheet is shown through effect + @Test + fun `Given Case 10 on Certificate Step, When setEvent is called, Then bottom sheet with QTSP options is shown`() = + coroutineRule.runTest { + // Arrange + val mockQtsps = listOf(qtspData) + val selectedIndex = 0 + whenever(qtspData.name).thenReturn(mockedQtspName) + whenever(optionsSelectionInteractor.getQtsps()) + .thenReturn(EudiRqesGetQtspsPartialState.Success(qtsps = mockQtsps)) + + // Act + viewModel.setEvent(Event.RqesServiceSelectionItemPressed) + + // Assert + val expectedBottomSheetContent = OptionsSelectionBottomSheetContent.SelectQTSP( + bottomSheetTextData = BottomSheetTextData( + title = resourceProvider.getLocalizedString(LocalizableKey.SelectServiceTitle), + message = resourceProvider.getLocalizedString(LocalizableKey.SelectServiceSubtitle), + positiveButtonText = resourceProvider.getLocalizedString(LocalizableKey.Done), + negativeButtonText = resourceProvider.getLocalizedString(LocalizableKey.Cancel), + ), + options = listOf( + ModalOptionUi( + title = mockedQtspName, + trailingIcon = null, + event = Event.BottomSheet.QtspSelectedOnDoneButtonPressed( + mockQtsps[selectedIndex] + ), + radioButtonSelected = true + ), + ), + selectedIndex = selectedIndex + ) + + assertEquals(expectedBottomSheetContent, viewModel.viewState.value.sheetContent) + viewModel.effect.runFlowTest { + assertEquals(Effect.ShowBottomSheet, awaitItem()) + } + } + + // Case 11 + // Function setEvent() is called with an Event.RqesServiceSelectionItemPressed event and + // EudiRqesGetQtspsPartialState.Failure is returned + // Case 11 Expected Result: + // 1. Corresponding error should be set into view state. + @Test + fun `Given Case 11 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val errorMessage = mockedPlainFailureMessage + val failureState = EudiRqesGetQtspsPartialState.Failure( + error = EudiRQESUiError(message = errorMessage) + ) + whenever(optionsSelectionInteractor.getQtsps()) + .thenReturn(failureState) + + // Act + viewModel.setEvent(Event.RqesServiceSelectionItemPressed) + + // Assert + assertNotNull(viewModel.viewState.value.error) + } + + // Case 12 + // Function setEvent() is called with an Event.QtspSelectedOnDoneButtonPressed event on Certificate step and + // EudiRqesGetServiceAuthorizationUrlPartialStateFailure is returned + // Case 12 Expected Result: + // 1. Bottom sheet is closed (CloseBottomSheet effect is emitted) + // 2. Error state is set with correct error message + // 3. Error configuration includes retry and cancel actions + // 4. Error is properly dismissed when cancel is called + @Test + fun `Given Case 12 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val failureState = EudiRqesSetSelectedQtspPartialState.Failure( + error = EudiRQESUiError(message = mockedPlainFailureMessage) + ) + whenever(optionsSelectionInteractor.updateQtspUserSelection(qtspData)).thenReturn( + failureState + ) + + viewModel.setEvent( + Event.BottomSheet.QtspSelectedOnDoneButtonPressed( + qtspData = qtspData + ) + ) + + // Assert + with(viewModel.viewState.value) { + // Verify error state + assertNotNull(error) + assertEquals(mockedPlainFailureMessage, error.errorSubTitle) + + // Test error configuration + viewModel.effect.runFlowTest { + assertEquals(Effect.CloseBottomSheet, awaitItem()) + } + + // Test retry action + error.onRetry?.invoke() + + // Test cancel action + error.onCancel() + assertNull(viewModel.viewState.value.error) + } + } + + // Case 13 + // Function setEvent() is called with an Event.CertificateSelectionItemPressed event and + // multiple certificates are available in the state + // Case 13 Expected Result: + // 1. Bottom sheet content is populated with list of available certificates + // 2. Each certificate option has correct title and selection state + // 3. Bottom sheet text data contains correct localized strings + // 4. First certificate is selected by default (selectedIndex = 0) + // 5. ShowBottomSheet effect is emitted + @Test + fun `Given Case 13 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + whenever(certificateData.name).thenReturn(mockedCertificateName) + val mockedOtherCertificate = "Certificate 2" + val mockCertificates = listOf( + certificateData, + CertificateData(name = mockedOtherCertificate, certificate = credentialInfo) + ) + + whenever(optionsSelectionInteractor.authorizeServiceAndFetchCertificates()) + .thenReturn( + OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState.Success( + certificates = mockCertificates + ) + ) + + // First fetch the certificates + viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) + + // Act + viewModel.setEvent(Event.CertificateSelectionItemPressed) + + // Assert + val expectedBottomSheetContent = OptionsSelectionBottomSheetContent.SelectCertificate( + bottomSheetTextData = BottomSheetTextData( + title = resourceProvider.getLocalizedString(LocalizableKey.SigningCertificates), + message = resourceProvider.getLocalizedString(LocalizableKey.SelectSigningCertificateSubtitle), + positiveButtonText = resourceProvider.getLocalizedString(LocalizableKey.Done), + negativeButtonText = resourceProvider.getLocalizedString(LocalizableKey.Cancel), + ), + options = listOf( + ModalOptionUi( + title = mockedCertificateName, + trailingIcon = null, + event = Event.BottomSheet.CertificateSelectedOnDoneButtonPressed( + mockCertificates[0] + ), + radioButtonSelected = true + ), + ModalOptionUi( + title = mockedOtherCertificate, + trailingIcon = null, + event = Event.BottomSheet.CertificateSelectedOnDoneButtonPressed( + mockCertificates[1] + ), + radioButtonSelected = false + ) + ), + selectedIndex = 0 + ) + + assertEquals( + expectedBottomSheetContent, + viewModel.viewState.value.sheetContent + ) + + // Verify bottom sheet is shown + viewModel.effect.runFlowTest { + assertEquals(Effect.ShowBottomSheet, awaitItem()) + } + } + + // Case 14 + // Function setEvent() is called with an Event.BottomSheet.CertificateSelectedOnDoneButtonPressed event and + // EudiRqesGetCredentialAuthorizationUrlPartialState.Success is returned with authorization URL + // Case 14 Expected Result: + // 1. Bottom bar button is configured with correct text ("Continue") + // 2. Bottom bar button event contains the authorization URL + // 3. Bottom bar is made visible + // 4. No error state is present + // 5. Proper interaction with getCredentialAuthorizationUrl is verified + @Test + fun `Given Case 14 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val authorizationUrl = Uri.parse(mockedAuthorizationUrl) + val mockedCertificates = listOf( + certificateData + ) + whenever(certificateData.name).thenReturn(mockedCertificateName) + whenever(optionsSelectionInteractor.authorizeServiceAndFetchCertificates()) + .thenReturn( + OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState.Success( + certificates = mockedCertificates + ) + ) + + whenever(optionsSelectionInteractor.getCredentialAuthorizationUrl(certificateData)) + .thenReturn( + EudiRqesGetCredentialAuthorizationUrlPartialState.Success( + authorizationUrl = authorizationUrl + ) + ) + + // First load certificates + viewModel.setEvent( + Event.AuthorizeServiceAndFetchCertificates + ) + + // Act + viewModel.setEvent( + Event.BottomSheet.CertificateSelectedOnDoneButtonPressed( + certificateData + ) + ) + + // Assert + viewModel.viewState.runFlowTest { + with(viewModel.viewState.value) { + assertNotNull(bottomBarButtonAction) + bottomBarButtonAction.let { button -> + assertEquals( + resourceProvider.getLocalizedString(LocalizableKey.Continue), + button.buttonText + ) + + assertEquals( + authorizationUrl, + (button.event).uri + ) + } + + // Bottom bar visibility + assertTrue(isBottomBarButtonVisible) + + // Verify no error state + assertNull(error) + } + } + + // Verify interactions + verify(optionsSelectionInteractor).getCredentialAuthorizationUrl(certificateData) + } + + // Case 15 + // Function setEvent() is called with complete certificate selection flow: + // 1. Initialize with CERTIFICATE_SELECTION_STATE + // 2. Fetch certificates successfully + // 3. Select certificate index + // 4. Show certificate selection + // 5. Complete certificate selection with CertificateSelectedOnDoneButtonPressed + // Case 15 Expected Result: + // 1. Certificate selection item is properly updated with: + // - Correct certificate name as main text + // - Success color for leading icon + // - Disabled state + // 2. Selected file and QTSP data are properly maintained + // 3. Complete flow executes without errors + @Test + fun `Given Case 15 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + whenever(optionsSelectionInteractor.getSelectedFile()) + .thenReturn(EudiRqesGetSelectedFilePartialState.Success(file = documentData)) + whenever(optionsSelectionInteractor.getSelectedQtsp()) + .thenReturn( + OptionsSelectionInteractorGetSelectedQtspPartialState.Success( + selectedQtsp = qtspData + ) + ) + + val authorizationUrl = Uri.parse(mockedAuthorizationUrl) + whenever(certificateData.name).thenReturn(mockedCertificateName) + + // Mock successful certificates fetch + val mockCertificates = listOf(certificateData) + whenever(optionsSelectionInteractor.authorizeServiceAndFetchCertificates()) + .thenReturn( + OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState.Success( + certificates = mockCertificates + ) + ) + + // Mock authorization URL fetch + whenever(optionsSelectionInteractor.getCredentialAuthorizationUrl(certificateData)) + .thenReturn( + EudiRqesGetCredentialAuthorizationUrlPartialState.Success( + authorizationUrl = authorizationUrl + ) + ) + + viewModel.setEvent(Event.Initialize(screenSelectionState = CERTIFICATE_SELECTION_STATE)) + viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) + // Click certificate selection screen item + viewModel.setEvent(Event.CertificateSelectionItemPressed) + // Select certificate index + viewModel.setEvent(Event.BottomSheet.CertificateIndexSelectedOnRadioButtonPressed(0)) + + // Act + viewModel.setEvent( + Event.BottomSheet.CertificateSelectedOnDoneButtonPressed( + certificateData + ) + ) + + // Assert + with(viewModel.viewState.value) { + assertNotNull(certificateSelectionItem) + with(certificateSelectionItem) { + assertEquals(mockedCertificateName, mainText) + assertEquals(ThemeColors.success, leadingIconTint) + assertFalse(enabled) + } + } + } + + // Case 16 + // Function setEvent() is called with Event.Initialize(QTSP_SELECTION_STATE) and + // EudiRqesGetSelectedFilePartialState.Failure is returned when attempting to get selected file + // Case 16 Expected Result: + // 1. Error state is set with: + // - Correct error message + // - Working cancel action that: + // * Dismisses error + // * Triggers Navigation.Finish effect + // - Working retry action that: + // * Attempts to get selected file again + // 3. Error handling flow completes properly + @Test + fun `Given Case 16 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val errorMessage = mockedPlainFailureMessage + whenever(optionsSelectionInteractor.getSelectedFile()) + .thenReturn( + EudiRqesGetSelectedFilePartialState.Failure( + error = EudiRQESUiError(message = errorMessage) + ) + ) + + // Act + viewModel.setEvent( + Event.Initialize(screenSelectionState = QTSP_SELECTION_STATE) + ) + + // Assert + with(viewModel.viewState.value) { + // Verify error state + assertNotNull(error) + with(error) { + // Check error message + assertEquals(errorMessage, errorSubTitle) + + // Test cancel action + onCancel() + + // Verify error is dismissed and navigation effect is triggered + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.Navigation.Finish) + } + + // Test retry action + onRetry?.invoke() + verify(optionsSelectionInteractor, times(4)).getSelectedFile() + } + } + } + + // Case 17 + // Function setEvent() is called with Event.BottomSheet.QtspIndexSelectedOnRadioButtonPressed and + // a specific index is selected in the QTSP selection bottom sheet + // Case 17 Expected Result: + // 1. Selected QTSP index in view state is updated to match the selected index + // 2. State update occurs without side effects + @Test + fun `Given Case 17 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val indexSelected = 0 + + // Act + viewModel.setEvent( + Event.BottomSheet.QtspIndexSelectedOnRadioButtonPressed(index = indexSelected) + ) + + // Assert + assertEquals( + indexSelected, + viewModel.viewState.value.selectedQtspIndex + ) + } + + // Case 18 + // Function setEvent() is called with Event.Initialize(CERTIFICATE_SELECTION_STATE) and + // OptionsSelectionInteractorGetSelectedQtspPartialState.Failure is returned when attempting to get selected QTSP + // (while file selection succeeds) + // Case 18 Expected Result: + // 1. Error state is set with: + // - Correct error message from failure + // - Working cancel action + // - Working retry action that attempts to get selected QTSP again + // 2. File selection success state is maintained + @Test + fun `Given Case 18 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + whenever(optionsSelectionInteractor.getSelectedFile()) + .thenReturn(EudiRqesGetSelectedFilePartialState.Success(file = documentData)) + + val errorMessage = mockedPlainFailureMessage + whenever(optionsSelectionInteractor.getSelectedQtsp()) + .thenReturn( + OptionsSelectionInteractorGetSelectedQtspPartialState.Failure( + error = EudiRQESUiError(message = errorMessage) + ) + ) + + // Act + viewModel.setEvent( + Event.Initialize(screenSelectionState = CERTIFICATE_SELECTION_STATE) + ) + + // Assert + with(viewModel.viewState.value) { + // Assert error state + assertNotNull(error) + with(error) { + assertEquals(errorMessage, errorSubTitle) + // Test cancel action + onCancel() + // Test retry action + onRetry?.invoke() + } + } + } + + // Case 19 + // Function setEvent() is called with complete certificate selection flow but + // EudiRqesGetCredentialAuthorizationUrlPartialState.Failure is returned when fetching authorization URL + // Flow: + // 1. Successfully fetch certificates + // 2. Select certificate index + // 3. Attempt to get credential authorization URL (fails) + // Case 19 Expected Result: + // 1. Error state is set with: + // - Correct error message + // - Working retry action that: + // * Dismisses error + // * Attempts to get authorization URL again + // - Working cancel action that dismisses error + // 2. Bottom bar is properly handled: + // - Not visible during error state + // - Button action is null + // 3. Interactions are verified: + // - Authorization URL is requested correct number of times + @Test + fun `Given Case 19 on Certificate Step, When setEvent is called, Then the expected result is returned`() = + coroutineRule.runTest { + // Arrange + val errorMessage = mockedPlainFailureMessage + + // Mock certificate data + whenever(certificateData.name).thenReturn(mockedCertificateName) + + // Mock successful certificates fetch + val mockedCertificates = listOf(certificateData) + whenever(optionsSelectionInteractor.authorizeServiceAndFetchCertificates()) + .thenReturn( + OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState.Success( + certificates = mockedCertificates + ) + ) + + // Mock authorization URL fetch failure + whenever(optionsSelectionInteractor.getCredentialAuthorizationUrl(certificateData)) + .thenReturn( + EudiRqesGetCredentialAuthorizationUrlPartialState.Failure( + error = EudiRQESUiError(message = errorMessage) + ) + ) + + // Initialize certificates + viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) + + // Select certificate index + val indexSelected = 0 + viewModel.setEvent( + Event.BottomSheet.CertificateIndexSelectedOnRadioButtonPressed(index = indexSelected) + ) + + // Act + viewModel.setEvent( + Event.BottomSheet.CertificateSelectedOnDoneButtonPressed( + certificateData + ) + ) + + // Assert + with(viewModel.viewState.value) { + // Verify error state + assertNotNull(error) + error.let { errorConfig -> + assertEquals(errorMessage, errorConfig.errorSubTitle) + + // Test retry action + errorConfig.onRetry?.invoke() + // Should dismiss error and retry the event + verify(optionsSelectionInteractor, times(2)) + .getCredentialAuthorizationUrl(certificateData) + + // Test cancel action + errorConfig.onCancel() + // After cancel, error should be dismissed + assertNull(viewModel.viewState.value.error) + } + + // Verify bottom bar is not visible in error state + assertFalse(isBottomBarButtonVisible) + assertNull(bottomBarButtonAction) + } + + // Verify interactions + verify(optionsSelectionInteractor, times(2)) + .getCredentialAuthorizationUrl(certificateData) + } + //endregion + + //region simple events being called and then effect being triggered + + @Test + fun `When BottomSheet CancelCertificateSelection event is sent, Then CloseBottomSheet effect is observed`() = + coroutineRule.runTest { + viewModel.setEvent(Event.BottomSheet.CancelCertificateSelection) + + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.CloseBottomSheet) + } + } + + @Test + fun `When Finish event is sent, Then Navigation Finish effect is observed`() = + coroutineRule.runTest { + viewModel.setEvent(Event.Finish) + + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.Navigation.Finish) + } + } + + @Test + fun `When BottomSheet CancelQtspSelection event is sent, Then CloseBottomSheet effect is observed`() = + coroutineRule.runTest { + viewModel.setEvent(Event.BottomSheet.CancelQtspSelection) + + viewModel.effect.runFlowTest { + val effect = awaitItem() + assertTrue(effect is Effect.CloseBottomSheet) + } + } + //end region + + + //region of helper functions + private fun mockLocalizedStrings(resourceProvider: ResourceProvider) { + LocalizableKey.entries.forEach { key -> + whenever(resourceProvider.getLocalizedString(key)) + .thenReturn(key.defaultTranslation()) + } + } + + private fun mockScreenRouteGeneration(documentData: DocumentData): String { + return generateComposableNavigationLink( + screen = SdkScreens.ViewDocument, + arguments = generateComposableArguments( + arguments = mapOf( + ViewDocumentUiConfig.serializedKeyName to uiSerializer.toBase64( + model = ViewDocumentUiConfig( + isSigned = true, + documentData = documentData + ), + parser = ViewDocumentUiConfig.Parser + ) + ) + ) + ) + } + + private suspend fun mockGetServiceAuthorizationUrlCall(response: EudiRqesGetServiceAuthorizationUrlPartialState) { + whenever(optionsSelectionInteractor.getServiceAuthorizationUrl(rqesService)) + .thenReturn(response) + } + + private fun mockConfirmCancellationTextData(): BottomSheetTextData { + return BottomSheetTextData( + title = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessTitle), + message = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSubtitle), + positiveButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessPrimaryText), + negativeButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSecondaryText), + ) + } + + private suspend fun mockAuthorizeServiceAndFetchCertificatesCall( + response: OptionsSelectionInteractorAuthorizeServiceAndFetchCertificatesPartialState + ) { + whenever(optionsSelectionInteractor.authorizeServiceAndFetchCertificates()) + .thenReturn(response) + } + + private suspend fun mockGetCredentialAuthorizationUrlCall(response: EudiRqesGetCredentialAuthorizationUrlPartialState) { + whenever(optionsSelectionInteractor.getCredentialAuthorizationUrl(certificateData)) + .thenReturn(response) + } + //endregion +} \ No newline at end of file diff --git a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_certificate/TestSelectCertificateViewModel.kt b/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_certificate/TestSelectCertificateViewModel.kt deleted file mode 100644 index 5a278ad..0000000 --- a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_certificate/TestSelectCertificateViewModel.kt +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright (c) 2023 European Commission - * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European - * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work - * except in compliance with the Licence. - * - * You may obtain a copy of the Licence at: - * https://joinup.ec.europa.eu/software/page/eupl - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF - * ANY KIND, either express or implied. See the Licence for the specific language - * governing permissions and limitations under the Licence. - */ - -/** -package eu.europa.ec.eudi.rqesui.presentation.ui.select_certificate - - -import android.net.Uri -import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetCredentialAuthorizationUrlPartialState -import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetSelectedFilePartialState -import eu.europa.ec.eudi.rqesui.domain.entities.error.EudiRQESUiError -import eu.europa.ec.eudi.rqesui.domain.entities.localization.LocalizableKey -import eu.europa.ec.eudi.rqesui.infrastructure.config.data.CertificateData -import eu.europa.ec.eudi.rqesui.infrastructure.config.data.DocumentData -import eu.europa.ec.eudi.rqesui.infrastructure.provider.ResourceProvider -import eu.europa.ec.eudi.rqesui.util.CoroutineTestRule -import eu.europa.ec.eudi.rqesui.util.mockedFetchCertificatesFailureMessage -import eu.europa.ec.eudi.rqesui.util.runFlowTest -import eu.europa.ec.eudi.rqesui.util.runTest -import junit.framework.TestCase.assertEquals -import junit.framework.TestCase.assertNotNull -import junit.framework.TestCase.assertTrue -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.whenever -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -import kotlin.test.Test - -@RunWith(RobolectricTestRunner::class) -@Config(manifest = Config.NONE) -class TestSelectCertificateViewModel { - @get:Rule - val coroutineRule = CoroutineTestRule() - - @Mock - private lateinit var selectCertificateInteractor: SelectCertificateInteractor - - @Mock - private lateinit var resourceProvider: ResourceProvider - - @Mock - private lateinit var authorizationUri: Uri - - @Mock - private lateinit var certificateData: CertificateData - - @Mock - private lateinit var documentData: DocumentData - - private lateinit var viewModel: SelectCertificateViewModel - - private lateinit var autoCloseable: AutoCloseable - - @Before - fun setUp() { - autoCloseable = MockitoAnnotations.openMocks(this) - viewModel = SelectCertificateViewModel(selectCertificateInteractor, resourceProvider) - - mockLocalizedStrings(resourceProvider) - } - - @After - fun after() { - autoCloseable.close() - } - - //region setInitialState - // Case 1 - // Function setInitialState() is called to initialize the ViewModel's state. - // Case 1 Expected Result: - // 1. The title is set to the localized string for SignDocument. - // 2. The subtitle is set to the localized string for SelectCertificateTitle. - // 3. The certificates section title is set to the localized string for SelectCertificateSubtitle. - // 4. The bottom bar button text is set to the localized string for Sign. - @Test - fun `Given Case 1, When setInitialState is called, Then the expected result is returned`() { - // Act - viewModel.setInitialState() - - // Assert - assertEquals( - resourceProvider.getLocalizedString(LocalizableKey.SignDocument), - viewModel.viewState.value.title - ) - assertEquals( - resourceProvider.getLocalizedString(LocalizableKey.SelectCertificateTitle), - viewModel.viewState.value.subtitle - ) - assertEquals( - resourceProvider.getLocalizedString(LocalizableKey.SelectCertificateSubtitle), - viewModel.viewState.value.certificatesSectionTitle - ) - assertEquals( - resourceProvider.getLocalizedString(LocalizableKey.Sign), - viewModel.viewState.value.bottomBarButtonText - ) - } - //endregion - - //region setEvent - // Case 1 - // Event CertificateSelected is triggered by calling setEvent() with a selected certificate index. - // Case 1 Expected Result: - // 1. The ViewModel's state is updated with the selectedCertificateIndex set to the value passed in the event. - @Test - fun `Given Case 1, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val selectedIndex = 0 - - // Act - viewModel.setEvent( - Event.CertificateSelected(index = selectedIndex) - ) - - // Assert - assertEquals(selectedIndex, viewModel.viewState.value.selectedCertificateIndex) - } - - // Case 2 - // Events AuthorizeServiceAndFetchCertificates and BottomBarButtonPressed are triggered, simulating a - // scenario where certificates are successfully fetched and an authorization URL is returned. - // Case 2 Expected Result: - // 1. When BottomBarButtonPressed is triggered after successful certificate fetching, an OpenUrl effect is emitted. - // 2. The emitted URL matches the mocked authorization URI. - @Test - fun `Given Case 2, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val mockedCertificates = listOf(certificateData) - val fetchCertificatesResponse = - SelectCertificateInteractorAuthorizeServiceAndFetchCertificatesPartialState.Success( - certificates = mockedCertificates - ) - val authResponse = EudiRqesGetCredentialAuthorizationUrlPartialState.Success( - authorizationUrl = authorizationUri - ) - mockAuthorizeServiceAndFetchCertificatesCall(response = fetchCertificatesResponse) - mockGetCredentialAuthorizationUrlCall(response = authResponse) - - // Act - viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) - viewModel.setEvent(Event.BottomBarButtonPressed) - - // Assert - viewModel.effect.runFlowTest { - val effect = awaitItem() - assertTrue(effect is Effect.OpenUrl) - assertEquals(authorizationUri, (effect as Effect.OpenUrl).uri) - } - } - - // Case 3 - // Event AuthorizeServiceAndFetchCertificates is triggered, simulating a failure scenario where - // the interactor returns a Failure state with an error message. - // Case 3 Expected Result: - // 1. The ViewModel's state contains a non-null error object. - // 2. The error's subtitle matches the mocked failure message. - @Test - fun `Given Case 3, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val response = - SelectCertificateInteractorAuthorizeServiceAndFetchCertificatesPartialState.Failure( - EudiRQESUiError(message = mockedFetchCertificatesFailureMessage) - ) - mockAuthorizeServiceAndFetchCertificatesCall(response = response) - - // Act - viewModel.setEvent(Event.AuthorizeServiceAndFetchCertificates) - - // Assert - val viewState = viewModel.viewState.value - assertTrue(viewState.error != null) - assertEquals(mockedFetchCertificatesFailureMessage, viewState.error?.errorSubTitle) - } - - // Case 4 - // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed event. - // Case 4 Expected Result: - // 1. The effect should trigger a CloseBottomSheet action, indicating that the bottom sheet will be closed. - @Test - fun `Given Case 4, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent( - Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed - ) - - // Assert - viewModel.effect.runFlowTest { - val effect = awaitItem() - assertTrue(effect is Effect.CloseBottomSheet) - } - } - - // Case 5 - // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed event. - // Case 5 Expected Result: - // 1. The effect should trigger a Finish navigation action, indicating that the current screen will be finished. - @Test - fun `Given Case 5, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent(Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed) - - // Assert - viewModel.effect.runFlowTest { - val effect = awaitItem() - assertTrue(effect is Effect.Navigation.Finish) - } - } - - // Case 6 - // Function setEvent() is called with an Event.Init. - // Case 6 Expected Result: - // 1. The view state should reflect a non-null selectionItem with the correct documentData. - // 2. The effect should trigger Effect.OnSelectionItemCreated, indicating that the selection item - // was successfully created and set. - @Test - fun `Given Case 6, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val mockSuccessState = EudiRqesGetSelectedFilePartialState.Success(file = documentData) - whenever(selectCertificateInteractor.getSelectedFile()).thenReturn(mockSuccessState) - - // Act - viewModel.setEvent(Event.Init) - - // Assert - viewModel.viewStateHistory.runFlowTest { - val state = awaitItem() - assertNotNull(state.selectionItem) - assertEquals(documentData, state.selectionItem?.documentData) - } - - viewModel.effect.runFlowTest { - assertEquals(Effect.OnSelectionItemCreated, awaitItem()) - } - } - - // Case 7 - // Function setEvent() is called with an Event.Pop argument. - // Case 7 Expected Result: - // 1. The effect should trigger Effect.ShowBottomSheet, indicating that a bottom sheet is - // displayed to the user. - @Test - fun `Given Case 7, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent(Event.Pop) - - // Assert - viewModel.effect.runFlowTest { - assertEquals(Effect.ShowBottomSheet, awaitItem()) - } - } - - // Case 8 - // Function setEvent() is called with an Event.BottomSheet.UpdateBottomSheetState event, - // and argument isOpen with value of true. - // Case 8 Expected Result: - // 1. The view state should reflect that the bottom sheet is open, - // correspondingly isBottomSheetOpen state value should be true. - @Test - fun `Given Case 8, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent( - Event.BottomSheet.UpdateBottomSheetState(isOpen = true) - ) - - // Assert - assertTrue(viewModel.viewState.value.isBottomSheetOpen) - } - - // Case 9 - // Function setEvent() is called with an Event.DismissError event. - // Case 9 Expected Result: - // 1. The view state should have its error field set to null, indicating that the error has been cleared. - @Test - fun `Given Case 9, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent(Event.DismissError) - - // Assert - assertTrue(viewModel.viewState.value.error == null) - } - //endregion - - //region of helper functions - private fun mockLocalizedStrings(resourceProvider: ResourceProvider) { - LocalizableKey.entries.forEach { key -> - whenever(resourceProvider.getLocalizedString(key)).thenReturn(key.defaultTranslation()) - } - } - - private suspend fun mockAuthorizeServiceAndFetchCertificatesCall( - response: SelectCertificateInteractorAuthorizeServiceAndFetchCertificatesPartialState - ) { - whenever(selectCertificateInteractor.authorizeServiceAndFetchCertificates()) - .thenReturn(response) - } - - private suspend fun mockGetCredentialAuthorizationUrlCall(response: EudiRqesGetCredentialAuthorizationUrlPartialState) { - whenever(selectCertificateInteractor.getCredentialAuthorizationUrl(certificateData)) - .thenReturn(response) - } - //endregion -} - */ \ No newline at end of file diff --git a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_qtsp/TestSelectQtspViewModel.kt b/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_qtsp/TestSelectQtspViewModel.kt deleted file mode 100644 index 66ca761..0000000 --- a/rqes-ui-sdk/src/test/java/eu/europa/ec/eudi/rqesui/presentation/ui/select_qtsp/TestSelectQtspViewModel.kt +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (c) 2023 European Commission - * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European - * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work - * except in compliance with the Licence. - * - * You may obtain a copy of the Licence at: - * https://joinup.ec.europa.eu/software/page/eupl - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF - * ANY KIND, either express or implied. See the Licence for the specific language - * governing permissions and limitations under the Licence. - */ - -/* - -package eu.europa.ec.eudi.rqesui.presentation.ui.select_qtsp - -import android.net.Uri -import eu.europa.ec.eudi.rqes.core.RQESService -import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetQtspsPartialState -import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetSelectedFilePartialState -import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesGetServiceAuthorizationUrlPartialState -import eu.europa.ec.eudi.rqesui.domain.controller.EudiRqesSetSelectedQtspPartialState -import eu.europa.ec.eudi.rqesui.domain.entities.error.EudiRQESUiError -import eu.europa.ec.eudi.rqesui.domain.entities.localization.LocalizableKey -import eu.europa.ec.eudi.rqesui.domain.extension.toUri -import eu.europa.ec.eudi.rqesui.domain.interactor.OptionsSelectionInteractor -import eu.europa.ec.eudi.rqesui.domain.serializer.UiSerializer -import eu.europa.ec.eudi.rqesui.infrastructure.config.data.DocumentData -import eu.europa.ec.eudi.rqesui.infrastructure.config.data.QtspData -import eu.europa.ec.eudi.rqesui.infrastructure.provider.ResourceProvider -import eu.europa.ec.eudi.rqesui.presentation.entities.ModalOptionUi -import eu.europa.ec.eudi.rqesui.presentation.entities.config.ViewDocumentUiConfig -import eu.europa.ec.eudi.rqesui.presentation.navigation.SdkScreens -import eu.europa.ec.eudi.rqesui.presentation.navigation.helper.generateComposableArguments -import eu.europa.ec.eudi.rqesui.presentation.navigation.helper.generateComposableNavigationLink -import eu.europa.ec.eudi.rqesui.presentation.ui.component.wrap.BottomSheetTextData -import eu.europa.ec.eudi.rqesui.util.CoroutineTestRule -import eu.europa.ec.eudi.rqesui.util.mockedAuthorizationUrl -import eu.europa.ec.eudi.rqesui.util.mockedDocumentName -import eu.europa.ec.eudi.rqesui.util.mockedLocalFileUri -import eu.europa.ec.eudi.rqesui.util.mockedPlainFailureMessage -import eu.europa.ec.eudi.rqesui.util.mockedQtspName -import eu.europa.ec.eudi.rqesui.util.runFlowTest -import eu.europa.ec.eudi.rqesui.util.runTest -import junit.framework.TestCase.assertNull -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.whenever -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -@RunWith(RobolectricTestRunner::class) -@Config(manifest = Config.NONE) -class TestSelectQtspViewModel { - - @get:Rule - val coroutineRule = CoroutineTestRule() - - @Mock - private lateinit var selectQtspInteractor: SelectQtspInteractor - - @Mock - private lateinit var resourceProvider: ResourceProvider - - @Mock - private lateinit var uiSerializer: UiSerializer - - @Mock - private lateinit var qtspData: QtspData - - @Mock - private lateinit var rqesService: RQESService - - @Mock - private lateinit var documentData: DocumentData - - private val documentFileUri = Uri.parse(mockedLocalFileUri) - - private lateinit var viewModel: SelectQtspViewModel - - private lateinit var autoCloseable: AutoCloseable - - @Before - fun setUp() { - autoCloseable = MockitoAnnotations.openMocks(this) - viewModel = SelectQtspViewModel(selectQtspInteractor, resourceProvider, uiSerializer) - mockLocalizedStrings(resourceProvider) - } - - @After - fun after() { - autoCloseable.close() - } - - //region setInitialState - // Case 1 - // The function setInitialState() is called to initialize the ViewModel's state with default values. - // Case 1 Expected Result: - // 1. The title is set to the localized string for "SignDocument". - // 2. The subtitle is set to the localized string for "ConfirmSelectionTitle". - // 3. The bottom bar button text is set to the localized string for "Sign". - // 4. The bottom sheet content is set with a "ConfirmCancellation" structure containing localized - // strings for CancelSignProcessTitle, CancelSignProcessSubtitle, CancelSignProcessPrimaryText, - // CancelSignProcessSecondaryText. - @Test - fun `Given Case 1, When setInitialState is called, Then the expected result is returned`() { - // Act - viewModel.setInitialState() - - // Assert - val expectedState = State( - isLoading = false, - selectionItem = null, - error = null, - isBottomSheetOpen = false, - title = resourceProvider.getLocalizedString(LocalizableKey.SignDocument), - subtitle = resourceProvider.getLocalizedString(LocalizableKey.ConfirmSelectionTitle), - bottomBarButtonText = resourceProvider.getLocalizedString(LocalizableKey.Sign), - sheetContent = SelectQtspBottomSheetContent.ConfirmCancellation( - bottomSheetTextData = BottomSheetTextData( - title = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessTitle), - message = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSubtitle), - positiveButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessPrimaryText), - negativeButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSecondaryText) - ) - ) - ) - assertEquals(expectedState, viewModel.viewState.value) - } - //endregion - - //region setEvent - // Case 1 - // The function `createSelectionItem` is tested to ensure that when it sets the success state, - // the ViewModel updates the `selectionItem` in the state correctly. - // Case 1 Expected Result: - // 1. The `selectQtspInteractor.getSelectedFile()` function returns a successful state with `documentData`. - // 2. The `viewModel.setEvent(Event.Init)` function is called, triggering the creation of a `SelectionItemUi`. - // 3. The `selectionItem` in the ViewModel's state is updated to reflect the successful selection, - // containing the expected `documentData` and `View` action string. - @Test - fun `Given Case 1, When setEvent is called, Then the expected result is returned`() { - // Arrange - whenever(selectQtspInteractor.getSelectedFile()) - .thenReturn(EudiRqesGetSelectedFilePartialState.Success(file = documentData)) - - // Act - viewModel.setEvent(Event.Init) - - // Assert - assertEquals( - SelectionItemUi( - documentData = documentData, - action = resourceProvider.getLocalizedString(LocalizableKey.View), - subtitle = null - ), - viewModel.viewState.value.selectionItem - ) - } - - // Case 2 - // The function `setEvent` is tested to ensure that when the event `Event.BottomBarButtonPressed` is triggered - // Case 2 Expected Result: - // 1. The `selectQtspInteractor.getQtsps()` function returns a successful state with a list of QTSP data. - // 2. The `viewModel.setEvent(Event.BottomBarButtonPressed)` function is called, triggering the update of the `sheetContent`. - // 3. The `sheetContent` in the ViewModel's state is updated to reflect a `SelectQtspBottomSheetContent.SelectQTSP`. - // 4. The `bottomSheetTextData` is set with localized string values for the title and message. - // 5. The `options` in the `sheetContent` are a list of `ModalOptionUi` objects, each corresponding to a QTSP data entry - @Test - fun `Given Case 2, When setEvent is called, Then the expected result is returned`() { - // Arrange - val mockedQtspDataList = listOf(qtspData) - whenever(qtspData.name).thenReturn(mockedQtspName) - whenever(selectQtspInteractor.getQtsps()) - .thenReturn(EudiRqesGetQtspsPartialState.Success(qtsps = mockedQtspDataList)) - - // Act - viewModel.setEvent(Event.BottomBarButtonPressed) - - // Assert - val bottomSheetOptions: List> = - mockedQtspDataList.map { qtspData -> - ModalOptionUi( - title = qtspData.name, - trailingIcon = null, - event = Event.BottomSheet.QtspSelected(qtspData) - ) - } - val sheetContent = SelectQtspBottomSheetContent.SelectQTSP( - bottomSheetTextData = BottomSheetTextData( - title = resourceProvider.getLocalizedString(LocalizableKey.SelectServiceTitle), - message = resourceProvider.getLocalizedString(LocalizableKey.SelectServiceSubtitle), - ), - options = bottomSheetOptions - ) - - assertEquals(sheetContent, viewModel.viewState.value.sheetContent) - } - - // Case 3 - // The function `setEvent` is tested to ensure that when the event `Event.FetchServiceAuthorizationUrl` is triggered - // and a failure response is returned from the `mockGetServiceAuthorizationUrlCall` - // Case 3 Expected Result: - // 1. The `mockGetServiceAuthorizationUrlCall` is called with a response that simulates a failure, - // returning an error. - // 2. The `setEvent` call with event of `Event.FetchServiceAuthorizationUrl` triggers a service authorization fetch action. - // 3. The `viewModel.viewState.value.error` is updated with the error returned in the failure response. - // 4. The test asserts that the `error` in the `viewState` is not null, ensuring that the failure - // is correctly reflected in the ViewModel's state. - @Test - fun `Given Case 3, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val response = EudiRqesGetServiceAuthorizationUrlPartialState.Failure( - error = EudiRQESUiError(mockedPlainFailureMessage) - ) - mockGetServiceAuthorizationUrlCall(response = response) - - // Act - viewModel.setEvent(Event.FetchServiceAuthorizationUrl(rqesService)) - - // Assert - assertNotNull(viewModel.viewState.value.error) - } - - // Case 4 - // Function setEvent() is called with an Event.Finish event. - // Case 4 Expected Result: - // 1. The effect should trigger a navigation finish action. - @Test - fun `Given Case 4, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent(Event.Finish) - - // Assert - viewModel.effect.runFlowTest { - assertEquals(Effect.Navigation.Finish, awaitItem()) - } - } - - // Case 5 - // Function setEvent() is called with an Event.DismissError event. - // Case 5 Expected Result: - // 1. The view state should have its error field set to null, indicating that the error has been cleared. - @Test - fun `Given Case 5, When setEvent is called, Then the expected result is returned`() { - // Act - viewModel.setEvent(Event.DismissError) - - // Assert - assertNull(viewModel.viewState.value.error) - } - - // Case 6 - // Function setEvent() is called with an Event.BottomSheet.UpdateBottomSheetState event, - // setting isOpen to true. - // Case 6 Expected Result: - // 1. The bottom sheet should be opened and the view state's `isBottomSheetOpen` field should be true. - @Test - fun `Given Case 6, When setEvent is called, Then the expected result is returned`() { - // Act - viewModel.setEvent(Event.BottomSheet.UpdateBottomSheetState(isOpen = true)) - - // Assert - assertTrue(viewModel.viewState.value.isBottomSheetOpen) - } - - // Case 7 - // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed event. - // Case 7 Expected Result: - // 1. The bottom sheet should be closed and the view state's `isBottomSheetOpen` field should be false. - @Test - fun `Given Case 7, When setEvent is called, Then the expected result is returned`() { - // Act - viewModel.setEvent(Event.BottomSheet.CancelSignProcess.PrimaryButtonPressed) - - // Assert - assertFalse(viewModel.viewState.value.isBottomSheetOpen) - } - - // Case 8 - // Function setEvent() is called with an Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed event. - // Case 8 Expected Result: - // 1. The navigation effect should trigger a finish action. - @Test - fun `Given Case 8, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Act - viewModel.setEvent(Event.BottomSheet.CancelSignProcess.SecondaryButtonPressed) - - // Assert - viewModel.effect.runFlowTest { - assertEquals(Effect.Navigation.Finish, awaitItem()) - } - } - - // Case 9 - // Function setEvent() is called with an Event.ViewDocument event, with - // documentData object as argument. - // Case 9 Expected Result: - // 1. The navigation effect should trigger a screen switch and the expected screen route - // based on the document data should be provided. - @Test - fun `Given Case 9, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val documentData = - DocumentData(documentName = mockedDocumentName, uri = documentFileUri) - - // Act - viewModel.setEvent(Event.ViewDocument(documentData)) - - // Assert - viewModel.effect.runFlowTest { - val expectedScreenRoute = mockScreenRouteGeneration(documentData) - val expectedEffect = - Effect.Navigation.SwitchScreen(screenRoute = expectedScreenRoute) - assertEquals(expectedEffect, awaitItem()) - } - } - - // Case 10 - // Function setEvent() is called with an Event.BottomSheet.QtspSelected event, with - // qtstData as argument. - // Case 10 Expected Result: - // 1. The BottomSheet should close, emitting Effect.CloseBottomSheet. - // 2. The Qtsp selection should update, emitting Effect.OnSelectedQtspUpdated. - @Test - fun `Given Case 10, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - whenever(selectQtspInteractor.updateQtspUserSelection(qtspData)).thenReturn( - EudiRqesSetSelectedQtspPartialState.Success(service = rqesService) - ) - - // Act - viewModel.setEvent(Event.BottomSheet.QtspSelected(qtspData)) - - // Assert - viewModel.effect.runFlowTest { - assertEquals(Effect.CloseBottomSheet, awaitItem()) - assertEquals(Effect.OnSelectedQtspUpdated(service = rqesService), awaitItem()) - } - } - - // Case 11 - // Function setEvent() is called with an Event.FetchServiceAuthorizationUrl event and an - // rqesService object as argument. - // Case 11 Expected Result: - // 1. The service authorization URL should be fetched, triggering Effect.OpenUrl with the authorization Uri. - @Test - fun `Given Case 11, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val response = EudiRqesGetServiceAuthorizationUrlPartialState.Success( - authorizationUrl = mockedAuthorizationUrl.toUri() - ) - mockGetServiceAuthorizationUrlCall(response = response) - - // Act - viewModel.setEvent(Event.FetchServiceAuthorizationUrl(rqesService)) - - // Assert - viewModel.effect.runFlowTest { - assertTrue(awaitItem() is Effect.OpenUrl) - } - } - - // Case 12 - // Function setEvent(Event.Pop) is called to trigger the event of popping the current screen or action. - // Case 12 Expected Result: - // 1. The sheet content should be updated to the expected "ConfirmCancellation" content. - // 2. The bottom sheet should be shown (Effect.ShowBottomSheet). - @Test - fun `Given Case 12, When setEvent is called, Then the expected result is returned`() = - coroutineRule.runTest { - // Arrange - val sheetContent = SelectQtspBottomSheetContent.ConfirmCancellation( - bottomSheetTextData = mockConfirmCancellationTextData() - ) - - // Act - viewModel.setEvent(Event.Pop) - - // Assert - assertEquals(sheetContent, viewModel.viewState.value.sheetContent) - viewModel.effect.runFlowTest { - val expectedEffect = Effect.ShowBottomSheet - assertEquals(expectedEffect, awaitItem()) - } - } - //endregion - - //region of helper functions - private fun mockLocalizedStrings(resourceProvider: ResourceProvider) { - LocalizableKey.entries.forEach { key -> - whenever(resourceProvider.getLocalizedString(key)).thenReturn(key.defaultTranslation()) - } - } - - private fun mockScreenRouteGeneration(documentData: DocumentData): String { - return generateComposableNavigationLink( - screen = SdkScreens.ViewDocument, - arguments = generateComposableArguments( - arguments = mapOf( - ViewDocumentUiConfig.serializedKeyName to uiSerializer.toBase64( - model = ViewDocumentUiConfig( - isSigned = true, - documentData = documentData - ), - parser = ViewDocumentUiConfig.Parser - ) - ) - ) - ) - } - - private fun mockConfirmCancellationTextData(): BottomSheetTextData { - return BottomSheetTextData( - title = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessTitle), - message = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSubtitle), - positiveButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessPrimaryText), - negativeButtonText = resourceProvider.getLocalizedString(LocalizableKey.CancelSignProcessSecondaryText), - ) - } - - private suspend fun mockGetServiceAuthorizationUrlCall(response: EudiRqesGetServiceAuthorizationUrlPartialState) { - whenever(selectQtspInteractor.getServiceAuthorizationUrl(rqesService)) - .thenReturn(response) - } - //endregion -} - */ \ No newline at end of file