From 42c66aefae97549fd8de1e38a11d1c09c6b9ba15 Mon Sep 17 00:00:00 2001 From: Christos Kaitatzis Date: Fri, 11 Oct 2024 13:00:29 +0300 Subject: [PATCH 01/14] Created TestDocumentOfferInteractor, providing tests for resolveDocumentOffer function Signed-off-by: Christos Kaitatzis --- .../document/TestDocumentOfferInteractor.kt | 402 ++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt new file mode 100644 index 000000000..519cef89f --- /dev/null +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -0,0 +1,402 @@ +/* + * 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.issuancefeature.interactor.document + +import eu.europa.ec.authenticationlogic.model.BiometricCrypto +import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor +import eu.europa.ec.commonfeature.ui.request.model.DocumentItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 +import eu.europa.ec.corelogic.controller.ResolveDocumentOfferPartialState +import eu.europa.ec.corelogic.controller.WalletCoreDocumentsController +import eu.europa.ec.corelogic.model.DocumentIdentifier +import eu.europa.ec.eudi.wallet.document.IssuedDocument +import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer +import eu.europa.ec.resourceslogic.R +import eu.europa.ec.resourceslogic.provider.ResourceProvider +import eu.europa.ec.testfeature.mockedExceptionWithNoMessage +import eu.europa.ec.testfeature.mockedGenericErrorMessage +import eu.europa.ec.testfeature.mockedMainPid +import eu.europa.ec.testfeature.mockedPlainFailureMessage +import eu.europa.ec.testlogic.extension.runFlowTest +import eu.europa.ec.testlogic.extension.runTest +import eu.europa.ec.testlogic.extension.toFlow +import eu.europa.ec.testlogic.rule.CoroutineTestRule +import eu.europa.ec.uilogic.serializer.UiSerializer +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever + +class TestDocumentOfferInteractor { + + @get:Rule + val coroutineRule = CoroutineTestRule() + + @Mock + private lateinit var walletCoreDocumentsController: WalletCoreDocumentsController + + @Mock + private lateinit var deviceAuthenticationInteractor: DeviceAuthenticationInteractor + + @Mock + private lateinit var resourceProvider: ResourceProvider + + @Mock + private lateinit var uiSerializer: UiSerializer + + private lateinit var interactor: DocumentOfferInteractor + + private lateinit var closeable: AutoCloseable + + private lateinit var biometricCrypto: BiometricCrypto + + @Before + fun before() { + closeable = MockitoAnnotations.openMocks(this) + + interactor = DocumentOfferInteractorImpl( + walletCoreDocumentsController = walletCoreDocumentsController, + deviceAuthenticationInteractor = deviceAuthenticationInteractor, + resourceProvider = resourceProvider, + uiSerializer = uiSerializer + ) + biometricCrypto = BiometricCrypto(cryptoObject = null) + + whenever(resourceProvider.genericErrorMessage()).thenReturn(mockedGenericErrorMessage) + } + + @After + fun after() { + closeable.close() + } + + //region resolveDocumentOffer + // + // Case 1: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns + // ResolveDocumentOfferPartialState.Success + + // Case 1 Expected Result: + // ResolveDocumentOfferInteractorPartialState.NoDocument state with: + // - the issuer name string + @Test + fun `Given Case 1, When resolveDocumentOffer is called, Then Case 1 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever(mockedOffer.issuerName) + .thenReturn(mockedIssuerName) + mockGetMainPidDocumentCall( + mainPid = mockedMainPid + ) + mockWalletDocumentsControllerEventEmission( + event = ResolveDocumentOfferPartialState.Success( + offer = mockedOffer + ) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedResult = + ResolveDocumentOfferInteractorPartialState.NoDocument( + issuerName = mockedIssuerName + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 2: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns + // ResolveDocumentOfferPartialState.Success with: + // an Offer item holding a document list of Offer.OfferedDocument + + // Case 2 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Success state, with: + // - DocumentUiItem list + // - issuer name (string) + // - and txCodeLength (int) + @Test + fun `Given Case 2, When resolveDocumentOffer is called, Then Case 2 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) + whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) + whenever(mockedOfferedDocument.name).thenReturn(mockedOfferDocumentName) + whenever(mockedOfferedDocument.docType).thenReturn(mockedDocType) + val mockedTxCodeSpecLength = 4 + val mockedOfferTxCodeSpec = Offer.TxCodeSpec( + length = mockedTxCodeSpecLength + ) + whenever(mockedOffer.issuerName) + .thenReturn(mockedIssuerName) + whenever(mockedOffer.txCodeSpec) + .thenReturn(mockedOfferTxCodeSpec) + mockGetMainPidDocumentCall( + mainPid = mockedMainPid + ) + mockWalletDocumentsControllerEventEmission( + event = ResolveDocumentOfferPartialState.Success(mockedOffer) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedList = mockedOfferedDocumentsList.map { + DocumentItemUi(title = mockedOfferDocumentName) + } + val expectedResult = + ResolveDocumentOfferInteractorPartialState.Success( + documents = expectedList, + issuerName = mockedIssuerName, + txCodeLength = mockedTxCodeSpecLength + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 3: + // 1. walletCoreDocumentsController.resolveDocumentOffer() throws: + // a RuntimeException without message + + // Case 3 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - a generic error message + @Test + fun `Given Case 3, When resolveDocumentOffer is called, Then Case 3 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)).thenThrow( + mockedExceptionWithNoMessage + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + // Then + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedGenericErrorMessage + ) + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 4: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns + // ResolveDocumentOfferPartialState.Success with: + // an Offer item holding a document list of Offer.OfferedDocument items + // txCodeSpec with length of 2 (numeric type), less than given limits of 4 to 6 digits length + + // Case 4 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - an invalid code format error message + @Test + fun `Given Case 4, When resolveDocumentOffer is called, Then Case 4 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) + whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) + whenever(mockedOfferedDocument.name).thenReturn(mockedOfferDocumentName) + whenever(mockedOfferedDocument.docType).thenReturn(mockedDocType) + + val mockedTxCodeSpecLength = 2 + val mockedOfferTxCodeSpec = Offer.TxCodeSpec( + inputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + length = mockedTxCodeSpecLength + ) + whenever(mockedOffer.txCodeSpec).thenReturn(mockedOfferTxCodeSpec) + + val codeMinLength = 4 + val codeMaxLength = 6 + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_error_invalid_txcode_format, + codeMinLength, + codeMaxLength + ) + ).thenReturn(mockedInvalidCodeFormatMessage) + + mockWalletDocumentsControllerEventEmission( + event = ResolveDocumentOfferPartialState.Success(mockedOffer) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedResult = + ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedInvalidCodeFormatMessage + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 5: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns + // ResolveDocumentOfferPartialState.Success with: + // an Offer item holding a document list of Offer.OfferedDocument items + // one of the OfferedDocument list items with docType of "load_sample_documents" + // (isSupported() returns false in this case) + // txCodeSpec with length of 4 (numeric type) + + // Case 5 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - an invalid wallet activation error message + @Test + fun `Given Case 5, When resolveDocumentOffer is called, Then Case 5 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) + whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) + whenever(mockedOfferedDocument.name).thenReturn(mockedOfferDocumentName) + whenever(mockedOfferedDocument.docType).thenReturn(mockedSampleDocumentType) + + val mockedTxCodeSpecLength = 4 + val mockedTxCodeSpec = Offer.TxCodeSpec( + length = mockedTxCodeSpecLength + ) + whenever(mockedOffer.issuerName).thenReturn(mockedIssuerName) + whenever(mockedOffer.txCodeSpec).thenReturn(mockedTxCodeSpec) + whenever(resourceProvider.getString(R.string.issuance_document_offer_error_missing_pid_text)) + .thenReturn(mockedWalletActivationErrorMessage) + mockWalletDocumentsControllerEventEmission( + event = ResolveDocumentOfferPartialState.Success(mockedOffer) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedResult = + ResolveDocumentOfferInteractorPartialState.Failure( + mockedWalletActivationErrorMessage + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 6: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns + // ResolveDocumentOfferPartialState.Success with: + // an Offer item holding a document list of Offer.OfferedDocument items + // one of the OfferedDocument list items with docType of PID + // txCodeSpec with length of 4 (numeric type) + + // Case 6 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Success state, with: + // - the expected DocumentItemUi list + // - issuer name string and + // - txCodeLength (int) + @Test + fun `Given Case 6, When resolveDocumentOffer is called, Then Case 6 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) + whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) + whenever(mockedOfferedDocument.name) + .thenReturn(mockedOfferDocumentName) + whenever(mockedOfferedDocument.docType) + .thenReturn(mockedPidDocType) + whenever(resourceProvider.getString(R.string.pid)) + .thenReturn(mockedPidLabel) + + val mockedTxCodeSpecLength = 4 + val mockedOfferTxCodeSpec = Offer.TxCodeSpec( + length = mockedTxCodeSpecLength + ) + whenever(mockedOffer.issuerName).thenReturn(mockedIssuerName) + whenever(mockedOffer.txCodeSpec).thenReturn(mockedOfferTxCodeSpec) + + mockGetMainPidDocumentCall( + mainPid = mockedMainPid + ) + mockWalletDocumentsControllerEventEmission( + event = ResolveDocumentOfferPartialState.Success(mockedOffer) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedDocumentsUiList = listOf( + DocumentItemUi(mockedPidLabel) + ) + val expectedResult = + ResolveDocumentOfferInteractorPartialState.Success( + documents = expectedDocumentsUiList, + issuerName = mockedIssuerName, + txCodeLength = mockedTxCodeSpecLength + ) + + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 7: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns + // ResolveDocumentOfferPartialState.Failure with: + // a plain failure message + + // Case 7 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - the same failure message + @Test + fun `Given Case 7, When resolveDocumentOffer is called, Then Case 7 Expected Result is returned`() = + coroutineRule.runTest { + // Given + mockWalletDocumentsControllerEventEmission( + event = ResolveDocumentOfferPartialState.Failure(mockedPlainFailureMessage) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) + //Then + assertEquals(expectedResult, awaitItem()) + } + } + + //endregion + + //region helper functions + private fun mockGetMainPidDocumentCall(mainPid: IssuedDocument?) { + whenever(walletCoreDocumentsController.getMainPidDocument()) + .thenReturn(mainPid) + } + + private fun mockWalletDocumentsControllerEventEmission(event: ResolveDocumentOfferPartialState) { + whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)) + .thenReturn(event.toFlow()) + } + //endregion + + //region mocked objects + private val mockedOffer = mock() + private val mockedOfferedDocument = mock() + private val mockedIssuerName = "mockedIssuerName" + private val mockedOfferDocumentName = "offerDocumentName" + private val mockedDocType = "mockedDocType" + private val mockedSampleDocumentType = DocumentIdentifier.SAMPLE.docType + private val mockedPidLabel = "mocked PID label" + private val mockedPidDocType = DocumentIdentifier.PID.docType + private val mockedInvalidCodeFormatMessage = "mocked invalid code format message" + private val mockedWalletActivationErrorMessage = "mocked wallet activation error message" + //endregion +} \ No newline at end of file From c7e120f156c6d0516f9a81f9d5ddbb49c8b39d5c Mon Sep 17 00:00:00 2001 From: Christos Kaitatzis Date: Fri, 11 Oct 2024 15:44:03 +0300 Subject: [PATCH 02/14] Additional tests defined in TestDocumentOfferInteractor covering cases of issueDocuments function. TestApplication modified so as to initialize the application theme in order allow accessing theme colors called within the functions being tested. Signed-off-by: Christos Kaitatzis --- .../kotlin/AndroidTestConventionPlugin.kt | 2 + .../document/TestDocumentOfferInteractor.kt | 566 +++++++++++++++++- .../ec/testlogic/base/TestApplication.kt | 26 +- 3 files changed, 584 insertions(+), 10 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt index 166e35d4f..30e7929d9 100644 --- a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt @@ -20,6 +20,7 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.kotlin +import project.convention.logic.config.LibraryModule import project.convention.logic.configureGradleManagedDevices import project.convention.logic.libs @@ -51,6 +52,7 @@ class AndroidTestConventionPlugin : Plugin { add("api", libs.findLibrary("mockito-kotlin").get()) add("api", libs.findLibrary("mockito-inline").get()) add("api", libs.findLibrary("robolectric").get()) + add("implementation", project(LibraryModule.ResourcesLogic.path)) } } } diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index 519cef89f..94dca651d 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -16,36 +16,54 @@ package eu.europa.ec.issuancefeature.interactor.document +import eu.europa.ec.authenticationlogic.controller.authentication.DeviceAuthenticationResult import eu.europa.ec.authenticationlogic.model.BiometricCrypto +import eu.europa.ec.commonfeature.config.SuccessUIConfig import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor import eu.europa.ec.commonfeature.ui.request.model.DocumentItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedPendingMdlUi +import eu.europa.ec.commonfeature.util.TestsData.mockedPendingPidUi import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 +import eu.europa.ec.corelogic.controller.IssueDocumentsPartialState import eu.europa.ec.corelogic.controller.ResolveDocumentOfferPartialState import eu.europa.ec.corelogic.controller.WalletCoreDocumentsController +import eu.europa.ec.corelogic.model.DocType import eu.europa.ec.corelogic.model.DocumentIdentifier +import eu.europa.ec.eudi.wallet.document.DocumentId import eu.europa.ec.eudi.wallet.document.IssuedDocument import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.provider.ResourceProvider +import eu.europa.ec.resourceslogic.theme.values.ThemeColors +import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedGenericErrorMessage import eu.europa.ec.testfeature.mockedMainPid import eu.europa.ec.testfeature.mockedPlainFailureMessage +import eu.europa.ec.testlogic.base.TestApplication import eu.europa.ec.testlogic.extension.runFlowTest import eu.europa.ec.testlogic.extension.runTest import eu.europa.ec.testlogic.extension.toFlow import eu.europa.ec.testlogic.rule.CoroutineTestRule +import eu.europa.ec.uilogic.component.AppIcons +import eu.europa.ec.uilogic.config.ConfigNavigation +import eu.europa.ec.uilogic.config.NavigationType import eu.europa.ec.uilogic.serializer.UiSerializer import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.whenever +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +@RunWith(RobolectricTestRunner::class) +@Config(application = TestApplication::class) class TestDocumentOfferInteractor { @get:Rule @@ -63,6 +81,9 @@ class TestDocumentOfferInteractor { @Mock private lateinit var uiSerializer: UiSerializer + @Mock + private lateinit var resultHandler: DeviceAuthenticationResult + private lateinit var interactor: DocumentOfferInteractor private lateinit var closeable: AutoCloseable @@ -107,7 +128,7 @@ class TestDocumentOfferInteractor { mockGetMainPidDocumentCall( mainPid = mockedMainPid ) - mockWalletDocumentsControllerEventEmission( + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success( offer = mockedOffer ) @@ -153,7 +174,7 @@ class TestDocumentOfferInteractor { mockGetMainPidDocumentCall( mainPid = mockedMainPid ) - mockWalletDocumentsControllerEventEmission( + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) @@ -233,7 +254,7 @@ class TestDocumentOfferInteractor { ) ).thenReturn(mockedInvalidCodeFormatMessage) - mockWalletDocumentsControllerEventEmission( + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) @@ -276,7 +297,7 @@ class TestDocumentOfferInteractor { whenever(mockedOffer.txCodeSpec).thenReturn(mockedTxCodeSpec) whenever(resourceProvider.getString(R.string.issuance_document_offer_error_missing_pid_text)) .thenReturn(mockedWalletActivationErrorMessage) - mockWalletDocumentsControllerEventEmission( + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) @@ -326,7 +347,7 @@ class TestDocumentOfferInteractor { mockGetMainPidDocumentCall( mainPid = mockedMainPid ) - mockWalletDocumentsControllerEventEmission( + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) @@ -359,7 +380,7 @@ class TestDocumentOfferInteractor { fun `Given Case 7, When resolveDocumentOffer is called, Then Case 7 Expected Result is returned`() = coroutineRule.runTest { // Given - mockWalletDocumentsControllerEventEmission( + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Failure(mockedPlainFailureMessage) ) @@ -375,16 +396,538 @@ class TestDocumentOfferInteractor { //endregion + //region issueDocuments + // Case 1: + @Test + fun `Given Case 1, When issueDocuments is called, Then Case 1 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever( + walletCoreDocumentsController.issueDocumentsByOfferUri( + offerUri = mockedUriPath1, + txCode = mockedTxCode + ) + ).thenThrow( + mockedExceptionWithMessage + ) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = + IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedExceptionWithMessage.localizedMessage!! + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 2, When issueDocuments is called, Then Case 2 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever( + walletCoreDocumentsController.issueDocumentsByOfferUri( + offerUri = mockedUriPath1, + txCode = mockedTxCode + ) + ).thenThrow(mockedExceptionWithNoMessage) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedGenericErrorMessage + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 3, When issueDocuments is called, Then Case 3 Expected Result is returned`() = + coroutineRule.runTest { + // Given + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.Failure(errorMessage = mockedPlainFailureMessage) + ) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + + val expectedResult = IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 4, When issueDocuments is called, Then Case 4 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever(resourceProvider.getString(R.string.issuance_generic_error)).thenReturn( + mockedErrorMessage + ) + + val mockedArgument = "mockedArgument" + val mockedSuccessSubtitle = "mocked success subtitle" + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_success_subtitle, + mockedArgument + ) + ).thenReturn(mockedSuccessSubtitle) + + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.UserAuthRequired( + crypto = biometricCrypto, + resultHandler = resultHandler + ) + ) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = IssueDocumentsInteractorPartialState.UserAuthRequired( + crypto = biometricCrypto, + resultHandler = resultHandler + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 5, When issueDocuments is called, Then Case 5 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedDocumentId = "mockedDocumentId" + val mockedSuccessTitle = "mocked success title" + val mockedSuccessSubtitle = "mocked success subtitle" + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_success_subtitle, + mockedIssuerName + ) + ).thenReturn(mockedSuccessSubtitle) + + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.Success( + documentIds = listOf(mockedDocumentId) + ) + ) + + whenever( + resourceProvider.getString(R.string.issuance_document_offer_success_title) + ).thenReturn(mockedSuccessTitle) + + whenever( + resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) + ).thenReturn(mockedButtonText) + + val mockedTripleObject = Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_success_title), + color = ThemeColors.success + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DEFAULT, + drawableRes = null, + tint = ThemeColors.success, + contentDescription = resourceProvider.getString(R.string.content_description_success) + ), + third = resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) + ) + + val mockedSuccessUiConfig = SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = mockedSuccessSubtitle, + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigation + ) + ), + onBackScreenToNavigate = mockedConfigNavigation, + ) + + whenever( + uiSerializer.toBase64( + model = mockedSuccessUiConfig, + parser = SuccessUIConfig.Parser + ) + ).thenReturn(mockedArguments) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = IssueDocumentsInteractorPartialState.Success( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) + + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 6, When issueDocuments is called, Then Case 6 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedSuccessTitle = "mocked success title" + val mockedSuccessSubtitle = "mocked success subtitle" + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_deferred_success_subtitle, + mockedIssuerName + ) + ).thenReturn(mockedSuccessSubtitle) + + val mockDeferredPendingDocId1 = mockedPendingPidUi.documentId + val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType + + val mockDeferredPendingDocId2 = mockedPendingMdlUi.documentId + val mockDeferredPendingType2 = mockedPendingMdlUi.documentIdentifier.docType + + val deferredDocuments: Map = mapOf( + mockDeferredPendingDocId1 to mockDeferredPendingType1, + mockDeferredPendingDocId2 to mockDeferredPendingType2 + ) + + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.DeferredSuccess( + deferredDocuments = deferredDocuments + ) + ) + + whenever( + resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title) + ).thenReturn(mockedSuccessTitle) + + whenever( + resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + ).thenReturn(mockedButtonText) + + val mockedTripleObject = Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title), + color = ThemeColors.warning + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, + drawableRes = AppIcons.ClockTimer.resourceId, + tint = ThemeColors.warning, + contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) + ), + third = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + ) + + val config = SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = mockedSuccessSubtitle, + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigation + ) + ), + onBackScreenToNavigate = mockedConfigNavigation + ) + + whenever( + uiSerializer.toBase64( + model = config, + parser = SuccessUIConfig.Parser + ) + ).thenReturn(mockedArguments) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = + IssueDocumentsInteractorPartialState.DeferredSuccess( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) + + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 7, When issueDocuments is called, Then Case 7 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedDocumentId = "mockedDocumentId" + val mockedSuccessTitle = "mocked success subtitle" + + val mockDeferredPendingDocId1 = mockedPendingPidUi.documentId + val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType + + val mockDeferredPendingDocId2 = mockedPendingMdlUi.documentId + val mockDeferredPendingType2 = mockedPendingMdlUi.documentIdentifier.docType + + val nonIssuedDeferredDocuments: Map = mapOf( + mockDeferredPendingDocId1 to mockDeferredPendingType1, + mockDeferredPendingDocId2 to mockDeferredPendingType2 + ) + + val nonIssuedDocsNames = + "${mockedPendingPidUi.documentIdentifier.docType}, ${mockedPendingMdlUi.documentIdentifier.docType}" + + val mockedSuccessSubtitle = "mocked success subtitle" + val mockedContentDescription = "mocked content description" + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_partial_success_subtitle, + mockedIssuerName, + nonIssuedDocsNames + ) + ).thenReturn(mockedSuccessSubtitle) + + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.PartialSuccess( + documentIds = listOf(mockedDocumentId), + nonIssuedDocuments = nonIssuedDeferredDocuments + ) + ) + + whenever( + resourceProvider.getString(R.string.issuance_document_offer_success_title) + ).thenReturn(mockedSuccessTitle) + + whenever( + resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) + ).thenReturn(mockedButtonText) + + whenever(resourceProvider.getString(R.string.content_description_success)).thenReturn( + mockedContentDescription + ) + + val mockedTripleObject = Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_success_title), + color = ThemeColors.success + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DEFAULT, + drawableRes = null, + tint = ThemeColors.success, + contentDescription = resourceProvider.getString(R.string.content_description_success) + ), + third = resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) + ) + + val config = SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = mockedSuccessSubtitle, + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigation + ) + ), + onBackScreenToNavigate = mockedConfigNavigation + ) + + whenever( + uiSerializer.toBase64( + model = config, + parser = SuccessUIConfig.Parser + ) + ).thenReturn(mockedArguments) + + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = + IssueDocumentsInteractorPartialState.Success( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) + + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + @Test + fun `Given Case 8, When issueDocuments is called, Then Case 8 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedDocumentId = "mockedDocumentId" + val mockedSubtitle = "mocked subtitle" + val mockedDeferredPendingDocId1 = mockedPidDocType + val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType + val nonIssuedDeferredDocuments: Map = mapOf( + mockedDeferredPendingDocId1 to mockDeferredPendingType1 + ) + whenever(resourceProvider.getString(R.string.pid)).thenReturn(mockedPidLabel) + val nonIssuedDocsNames = mockedPidLabel + + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_partial_success_subtitle, + mockedIssuerName, + nonIssuedDocsNames + ) + ).thenReturn(mockedSubtitle) + + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.PartialSuccess( + documentIds = listOf(mockedDocumentId), + nonIssuedDocuments = nonIssuedDeferredDocuments + ) + ) + + val mockedOfferSuccessTitle = "mocked offer success title" + val mockedContentDescription = "mocked content description" + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) + .thenReturn(mockedOfferSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)).thenReturn( + mockedButtonText + ) + whenever(resourceProvider.getString(R.string.content_description_success)).thenReturn( + mockedContentDescription + ) + + val mockedTripleObject = Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_success_title), + color = ThemeColors.success + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DEFAULT, + drawableRes = null, + tint = ThemeColors.success, + contentDescription = resourceProvider.getString(R.string.content_description_success) + ), + third = resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) + ) + + val config = SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = mockedSubtitle, + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigation + ) + ), + onBackScreenToNavigate = mockedConfigNavigation + ) + + whenever( + uiSerializer.toBase64( + model = config, + parser = SuccessUIConfig.Parser + ) + ).thenReturn(mockedArguments) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = + IssueDocumentsInteractorPartialState.Success( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) + + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + + @Test + fun `Given Case 9, When issueDocuments is called, Then Case 9 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val failureResponse = + IssueDocumentsPartialState.Failure(errorMessage = mockedPlainFailureMessage) + whenever( + walletCoreDocumentsController.issueDocumentsByOfferUri( + offerUri = mockedUriPath1, + txCode = null + ) + ).thenReturn(failureResponse.toFlow()) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigation + ).runFlowTest { + val expectedResult = + IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + + //endregion + //region helper functions private fun mockGetMainPidDocumentCall(mainPid: IssuedDocument?) { whenever(walletCoreDocumentsController.getMainPidDocument()) .thenReturn(mainPid) } - private fun mockWalletDocumentsControllerEventEmission(event: ResolveDocumentOfferPartialState) { + private fun mockWalletDocumentsControllerResolveOfferEventEmission(event: ResolveDocumentOfferPartialState) { whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)) .thenReturn(event.toFlow()) } + + private fun mockWalletDocumentsControllerIssueByUriEventEmission(event: IssueDocumentsPartialState) { + whenever( + walletCoreDocumentsController.issueDocumentsByOfferUri( + offerUri = mockedUriPath1, + txCode = mockedTxCode + ) + ).thenReturn(event.toFlow()) + } //endregion //region mocked objects @@ -393,10 +936,15 @@ class TestDocumentOfferInteractor { private val mockedIssuerName = "mockedIssuerName" private val mockedOfferDocumentName = "offerDocumentName" private val mockedDocType = "mockedDocType" - private val mockedSampleDocumentType = DocumentIdentifier.SAMPLE.docType private val mockedPidLabel = "mocked PID label" - private val mockedPidDocType = DocumentIdentifier.PID.docType private val mockedInvalidCodeFormatMessage = "mocked invalid code format message" private val mockedWalletActivationErrorMessage = "mocked wallet activation error message" + private val mockedArguments = "mockedArguments" + private val mockedTxCode = "mockedTxCode" + private val mockedButtonText = "mocked button text" + private val mockedErrorMessage = "mocked error message" + private val mockedPidDocType = DocumentIdentifier.PID.docType + private val mockedSampleDocumentType = DocumentIdentifier.SAMPLE.docType + private val mockedConfigNavigation = ConfigNavigation(navigationType = NavigationType.Pop) //endregion } \ No newline at end of file diff --git a/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt b/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt index 57034e28d..fbdf800e4 100644 --- a/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt +++ b/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt @@ -17,5 +17,29 @@ package eu.europa.ec.testlogic.base import android.app.Application +import eu.europa.ec.resourceslogic.theme.ThemeManager +import eu.europa.ec.resourceslogic.theme.templates.ThemeDimensTemplate +import eu.europa.ec.resourceslogic.theme.values.ThemeColors +import eu.europa.ec.resourceslogic.theme.values.ThemeShapes +import eu.europa.ec.resourceslogic.theme.values.ThemeTypography -class TestApplication : Application() \ No newline at end of file +class TestApplication : Application() { + override fun onCreate() { + super.onCreate() + initializeTheme() + } + + private fun initializeTheme() { + ThemeManager.Builder() + .withLightColors(ThemeColors.lightColors) + .withDarkColors(ThemeColors.darkColors) + .withTypography(ThemeTypography.typo) + .withShapes(ThemeShapes.shapes) + .withDimensions( + ThemeDimensTemplate( + screenPadding = 10.0 + ) + ) + .build() + } +} \ No newline at end of file From 2147208dae0076cf4c19b2ff8aaa6aacc8c0227c Mon Sep 17 00:00:00 2001 From: Christos Kaitatzis Date: Mon, 14 Oct 2024 11:15:57 +0300 Subject: [PATCH 03/14] Extra tests in TestAddDocumentInteractor covering call of buildGenericSuccessRouteForDeferred, provided comments on test cases --- .../document/TestAddDocumentInteractor.kt | 178 +++++++++++++++ .../document/TestDocumentOfferInteractor.kt | 210 +++++++++++++++++- 2 files changed, 379 insertions(+), 9 deletions(-) diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt index ceb7b74fe..60f8f3f01 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt @@ -21,6 +21,7 @@ import eu.europa.ec.authenticationlogic.controller.authentication.BiometricsAvai import eu.europa.ec.authenticationlogic.controller.authentication.DeviceAuthenticationResult import eu.europa.ec.authenticationlogic.model.BiometricCrypto import eu.europa.ec.commonfeature.config.IssuanceFlowUiConfig +import eu.europa.ec.commonfeature.config.SuccessUIConfig import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor import eu.europa.ec.commonfeature.util.TestsData.mockedAgeOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedMdlOptionItemUi @@ -28,27 +29,36 @@ import eu.europa.ec.commonfeature.util.TestsData.mockedPhotoIdOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedPidId import eu.europa.ec.commonfeature.util.TestsData.mockedPidOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedSampleDataOptionItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 import eu.europa.ec.corelogic.controller.AddSampleDataPartialState import eu.europa.ec.corelogic.controller.IssuanceMethod import eu.europa.ec.corelogic.controller.IssueDocumentPartialState import eu.europa.ec.corelogic.controller.WalletCoreDocumentsController import eu.europa.ec.corelogic.model.DocumentIdentifier +import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.provider.ResourceProvider +import eu.europa.ec.resourceslogic.theme.values.ThemeColors import eu.europa.ec.testfeature.MockResourceProviderForStringCalls.mockDocumentTypeUiToUiNameCall import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedGenericErrorMessage import eu.europa.ec.testfeature.mockedPlainFailureMessage +import eu.europa.ec.testlogic.base.TestApplication import eu.europa.ec.testlogic.extension.runFlowTest import eu.europa.ec.testlogic.extension.runTest import eu.europa.ec.testlogic.extension.toFlow import eu.europa.ec.testlogic.rule.CoroutineTestRule +import eu.europa.ec.uilogic.component.AppIcons +import eu.europa.ec.uilogic.config.ConfigNavigation +import eu.europa.ec.uilogic.config.NavigationType +import eu.europa.ec.uilogic.navigation.DashboardScreens import eu.europa.ec.uilogic.serializer.UiSerializer import junit.framework.TestCase.assertEquals import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -56,7 +66,11 @@ import org.mockito.kotlin.any import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +@RunWith(RobolectricTestRunner::class) +@Config(application = TestApplication::class) class TestAddDocumentInteractor { @get:Rule @@ -371,6 +385,144 @@ class TestAddDocumentInteractor { } //endregion + //region buildGenericSuccessRouteForDeferred + // + // Case 1: + // 1. ConfigNavigation with NavigationType.PushRoute + // 2. string resources mocked + // + // when buildGenericSuccessRouteForDeferred is called on the interactor + // the expected string result is generated for route definition + @Test + fun `When buildGenericSuccessRouteForDeferred is called, then the expected string result is returned`() { + // Given + mockDocumentIssuanceStrings() + + val mockedTripleObject = Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title), + color = ThemeColors.warning + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, + drawableRes = AppIcons.ClockTimer.resourceId, + tint = ThemeColors.warning, + contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) + ), + third = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + ) + + val mockedConfigNavigation = + ConfigNavigation( + navigationType = NavigationType.PushRoute( + route = DashboardScreens.Dashboard.screenRoute + ) + ) + val config = SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle), + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigation + ) + ), + onBackScreenToNavigate = mockedConfigNavigation + ) + + val mockedArguments = "mockedArguments" + whenever( + uiSerializer.toBase64( + model = config, + parser = SuccessUIConfig.Parser + ) + ).thenReturn(mockedArguments) + + val flowType = IssuanceFlowUiConfig.NO_DOCUMENT + val result = interactor.buildGenericSuccessRouteForDeferred(flowType = flowType) + + val expectedResult = "SUCCESS?successConfig=$mockedArguments" + assertEquals(expectedResult, result) + } + + // + // Case 2: + // 1. ConfigNavigation with NavigationType.PopRoute + // 2. string resources mocked + + // when buildGenericSuccessRouteForDeferred is called on the interactor + // the expected string result is generated for route definition + @Test + fun `When buildGenericSuccessRouteForDeferred (PopRoute) is called, then the expected string result is returned`() { + // Given + mockDocumentIssuanceStrings() + + val mockedTripleObject = Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title), + color = ThemeColors.warning + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, + drawableRes = AppIcons.ClockTimer.resourceId, + tint = ThemeColors.warning, + contentDescription = resourceProvider.getString( + AppIcons.ClockTimer.contentDescriptionId + ) + ), + third = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + ) + + val mockedConfigNavigation = + ConfigNavigation(navigationType = NavigationType.PopTo(screen = DashboardScreens.Dashboard)) + + val config = SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle), + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigation + ) + ), + onBackScreenToNavigate = mockedConfigNavigation + ) + + val mockedArguments = "mockedArguments" + whenever( + uiSerializer.toBase64( + model = config, + parser = SuccessUIConfig.Parser + ) + ).thenReturn(mockedArguments) + + val flowType = IssuanceFlowUiConfig.EXTRA_DOCUMENT + val result = interactor.buildGenericSuccessRouteForDeferred(flowType = flowType) + + val expectedResult = "SUCCESS?successConfig=$mockedArguments" + assertEquals(expectedResult, result) + } + //endregion + + //region resumeOpenId4VciWithAuthorization + // + // Case of resumeOpenId4VciWithAuthorization being called on the interactor + // the expected result is the resumeOpenId4VciWithAuthorization function to be executed on + // the walletCoreDocumentsController + @Test + fun `When interactor resumeOpenId4VciWithAuthorization is called, Then resumeOpenId4VciWithAuthorization should be invoked on the controller`() { + // When + interactor.resumeOpenId4VciWithAuthorization(mockedUriPath1) + + verify(walletCoreDocumentsController, times(1)) + .resumeOpenId4VciWithAuthorization(mockedUriPath1) + } + //endregion + //region helper functions private fun mockBiometricsAvailabilityResponse(response: BiometricsAvailability) { whenever(deviceAuthenticationInteractor.getBiometricsAvailability(listener = any())) @@ -379,5 +531,31 @@ class TestAddDocumentInteractor { bioAvailability(response) } } + + private fun mockDocumentIssuanceStrings() { + val mockedSuccessTitle = "mocked success title" + val mockedSuccessSubtitle = "mocked success subtitle" + val mockedButtonText = "mocked button text" + val mockedContentDescriptionId = "mocked content description id" + + whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_title)).thenReturn( + mockedSuccessTitle + ) + whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text)).thenReturn( + mockedButtonText + ) + whenever(resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId)).thenReturn( + mockedContentDescriptionId + ) + whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle)).thenReturn( + mockedSuccessSubtitle + ) + whenever( + resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title) + ).thenReturn(mockedSuccessTitle) + whenever( + resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + ).thenReturn(mockedButtonText) + } //endregion } \ No newline at end of file diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index 94dca651d..35e67bd72 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -16,6 +16,8 @@ package eu.europa.ec.issuancefeature.interactor.document +import android.content.Context +import eu.europa.ec.authenticationlogic.controller.authentication.BiometricsAvailability import eu.europa.ec.authenticationlogic.controller.authentication.DeviceAuthenticationResult import eu.europa.ec.authenticationlogic.model.BiometricCrypto import eu.europa.ec.commonfeature.config.SuccessUIConfig @@ -49,8 +51,8 @@ import eu.europa.ec.uilogic.component.AppIcons import eu.europa.ec.uilogic.config.ConfigNavigation import eu.europa.ec.uilogic.config.NavigationType import eu.europa.ec.uilogic.serializer.UiSerializer +import junit.framework.TestCase.assertEquals import org.junit.After -import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -58,6 +60,9 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -84,6 +89,9 @@ class TestDocumentOfferInteractor { @Mock private lateinit var resultHandler: DeviceAuthenticationResult + @Mock + private lateinit var context: Context + private lateinit var interactor: DocumentOfferInteractor private lateinit var closeable: AutoCloseable @@ -398,6 +406,13 @@ class TestDocumentOfferInteractor { //region issueDocuments // Case 1: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri() is called with: + // parameters being mocked + // 2. A mockedExceptionWithMessage (RuntimeException) is thrown with message + + // Case 1 Expected Result: + // IssueDocumentsInteractorPartialState.Failure state, with: + // - errorMessage equal to the localized message of mockedExceptionWithMessage @Test fun `Given Case 1, When issueDocuments is called, Then Case 1 Expected Result is returned`() = coroutineRule.runTest { @@ -427,6 +442,14 @@ class TestDocumentOfferInteractor { } } + // Case 2: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri() is called with: + // parameters being mocked + // 2. A mockedExceptionWithNoMessage (RuntimeException) is thrown + + // Case 2 Expected Result: + // IssueDocumentsInteractorPartialState.Failure state, with: + // - a mockedGenericErrorMessage @Test fun `Given Case 2, When issueDocuments is called, Then Case 2 Expected Result is returned`() = coroutineRule.runTest { @@ -453,6 +476,14 @@ class TestDocumentOfferInteractor { } } + // Case 3: + // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // IssueDocumentsPartialState.Failure with: + // mockedPlainFailureMessage as the error message + + // Case 3 Expected Result: + // IssueDocumentsInteractorPartialState.Failure state, with: + // - errorMessage equal to mockedPlainFailureMessage @Test fun `Given Case 3, When issueDocuments is called, Then Case 3 Expected Result is returned`() = coroutineRule.runTest { @@ -476,16 +507,24 @@ class TestDocumentOfferInteractor { } } + // Case 4: + // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // IssueDocumentsPartialState.UserAuthRequired with: + // biometricCrypto object and resultHandler as DeviceAuthenticationResult + // 2. required arguments are mocked + + // Case 4 Expected Result: + // IssueDocumentsInteractorPartialState.UserAuthRequired state, with parameters of: + // - biometricCrypto object and resultHandler as DeviceAuthenticationResult @Test fun `Given Case 4, When issueDocuments is called, Then Case 4 Expected Result is returned`() = coroutineRule.runTest { // Given + val mockedArgument = "mockedArgument" + val mockedSuccessSubtitle = "mocked success subtitle" whenever(resourceProvider.getString(R.string.issuance_generic_error)).thenReturn( mockedErrorMessage ) - - val mockedArgument = "mockedArgument" - val mockedSuccessSubtitle = "mocked success subtitle" whenever( resourceProvider.getString( R.string.issuance_document_offer_success_subtitle, @@ -516,6 +555,16 @@ class TestDocumentOfferInteractor { } } + // Case 5: + // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // IssueDocumentsPartialState.Success with: + // biometricCrypto object and resultHandler as DeviceAuthenticationResult + // 2. required strings are mocked + // 3. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedArguments + + // Case 5 Expected Result: + // IssueDocumentsInteractorPartialState.Success state, with: + // - successRoute equal to "SUCCESS?successConfig=mockedArguments" @Test fun `Given Case 5, When issueDocuments is called, Then Case 5 Expected Result is returned`() = coroutineRule.runTest { @@ -539,7 +588,6 @@ class TestDocumentOfferInteractor { whenever( resourceProvider.getString(R.string.issuance_document_offer_success_title) ).thenReturn(mockedSuccessTitle) - whenever( resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) ).thenReturn(mockedButtonText) @@ -595,6 +643,16 @@ class TestDocumentOfferInteractor { } } + // Case 6: + // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // IssueDocumentsPartialState.DeferredSuccess with: + // mocked deferred documents + // 2. required strings are mocked + // 3. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedArguments + + // Case 6 Expected Result: + // IssueDocumentsInteractorPartialState.DeferredSuccess state, with: + // - successRoute equal to "SUCCESS?successConfig=mockedArguments" @Test fun `Given Case 6, When issueDocuments is called, Then Case 6 Expected Result is returned`() = coroutineRule.runTest { @@ -628,7 +686,6 @@ class TestDocumentOfferInteractor { whenever( resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title) ).thenReturn(mockedSuccessTitle) - whenever( resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) ).thenReturn(mockedButtonText) @@ -685,6 +742,20 @@ class TestDocumentOfferInteractor { } } + // Case 7: + // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // IssueDocumentsPartialState.PartialSuccess with: + // - documentIds containing mockedDocumentId. + // - nonIssuedDocuments map containing mockDeferredPendingDocId1 to mockDeferredPendingType1 + // and mockDeferredPendingDocId2 to mockDeferredPendingType2. + // 2. nonIssuedDocsNames is formed by combining the document types of non-issued documents: + // "eu.europa.ec.eudi.pid.1, org.iso.18013.5.1.mDL" + // 3. mocked string resources + // 7. uiSerializer.toBase64() serializes the SuccessUIConfig object into mockedArguments. + + // Case 7 Expected Result: + // IssueDocumentsInteractorPartialState.Success state, with: + // - successRoute equal to "SUCCESS?successConfig=mockedArguments" @Test fun `Given Case 7, When issueDocuments is called, Then Case 7 Expected Result is returned`() = coroutineRule.runTest { @@ -726,11 +797,9 @@ class TestDocumentOfferInteractor { whenever( resourceProvider.getString(R.string.issuance_document_offer_success_title) ).thenReturn(mockedSuccessTitle) - whenever( resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) ).thenReturn(mockedButtonText) - whenever(resourceProvider.getString(R.string.content_description_success)).thenReturn( mockedContentDescription ) @@ -786,6 +855,13 @@ class TestDocumentOfferInteractor { } } + // Case 8: + // 1. IssueDocumentsPartialState.PartialSuccess is returned by issueDocumentsByOfferUri + // 2. The interactor is called with the given offerUri, issuerName, navigation and txCode. + + // Case 8 Expected Result: + // IssueDocumentsInteractorPartialState.Success state, with: + // - successRoute equal to "SUCCESS?successConfig=mockedArguments". @Test fun `Given Case 8, When issueDocuments is called, Then Case 8 Expected Result is returned`() = coroutineRule.runTest { @@ -799,7 +875,6 @@ class TestDocumentOfferInteractor { ) whenever(resourceProvider.getString(R.string.pid)).thenReturn(mockedPidLabel) val nonIssuedDocsNames = mockedPidLabel - whenever( resourceProvider.getString( R.string.issuance_document_offer_partial_success_subtitle, @@ -878,7 +953,13 @@ class TestDocumentOfferInteractor { } } + // Case 9: + // 1. IssueDocumentsPartialState.Failure is returned by issueDocumentsByOfferUri + // 2. The interactor is called with a mocked offerUri and null txCode + // Case 9 Expected Result: + // IssueDocumentsInteractorPartialState.Failure state, with: + // - errorMessage equal to mockedPlainFailureMessage. @Test fun `Given Case 9, When issueDocuments is called, Then Case 9 Expected Result is returned`() = coroutineRule.runTest { @@ -909,6 +990,109 @@ class TestDocumentOfferInteractor { //endregion + //region handleUserAuthentication + // + // Case 1: + // 1. deviceAuthenticationInteractor.getBiometricsAvailability returns: + // BiometricsAvailability.CanAuthenticate + + // Case 1 Expected Result: + // deviceAuthenticationInteractor.authenticateWithBiometrics called once. + @Test + fun `Given case 1, When handleUserAuthentication is called, Then Case 1 expected result is returned`() { + // Given + mockBiometricsAvailabilityResponse( + response = BiometricsAvailability.CanAuthenticate + ) + + // When + interactor.handleUserAuthentication( + context = context, + crypto = biometricCrypto, + resultHandler = resultHandler + ) + + // Then + verify(deviceAuthenticationInteractor, times(1)) + .authenticateWithBiometrics( + context = context, + crypto = biometricCrypto, + resultHandler = resultHandler + ) + } + + // Case 2: + // 1. deviceAuthenticationInteractor.getBiometricsAvailability returns: + // BiometricsAvailability.NonEnrolled + + // Case 2 Expected Result: + // deviceAuthenticationInteractor.authenticateWithBiometrics called once. + @Test + fun `Given case 2, When handleUserAuthentication is called, Then Case 2 expected result is returned`() { + // Given + mockBiometricsAvailabilityResponse( + response = BiometricsAvailability.NonEnrolled + ) + + // When + interactor.handleUserAuthentication( + context = context, + crypto = biometricCrypto, + resultHandler = resultHandler + ) + + // Then + verify(deviceAuthenticationInteractor, times(1)) + .authenticateWithBiometrics( + context = context, + crypto = biometricCrypto, + resultHandler = resultHandler + ) + } + + // Case 3: + // 1. deviceAuthenticationInteractor.getBiometricsAvailability returns: + // BiometricsAvailability.Failure + + // Case 3 Expected Result: + // resultHandler.onAuthenticationFailure called once. + @Test + fun `Given case 3, When handleUserAuthentication is called, Then Case 3 expected result is returned`() { + // Given + val mockedOnAuthenticationFailure: () -> Unit = {} + whenever(resultHandler.onAuthenticationFailure) + .thenReturn(mockedOnAuthenticationFailure) + + mockBiometricsAvailabilityResponse( + response = BiometricsAvailability.Failure( + errorMessage = mockedPlainFailureMessage + ) + ) + + // When + interactor.handleUserAuthentication( + context = context, + crypto = biometricCrypto, + resultHandler = resultHandler + ) + + // Then + verify(resultHandler, times(1)) + .onAuthenticationFailure + } + + //endregion + + //region resumeOpenId4VciWithAuthorization + @Test + fun `when interactor resumeOpenId4VciWithAuthorization is called, then resumeOpenId4VciWithAuthorization should be invoked on the controller`() { + interactor.resumeOpenId4VciWithAuthorization(mockedUriPath1) + + verify(walletCoreDocumentsController, times(1)) + .resumeOpenId4VciWithAuthorization(mockedUriPath1) + } + //endregion + //region helper functions private fun mockGetMainPidDocumentCall(mainPid: IssuedDocument?) { whenever(walletCoreDocumentsController.getMainPidDocument()) @@ -928,6 +1112,14 @@ class TestDocumentOfferInteractor { ) ).thenReturn(event.toFlow()) } + + private fun mockBiometricsAvailabilityResponse(response: BiometricsAvailability) { + whenever(deviceAuthenticationInteractor.getBiometricsAvailability(listener = any())) + .thenAnswer { + val bioAvailability = it.getArgument<(BiometricsAvailability) -> Unit>(0) + bioAvailability(response) + } + } //endregion //region mocked objects From f29bb491a84efad594583f0c42561f2ee94cdc8b Mon Sep 17 00:00:00 2001 From: Christos Kaitatzis Date: Mon, 14 Oct 2024 12:30:42 +0300 Subject: [PATCH 04/14] Correction in comments of a test case --- .../interactor/document/TestDocumentOfferInteractor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index 35e67bd72..d0ac9e5ac 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -955,7 +955,7 @@ class TestDocumentOfferInteractor { // Case 9: // 1. IssueDocumentsPartialState.Failure is returned by issueDocumentsByOfferUri - // 2. The interactor is called with a mocked offerUri and null txCode + // 2. The controller issueDocumentsByOfferUri is called with a mocked offerUri and null txCode // Case 9 Expected Result: // IssueDocumentsInteractorPartialState.Failure state, with: From 07490ef915c5cb469186e4e20b1b6e70e9ea7510 Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Mon, 14 Oct 2024 14:14:01 +0300 Subject: [PATCH 05/14] Refactor: Make issuanceJob private in AddDocumentViewModel. --- .../ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt index 8d1f145a9..8d417340d 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt @@ -106,7 +106,7 @@ class AddDocumentViewModel( @InjectedParam private val flowType: IssuanceFlowUiConfig, ) : MviViewModel() { - var issuanceJob: Job? = null + private var issuanceJob: Job? = null override fun setInitialState(): State = State( navigatableAction = getNavigatableAction(flowType), From 4d2ff7943c28165eed960c70f2e78ec33b857b87 Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Mon, 14 Oct 2024 15:28:37 +0300 Subject: [PATCH 06/14] Refactor: Small corrections --- .../europa/ec/commonfeature/util/TestsData.kt | 1 + .../document/TestAddDocumentInteractor.kt | 38 ++--- .../document/TestDocumentOfferInteractor.kt | 155 ++++++++---------- 3 files changed, 81 insertions(+), 113 deletions(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt index d2d016cb2..6c307b9d5 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt @@ -78,6 +78,7 @@ object TestsData { const val mockedDocumentHasExpired = false const val mockedUserAuthentication = false const val mockedVerifierName = "EUDIW Verifier" + const val mockedIssuerName = "EUDIW Issuer" const val mockedRequestRequiredFieldsTitle = "Verification Data" const val mockedRequestElementIdentifierNotAvailable = "Not available" diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt index 60f8f3f01..1a09cf939 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt @@ -400,7 +400,7 @@ class TestAddDocumentInteractor { val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title), + title = resourceProvider.getString(R.string.issuance_add_document_deferred_success_title), color = ThemeColors.warning ), second = SuccessUIConfig.ImageConfig( @@ -409,7 +409,7 @@ class TestAddDocumentInteractor { tint = ThemeColors.warning, contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) ), - third = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + third = resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text) ) val mockedConfigNavigation = @@ -461,18 +461,16 @@ class TestAddDocumentInteractor { val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title), + title = resourceProvider.getString(R.string.issuance_add_document_deferred_success_title), color = ThemeColors.warning ), second = SuccessUIConfig.ImageConfig( type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, drawableRes = AppIcons.ClockTimer.resourceId, tint = ThemeColors.warning, - contentDescription = resourceProvider.getString( - AppIcons.ClockTimer.contentDescriptionId - ) + contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) ), - third = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) + third = resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text) ) val mockedConfigNavigation = @@ -538,24 +536,14 @@ class TestAddDocumentInteractor { val mockedButtonText = "mocked button text" val mockedContentDescriptionId = "mocked content description id" - whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_title)).thenReturn( - mockedSuccessTitle - ) - whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text)).thenReturn( - mockedButtonText - ) - whenever(resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId)).thenReturn( - mockedContentDescriptionId - ) - whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle)).thenReturn( - mockedSuccessSubtitle - ) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title) - ).thenReturn(mockedSuccessTitle) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) - ).thenReturn(mockedButtonText) + whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_title)) + .thenReturn(mockedSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text)) + .thenReturn(mockedButtonText) + whenever(resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId)) + .thenReturn(mockedContentDescriptionId) + whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle)) + .thenReturn(mockedSuccessSubtitle) } //endregion } \ No newline at end of file diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index d0ac9e5ac..7a37f30a8 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -23,6 +23,8 @@ import eu.europa.ec.authenticationlogic.model.BiometricCrypto import eu.europa.ec.commonfeature.config.SuccessUIConfig import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor import eu.europa.ec.commonfeature.ui.request.model.DocumentItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedDocUiNamePid +import eu.europa.ec.commonfeature.util.TestsData.mockedIssuerName import eu.europa.ec.commonfeature.util.TestsData.mockedPendingMdlUi import eu.europa.ec.commonfeature.util.TestsData.mockedPendingPidUi import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 @@ -41,6 +43,7 @@ import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedGenericErrorMessage import eu.europa.ec.testfeature.mockedMainPid +import eu.europa.ec.testfeature.mockedPidDocType import eu.europa.ec.testfeature.mockedPlainFailureMessage import eu.europa.ec.testlogic.base.TestApplication import eu.europa.ec.testlogic.extension.runFlowTest @@ -144,10 +147,9 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - val expectedResult = - ResolveDocumentOfferInteractorPartialState.NoDocument( - issuerName = mockedIssuerName - ) + val expectedResult = ResolveDocumentOfferInteractorPartialState.NoDocument( + issuerName = mockedIssuerName + ) // Then assertEquals(expectedResult, awaitItem()) } @@ -191,12 +193,11 @@ class TestDocumentOfferInteractor { val expectedList = mockedOfferedDocumentsList.map { DocumentItemUi(title = mockedOfferDocumentName) } - val expectedResult = - ResolveDocumentOfferInteractorPartialState.Success( - documents = expectedList, - issuerName = mockedIssuerName, - txCodeLength = mockedTxCodeSpecLength - ) + val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( + documents = expectedList, + issuerName = mockedIssuerName, + txCodeLength = mockedTxCodeSpecLength + ) // Then assertEquals(expectedResult, awaitItem()) } @@ -213,9 +214,8 @@ class TestDocumentOfferInteractor { fun `Given Case 3, When resolveDocumentOffer is called, Then Case 3 Expected Result is returned`() = coroutineRule.runTest { // Given - whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)).thenThrow( - mockedExceptionWithNoMessage - ) + whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)) + .thenThrow(mockedExceptionWithNoMessage) // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { @@ -268,10 +268,9 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - val expectedResult = - ResolveDocumentOfferInteractorPartialState.Failure( - errorMessage = mockedInvalidCodeFormatMessage - ) + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedInvalidCodeFormatMessage + ) // Then assertEquals(expectedResult, awaitItem()) } @@ -311,10 +310,9 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - val expectedResult = - ResolveDocumentOfferInteractorPartialState.Failure( - mockedWalletActivationErrorMessage - ) + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedWalletActivationErrorMessage + ) // Then assertEquals(expectedResult, awaitItem()) } @@ -343,7 +341,7 @@ class TestDocumentOfferInteractor { whenever(mockedOfferedDocument.docType) .thenReturn(mockedPidDocType) whenever(resourceProvider.getString(R.string.pid)) - .thenReturn(mockedPidLabel) + .thenReturn(mockedDocUiNamePid) val mockedTxCodeSpecLength = 4 val mockedOfferTxCodeSpec = Offer.TxCodeSpec( @@ -362,14 +360,13 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { val expectedDocumentsUiList = listOf( - DocumentItemUi(mockedPidLabel) + DocumentItemUi(mockedDocUiNamePid) + ) + val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( + documents = expectedDocumentsUiList, + issuerName = mockedIssuerName, + txCodeLength = mockedTxCodeSpecLength ) - val expectedResult = - ResolveDocumentOfferInteractorPartialState.Success( - documents = expectedDocumentsUiList, - issuerName = mockedIssuerName, - txCodeLength = mockedTxCodeSpecLength - ) // Then assertEquals(expectedResult, awaitItem()) @@ -422,9 +419,7 @@ class TestDocumentOfferInteractor { offerUri = mockedUriPath1, txCode = mockedTxCode ) - ).thenThrow( - mockedExceptionWithMessage - ) + ).thenThrow(mockedExceptionWithMessage) // When interactor.issueDocuments( @@ -433,10 +428,9 @@ class TestDocumentOfferInteractor { navigation = mockedConfigNavigation, txCode = mockedTxCode ).runFlowTest { - val expectedResult = - IssueDocumentsInteractorPartialState.Failure( - errorMessage = mockedExceptionWithMessage.localizedMessage!! - ) + val expectedResult = IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedExceptionWithMessage.localizedMessage!! + ) // Then assertEquals(expectedResult, awaitItem()) } @@ -489,7 +483,9 @@ class TestDocumentOfferInteractor { coroutineRule.runTest { // Given mockWalletDocumentsControllerIssueByUriEventEmission( - event = IssueDocumentsPartialState.Failure(errorMessage = mockedPlainFailureMessage) + event = IssueDocumentsPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) ) // When @@ -522,9 +518,8 @@ class TestDocumentOfferInteractor { // Given val mockedArgument = "mockedArgument" val mockedSuccessSubtitle = "mocked success subtitle" - whenever(resourceProvider.getString(R.string.issuance_generic_error)).thenReturn( - mockedErrorMessage - ) + whenever(resourceProvider.getString(R.string.issuance_generic_error)) + .thenReturn(mockedErrorMessage) whenever( resourceProvider.getString( R.string.issuance_document_offer_success_subtitle, @@ -585,12 +580,10 @@ class TestDocumentOfferInteractor { ) ) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_success_title) - ).thenReturn(mockedSuccessTitle) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) - ).thenReturn(mockedButtonText) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) + .thenReturn(mockedSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) + .thenReturn(mockedButtonText) val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( @@ -683,12 +676,10 @@ class TestDocumentOfferInteractor { ) ) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title) - ).thenReturn(mockedSuccessTitle) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text) - ).thenReturn(mockedButtonText) + whenever(resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title)) + .thenReturn(mockedSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text)) + .thenReturn(mockedButtonText) val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( @@ -732,10 +723,9 @@ class TestDocumentOfferInteractor { navigation = mockedConfigNavigation, txCode = mockedTxCode ).runFlowTest { - val expectedResult = - IssueDocumentsInteractorPartialState.DeferredSuccess( - successRoute = "SUCCESS?successConfig=$mockedArguments" - ) + val expectedResult = IssueDocumentsInteractorPartialState.DeferredSuccess( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) // Then assertEquals(expectedResult, awaitItem()) @@ -794,15 +784,12 @@ class TestDocumentOfferInteractor { ) ) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_success_title) - ).thenReturn(mockedSuccessTitle) - whenever( - resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) - ).thenReturn(mockedButtonText) - whenever(resourceProvider.getString(R.string.content_description_success)).thenReturn( - mockedContentDescription - ) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) + .thenReturn(mockedSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) + .thenReturn(mockedButtonText) + whenever(resourceProvider.getString(R.string.content_description_success)) + .thenReturn(mockedContentDescription) val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( @@ -845,10 +832,9 @@ class TestDocumentOfferInteractor { navigation = mockedConfigNavigation, txCode = mockedTxCode ).runFlowTest { - val expectedResult = - IssueDocumentsInteractorPartialState.Success( - successRoute = "SUCCESS?successConfig=$mockedArguments" - ) + val expectedResult = IssueDocumentsInteractorPartialState.Success( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) // Then assertEquals(expectedResult, awaitItem()) @@ -873,8 +859,8 @@ class TestDocumentOfferInteractor { val nonIssuedDeferredDocuments: Map = mapOf( mockedDeferredPendingDocId1 to mockDeferredPendingType1 ) - whenever(resourceProvider.getString(R.string.pid)).thenReturn(mockedPidLabel) - val nonIssuedDocsNames = mockedPidLabel + whenever(resourceProvider.getString(R.string.pid)).thenReturn(mockedDocUiNamePid) + val nonIssuedDocsNames = mockedDocUiNamePid whenever( resourceProvider.getString( R.string.issuance_document_offer_partial_success_subtitle, @@ -894,12 +880,10 @@ class TestDocumentOfferInteractor { val mockedContentDescription = "mocked content description" whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) .thenReturn(mockedOfferSuccessTitle) - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)).thenReturn( - mockedButtonText - ) - whenever(resourceProvider.getString(R.string.content_description_success)).thenReturn( - mockedContentDescription - ) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) + .thenReturn(mockedButtonText) + whenever(resourceProvider.getString(R.string.content_description_success)) + .thenReturn(mockedContentDescription) val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( @@ -943,10 +927,9 @@ class TestDocumentOfferInteractor { navigation = mockedConfigNavigation, txCode = mockedTxCode ).runFlowTest { - val expectedResult = - IssueDocumentsInteractorPartialState.Success( - successRoute = "SUCCESS?successConfig=$mockedArguments" - ) + val expectedResult = IssueDocumentsInteractorPartialState.Success( + successRoute = "SUCCESS?successConfig=$mockedArguments" + ) // Then assertEquals(expectedResult, awaitItem()) @@ -979,10 +962,9 @@ class TestDocumentOfferInteractor { issuerName = mockedIssuerName, navigation = mockedConfigNavigation ).runFlowTest { - val expectedResult = - IssueDocumentsInteractorPartialState.Failure( - errorMessage = mockedPlainFailureMessage - ) + val expectedResult = IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) // Then assertEquals(expectedResult, awaitItem()) } @@ -1125,17 +1107,14 @@ class TestDocumentOfferInteractor { //region mocked objects private val mockedOffer = mock() private val mockedOfferedDocument = mock() - private val mockedIssuerName = "mockedIssuerName" private val mockedOfferDocumentName = "offerDocumentName" private val mockedDocType = "mockedDocType" - private val mockedPidLabel = "mocked PID label" private val mockedInvalidCodeFormatMessage = "mocked invalid code format message" private val mockedWalletActivationErrorMessage = "mocked wallet activation error message" private val mockedArguments = "mockedArguments" private val mockedTxCode = "mockedTxCode" private val mockedButtonText = "mocked button text" private val mockedErrorMessage = "mocked error message" - private val mockedPidDocType = DocumentIdentifier.PID.docType private val mockedSampleDocumentType = DocumentIdentifier.SAMPLE.docType private val mockedConfigNavigation = ConfigNavigation(navigationType = NavigationType.Pop) //endregion From 9c5a49f595062f858ab57c9ab30e1bfd44e5e497 Mon Sep 17 00:00:00 2001 From: Christos Kaitatzis Date: Tue, 15 Oct 2024 15:21:48 +0300 Subject: [PATCH 07/14] Centrally defining mocked data where possible, mocked objects moved to TestsData in order to be reused --- .../europa/ec/commonfeature/util/TestsData.kt | 33 ++ .../document/TestAddDocumentInteractor.kt | 79 ++-- .../document/TestDocumentOfferInteractor.kt | 410 +++++++++--------- 3 files changed, 260 insertions(+), 262 deletions(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt index 6c307b9d5..01b24204f 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt @@ -34,9 +34,14 @@ import eu.europa.ec.eudi.iso18013.transfer.DocItem import eu.europa.ec.eudi.iso18013.transfer.DocRequest import eu.europa.ec.eudi.iso18013.transfer.ReaderAuth import eu.europa.ec.eudi.iso18013.transfer.RequestDocument +import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer +import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec import eu.europa.ec.uilogic.component.AppIcons import eu.europa.ec.uilogic.component.InfoTextWithNameAndImageData import eu.europa.ec.uilogic.component.InfoTextWithNameAndValueData +import eu.europa.ec.uilogic.config.ConfigNavigation +import eu.europa.ec.uilogic.config.NavigationType +import eu.europa.ec.uilogic.navigation.DashboardScreens @VisibleForTesting(otherwise = VisibleForTesting.NONE) object TestsData { @@ -62,6 +67,7 @@ object TestsData { const val mockedMdlDocName = "mDL" const val mockedPidId = "000001" const val mockedMdlId = "000002" + const val mockedDocumentId = "document_id" const val mockedAgeVerificationId = "000003" const val mockedPhotoId = "000004" const val mockedUserFirstName = "JAN" @@ -81,6 +87,18 @@ object TestsData { const val mockedIssuerName = "EUDIW Issuer" const val mockedRequestRequiredFieldsTitle = "Verification Data" const val mockedRequestElementIdentifierNotAvailable = "Not available" + const val mockedOfferedDocumentName = "Offered Document" + const val mockedOfferedDocumentDocType = "mocked_offered_document_doc_type" + const val mockedTxCodeSpecFourDigits = 4 + const val mockedSuccessTitle = "Success title" + const val mockedSuccessSubtitle = "Success subtitle" + const val mockedSuccessContentDescription = "Content description" + const val mockedIssuanceErrorMessage = "Issuance error message" + const val mockedInvalidCodeFormatMessage = "Invalid code format message" + const val mockedWalletActivationErrorMessage = "Wallet activation error message" + const val mockedPrimaryButtonText = "Primary button text" + const val mockedRouteArguments = "mockedRouteArguments" + const val mockedTxCode = "mockedTxCode" const val mockedPidDocType = "eu.europa.ec.eudi.pid.1" const val mockedPidNameSpace = "eu.europa.ec.eudi.pid.1" @@ -477,6 +495,21 @@ object TestsData { available = true ) + val mockedConfigNavigationTypePop = ConfigNavigation(navigationType = NavigationType.Pop) + val mockedConfigNavigationTypePush = ConfigNavigation( + navigationType = NavigationType.PushRoute( + route = DashboardScreens.Dashboard.screenRoute + ) + ) + val mockedConfigNavigationTypePopToScreen = + ConfigNavigation(navigationType = NavigationType.PopTo(screen = DashboardScreens.Dashboard)) + + val mockedOfferTxCodeSpecFourDigits = + TxCodeSpec( + inputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + length = mockedTxCodeSpecFourDigits + ) + val mockedOptionalFieldsForPidWithBasicFields = listOf( TestFieldUi( elementIdentifier = "family_name", diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt index 1a09cf939..7150eca64 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt @@ -24,10 +24,13 @@ import eu.europa.ec.commonfeature.config.IssuanceFlowUiConfig import eu.europa.ec.commonfeature.config.SuccessUIConfig import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor import eu.europa.ec.commonfeature.util.TestsData.mockedAgeOptionItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedConfigNavigationTypePopToScreen +import eu.europa.ec.commonfeature.util.TestsData.mockedConfigNavigationTypePush import eu.europa.ec.commonfeature.util.TestsData.mockedMdlOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedPhotoIdOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedPidId import eu.europa.ec.commonfeature.util.TestsData.mockedPidOptionItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedRouteArguments import eu.europa.ec.commonfeature.util.TestsData.mockedSampleDataOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 import eu.europa.ec.corelogic.controller.AddSampleDataPartialState @@ -49,9 +52,6 @@ import eu.europa.ec.testlogic.extension.runTest import eu.europa.ec.testlogic.extension.toFlow import eu.europa.ec.testlogic.rule.CoroutineTestRule import eu.europa.ec.uilogic.component.AppIcons -import eu.europa.ec.uilogic.config.ConfigNavigation -import eu.europa.ec.uilogic.config.NavigationType -import eu.europa.ec.uilogic.navigation.DashboardScreens import eu.europa.ec.uilogic.serializer.UiSerializer import junit.framework.TestCase.assertEquals import org.junit.After @@ -398,26 +398,6 @@ class TestAddDocumentInteractor { // Given mockDocumentIssuanceStrings() - val mockedTripleObject = Triple( - first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_add_document_deferred_success_title), - color = ThemeColors.warning - ), - second = SuccessUIConfig.ImageConfig( - type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, - drawableRes = AppIcons.ClockTimer.resourceId, - tint = ThemeColors.warning, - contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) - ), - third = resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text) - ) - - val mockedConfigNavigation = - ConfigNavigation( - navigationType = NavigationType.PushRoute( - route = DashboardScreens.Dashboard.screenRoute - ) - ) val config = SuccessUIConfig( headerConfig = mockedTripleObject.first, content = resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle), @@ -426,24 +406,23 @@ class TestAddDocumentInteractor { SuccessUIConfig.ButtonConfig( text = mockedTripleObject.third, style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, - navigation = mockedConfigNavigation + navigation = mockedConfigNavigationTypePush ) ), - onBackScreenToNavigate = mockedConfigNavigation + onBackScreenToNavigate = mockedConfigNavigationTypePush ) - val mockedArguments = "mockedArguments" whenever( uiSerializer.toBase64( model = config, parser = SuccessUIConfig.Parser ) - ).thenReturn(mockedArguments) + ).thenReturn(mockedRouteArguments) val flowType = IssuanceFlowUiConfig.NO_DOCUMENT val result = interactor.buildGenericSuccessRouteForDeferred(flowType = flowType) - val expectedResult = "SUCCESS?successConfig=$mockedArguments" + val expectedResult = "SUCCESS?successConfig=$mockedRouteArguments" assertEquals(expectedResult, result) } @@ -459,23 +438,6 @@ class TestAddDocumentInteractor { // Given mockDocumentIssuanceStrings() - val mockedTripleObject = Triple( - first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_add_document_deferred_success_title), - color = ThemeColors.warning - ), - second = SuccessUIConfig.ImageConfig( - type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, - drawableRes = AppIcons.ClockTimer.resourceId, - tint = ThemeColors.warning, - contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) - ), - third = resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text) - ) - - val mockedConfigNavigation = - ConfigNavigation(navigationType = NavigationType.PopTo(screen = DashboardScreens.Dashboard)) - val config = SuccessUIConfig( headerConfig = mockedTripleObject.first, content = resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle), @@ -484,24 +446,23 @@ class TestAddDocumentInteractor { SuccessUIConfig.ButtonConfig( text = mockedTripleObject.third, style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, - navigation = mockedConfigNavigation + navigation = mockedConfigNavigationTypePopToScreen ) ), - onBackScreenToNavigate = mockedConfigNavigation + onBackScreenToNavigate = mockedConfigNavigationTypePopToScreen ) - val mockedArguments = "mockedArguments" whenever( uiSerializer.toBase64( model = config, parser = SuccessUIConfig.Parser ) - ).thenReturn(mockedArguments) + ).thenReturn(mockedRouteArguments) val flowType = IssuanceFlowUiConfig.EXTRA_DOCUMENT val result = interactor.buildGenericSuccessRouteForDeferred(flowType = flowType) - val expectedResult = "SUCCESS?successConfig=$mockedArguments" + val expectedResult = "SUCCESS?successConfig=$mockedRouteArguments" assertEquals(expectedResult, result) } //endregion @@ -546,4 +507,22 @@ class TestAddDocumentInteractor { .thenReturn(mockedSuccessSubtitle) } //endregion + + //region mocked objects + private val mockedTripleObject by lazy { + Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_add_document_deferred_success_title), + color = ThemeColors.warning + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DRAWABLE, + drawableRes = AppIcons.ClockTimer.resourceId, + tint = ThemeColors.warning, + contentDescription = resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId) + ), + third = resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text) + ) + } + //endregion } \ No newline at end of file diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index 7a37f30a8..0d0a4efda 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -23,11 +23,26 @@ import eu.europa.ec.authenticationlogic.model.BiometricCrypto import eu.europa.ec.commonfeature.config.SuccessUIConfig import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor import eu.europa.ec.commonfeature.ui.request.model.DocumentItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedConfigNavigationTypePop import eu.europa.ec.commonfeature.util.TestsData.mockedDocUiNamePid +import eu.europa.ec.commonfeature.util.TestsData.mockedDocumentId +import eu.europa.ec.commonfeature.util.TestsData.mockedInvalidCodeFormatMessage +import eu.europa.ec.commonfeature.util.TestsData.mockedIssuanceErrorMessage import eu.europa.ec.commonfeature.util.TestsData.mockedIssuerName +import eu.europa.ec.commonfeature.util.TestsData.mockedOfferTxCodeSpecFourDigits +import eu.europa.ec.commonfeature.util.TestsData.mockedOfferedDocumentDocType +import eu.europa.ec.commonfeature.util.TestsData.mockedOfferedDocumentName import eu.europa.ec.commonfeature.util.TestsData.mockedPendingMdlUi import eu.europa.ec.commonfeature.util.TestsData.mockedPendingPidUi +import eu.europa.ec.commonfeature.util.TestsData.mockedPrimaryButtonText +import eu.europa.ec.commonfeature.util.TestsData.mockedRouteArguments +import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessContentDescription +import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessSubtitle +import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessTitle +import eu.europa.ec.commonfeature.util.TestsData.mockedTxCode +import eu.europa.ec.commonfeature.util.TestsData.mockedTxCodeSpecFourDigits import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 +import eu.europa.ec.commonfeature.util.TestsData.mockedWalletActivationErrorMessage import eu.europa.ec.corelogic.controller.IssueDocumentsPartialState import eu.europa.ec.corelogic.controller.ResolveDocumentOfferPartialState import eu.europa.ec.corelogic.controller.WalletCoreDocumentsController @@ -36,6 +51,7 @@ import eu.europa.ec.corelogic.model.DocumentIdentifier import eu.europa.ec.eudi.wallet.document.DocumentId import eu.europa.ec.eudi.wallet.document.IssuedDocument import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer +import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.provider.ResourceProvider import eu.europa.ec.resourceslogic.theme.values.ThemeColors @@ -51,8 +67,6 @@ import eu.europa.ec.testlogic.extension.runTest import eu.europa.ec.testlogic.extension.toFlow import eu.europa.ec.testlogic.rule.CoroutineTestRule import eu.europa.ec.uilogic.component.AppIcons -import eu.europa.ec.uilogic.config.ConfigNavigation -import eu.europa.ec.uilogic.config.NavigationType import eu.europa.ec.uilogic.serializer.UiSerializer import junit.framework.TestCase.assertEquals import org.junit.After @@ -134,8 +148,9 @@ class TestDocumentOfferInteractor { fun `Given Case 1, When resolveDocumentOffer is called, Then Case 1 Expected Result is returned`() = coroutineRule.runTest { // Given - whenever(mockedOffer.issuerName) - .thenReturn(mockedIssuerName) + val mockedOffer = mockOffer( + issuerName = mockedIssuerName + ) mockGetMainPidDocumentCall( mainPid = mockedMainPid ) @@ -148,7 +163,7 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { val expectedResult = ResolveDocumentOfferInteractorPartialState.NoDocument( - issuerName = mockedIssuerName + issuerName = mockedOffer.issuerName ) // Then assertEquals(expectedResult, awaitItem()) @@ -169,18 +184,11 @@ class TestDocumentOfferInteractor { fun `Given Case 2, When resolveDocumentOffer is called, Then Case 2 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) - whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) - whenever(mockedOfferedDocument.name).thenReturn(mockedOfferDocumentName) - whenever(mockedOfferedDocument.docType).thenReturn(mockedDocType) - val mockedTxCodeSpecLength = 4 - val mockedOfferTxCodeSpec = Offer.TxCodeSpec( - length = mockedTxCodeSpecLength + val mockedOffer = mockOffer( + issuerName = mockedIssuerName, + offeredDocuments = mockedOfferedDocumentsList, + txCodeSpec = mockedOfferTxCodeSpecFourDigits ) - whenever(mockedOffer.issuerName) - .thenReturn(mockedIssuerName) - whenever(mockedOffer.txCodeSpec) - .thenReturn(mockedOfferTxCodeSpec) mockGetMainPidDocumentCall( mainPid = mockedMainPid ) @@ -191,12 +199,12 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { val expectedList = mockedOfferedDocumentsList.map { - DocumentItemUi(title = mockedOfferDocumentName) + DocumentItemUi(title = mockedOfferedDocumentName) } val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( documents = expectedList, issuerName = mockedIssuerName, - txCodeLength = mockedTxCodeSpecLength + txCodeLength = mockedOfferTxCodeSpecFourDigits.length ) // Then assertEquals(expectedResult, awaitItem()) @@ -240,17 +248,15 @@ class TestDocumentOfferInteractor { fun `Given Case 4, When resolveDocumentOffer is called, Then Case 4 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) - whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) - whenever(mockedOfferedDocument.name).thenReturn(mockedOfferDocumentName) - whenever(mockedOfferedDocument.docType).thenReturn(mockedDocType) - val mockedTxCodeSpecLength = 2 - val mockedOfferTxCodeSpec = Offer.TxCodeSpec( - inputMode = Offer.TxCodeSpec.InputMode.NUMERIC, - length = mockedTxCodeSpecLength + val mockedOffer = mockOffer( + issuerName = mockedIssuerName, + offeredDocuments = mockedOfferedDocumentsList, + txCodeSpec = mockOfferTxCodeSpec( + inputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + length = mockedTxCodeSpecLength + ) ) - whenever(mockedOffer.txCodeSpec).thenReturn(mockedOfferTxCodeSpec) val codeMinLength = 4 val codeMaxLength = 6 @@ -291,17 +297,17 @@ class TestDocumentOfferInteractor { fun `Given Case 5, When resolveDocumentOffer is called, Then Case 5 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) - whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) - whenever(mockedOfferedDocument.name).thenReturn(mockedOfferDocumentName) - whenever(mockedOfferedDocument.docType).thenReturn(mockedSampleDocumentType) - - val mockedTxCodeSpecLength = 4 - val mockedTxCodeSpec = Offer.TxCodeSpec( - length = mockedTxCodeSpecLength + val mockedOffer = mockOffer( + issuerName = mockedIssuerName, + offeredDocuments = listOf( + mockOfferedDocument( + name = mockedOfferedDocumentName, + docType = DocumentIdentifier.SAMPLE.docType + ) + ), + txCodeSpec = mockedOfferTxCodeSpecFourDigits ) - whenever(mockedOffer.issuerName).thenReturn(mockedIssuerName) - whenever(mockedOffer.txCodeSpec).thenReturn(mockedTxCodeSpec) + whenever(resourceProvider.getString(R.string.issuance_document_offer_error_missing_pid_text)) .thenReturn(mockedWalletActivationErrorMessage) mockWalletDocumentsControllerResolveOfferEventEmission( @@ -334,21 +340,13 @@ class TestDocumentOfferInteractor { fun `Given Case 6, When resolveDocumentOffer is called, Then Case 6 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedOfferedDocumentsList = listOf(mockedOfferedDocument) - whenever(mockedOffer.offeredDocuments).thenReturn(mockedOfferedDocumentsList) - whenever(mockedOfferedDocument.name) - .thenReturn(mockedOfferDocumentName) - whenever(mockedOfferedDocument.docType) - .thenReturn(mockedPidDocType) - whenever(resourceProvider.getString(R.string.pid)) - .thenReturn(mockedDocUiNamePid) - - val mockedTxCodeSpecLength = 4 - val mockedOfferTxCodeSpec = Offer.TxCodeSpec( - length = mockedTxCodeSpecLength + val mockedOffer = mockOffer( + issuerName = mockedIssuerName, + offeredDocuments = mockedOfferedDocumentsList, + txCodeSpec = mockedOfferTxCodeSpecFourDigits ) - whenever(mockedOffer.issuerName).thenReturn(mockedIssuerName) - whenever(mockedOffer.txCodeSpec).thenReturn(mockedOfferTxCodeSpec) + whenever(resourceProvider.getString(R.string.pid)) + .thenReturn(mockedOfferedDocumentName) mockGetMainPidDocumentCall( mainPid = mockedMainPid @@ -360,12 +358,12 @@ class TestDocumentOfferInteractor { // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { val expectedDocumentsUiList = listOf( - DocumentItemUi(mockedDocUiNamePid) + DocumentItemUi(mockedOfferedDocumentName) ) val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( documents = expectedDocumentsUiList, issuerName = mockedIssuerName, - txCodeLength = mockedTxCodeSpecLength + txCodeLength = mockedOffer.txCodeSpec?.length ) // Then @@ -425,7 +423,7 @@ class TestDocumentOfferInteractor { interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Failure( @@ -459,7 +457,7 @@ class TestDocumentOfferInteractor { interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Failure( @@ -492,7 +490,7 @@ class TestDocumentOfferInteractor { interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { @@ -516,14 +514,12 @@ class TestDocumentOfferInteractor { fun `Given Case 4, When issueDocuments is called, Then Case 4 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedArgument = "mockedArgument" - val mockedSuccessSubtitle = "mocked success subtitle" whenever(resourceProvider.getString(R.string.issuance_generic_error)) - .thenReturn(mockedErrorMessage) + .thenReturn(mockedIssuanceErrorMessage) whenever( resourceProvider.getString( R.string.issuance_document_offer_success_subtitle, - mockedArgument + mockedIssuerName ) ).thenReturn(mockedSuccessSubtitle) @@ -538,7 +534,7 @@ class TestDocumentOfferInteractor { interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.UserAuthRequired( @@ -564,40 +560,19 @@ class TestDocumentOfferInteractor { fun `Given Case 5, When issueDocuments is called, Then Case 5 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedDocumentId = "mockedDocumentId" - val mockedSuccessTitle = "mocked success title" - val mockedSuccessSubtitle = "mocked success subtitle" - whenever( - resourceProvider.getString( - R.string.issuance_document_offer_success_subtitle, - mockedIssuerName - ) - ).thenReturn(mockedSuccessSubtitle) - mockWalletDocumentsControllerIssueByUriEventEmission( event = IssueDocumentsPartialState.Success( documentIds = listOf(mockedDocumentId) ) ) - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) - .thenReturn(mockedSuccessTitle) - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) - .thenReturn(mockedButtonText) - - val mockedTripleObject = Triple( - first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_document_offer_success_title), - color = ThemeColors.success - ), - second = SuccessUIConfig.ImageConfig( - type = SuccessUIConfig.ImageConfig.Type.DEFAULT, - drawableRes = null, - tint = ThemeColors.success, - contentDescription = resourceProvider.getString(R.string.content_description_success) - ), - third = resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) - ) + mockIssuanceDocumentOfferSuccessStrings() + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_success_subtitle, + mockedIssuerName + ) + ).thenReturn(mockedSuccessSubtitle) val mockedSuccessUiConfig = SuccessUIConfig( headerConfig = mockedTripleObject.first, @@ -607,10 +582,10 @@ class TestDocumentOfferInteractor { SuccessUIConfig.ButtonConfig( text = mockedTripleObject.third, style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, - navigation = mockedConfigNavigation + navigation = mockedConfigNavigationTypePop ) ), - onBackScreenToNavigate = mockedConfigNavigation, + onBackScreenToNavigate = mockedConfigNavigationTypePop ) whenever( @@ -618,17 +593,17 @@ class TestDocumentOfferInteractor { model = mockedSuccessUiConfig, parser = SuccessUIConfig.Parser ) - ).thenReturn(mockedArguments) + ).thenReturn(mockedRouteArguments) // When interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Success( - successRoute = "SUCCESS?successConfig=$mockedArguments" + successRoute = "SUCCESS?successConfig=$mockedRouteArguments" ) // Then @@ -641,7 +616,8 @@ class TestDocumentOfferInteractor { // IssueDocumentsPartialState.DeferredSuccess with: // mocked deferred documents // 2. required strings are mocked - // 3. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedArguments + // 3. triple object with warning tint color + // 4. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedRouteArguments // Case 6 Expected Result: // IssueDocumentsInteractorPartialState.DeferredSuccess state, with: @@ -650,8 +626,6 @@ class TestDocumentOfferInteractor { fun `Given Case 6, When issueDocuments is called, Then Case 6 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedSuccessTitle = "mocked success title" - val mockedSuccessSubtitle = "mocked success subtitle" whenever( resourceProvider.getString( R.string.issuance_document_offer_deferred_success_subtitle, @@ -659,28 +633,13 @@ class TestDocumentOfferInteractor { ) ).thenReturn(mockedSuccessSubtitle) - val mockDeferredPendingDocId1 = mockedPendingPidUi.documentId - val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType - - val mockDeferredPendingDocId2 = mockedPendingMdlUi.documentId - val mockDeferredPendingType2 = mockedPendingMdlUi.documentIdentifier.docType - - val deferredDocuments: Map = mapOf( - mockDeferredPendingDocId1 to mockDeferredPendingType1, - mockDeferredPendingDocId2 to mockDeferredPendingType2 - ) - + mockIssuanceDocumentOfferDeferredSuccessStrings() mockWalletDocumentsControllerIssueByUriEventEmission( event = IssueDocumentsPartialState.DeferredSuccess( - deferredDocuments = deferredDocuments + deferredDocuments = mockDeferredDocumentsMap() ) ) - whenever(resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title)) - .thenReturn(mockedSuccessTitle) - whenever(resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text)) - .thenReturn(mockedButtonText) - val mockedTripleObject = Triple( first = SuccessUIConfig.HeaderConfig( title = resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title), @@ -703,10 +662,10 @@ class TestDocumentOfferInteractor { SuccessUIConfig.ButtonConfig( text = mockedTripleObject.third, style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, - navigation = mockedConfigNavigation + navigation = mockedConfigNavigationTypePop ) ), - onBackScreenToNavigate = mockedConfigNavigation + onBackScreenToNavigate = mockedConfigNavigationTypePop ) whenever( @@ -714,17 +673,17 @@ class TestDocumentOfferInteractor { model = config, parser = SuccessUIConfig.Parser ) - ).thenReturn(mockedArguments) + ).thenReturn(mockedRouteArguments) // When interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.DeferredSuccess( - successRoute = "SUCCESS?successConfig=$mockedArguments" + successRoute = "SUCCESS?successConfig=$mockedRouteArguments" ) // Then @@ -750,9 +709,6 @@ class TestDocumentOfferInteractor { fun `Given Case 7, When issueDocuments is called, Then Case 7 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedDocumentId = "mockedDocumentId" - val mockedSuccessTitle = "mocked success subtitle" - val mockDeferredPendingDocId1 = mockedPendingPidUi.documentId val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType @@ -767,8 +723,6 @@ class TestDocumentOfferInteractor { val nonIssuedDocsNames = "${mockedPendingPidUi.documentIdentifier.docType}, ${mockedPendingMdlUi.documentIdentifier.docType}" - val mockedSuccessSubtitle = "mocked success subtitle" - val mockedContentDescription = "mocked content description" whenever( resourceProvider.getString( R.string.issuance_document_offer_partial_success_subtitle, @@ -784,26 +738,9 @@ class TestDocumentOfferInteractor { ) ) - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) - .thenReturn(mockedSuccessTitle) - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) - .thenReturn(mockedButtonText) + mockIssuanceDocumentOfferSuccessStrings() whenever(resourceProvider.getString(R.string.content_description_success)) - .thenReturn(mockedContentDescription) - - val mockedTripleObject = Triple( - first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_document_offer_success_title), - color = ThemeColors.success - ), - second = SuccessUIConfig.ImageConfig( - type = SuccessUIConfig.ImageConfig.Type.DEFAULT, - drawableRes = null, - tint = ThemeColors.success, - contentDescription = resourceProvider.getString(R.string.content_description_success) - ), - third = resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) - ) + .thenReturn(mockedSuccessContentDescription) val config = SuccessUIConfig( headerConfig = mockedTripleObject.first, @@ -813,10 +750,10 @@ class TestDocumentOfferInteractor { SuccessUIConfig.ButtonConfig( text = mockedTripleObject.third, style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, - navigation = mockedConfigNavigation + navigation = mockedConfigNavigationTypePop ) ), - onBackScreenToNavigate = mockedConfigNavigation + onBackScreenToNavigate = mockedConfigNavigationTypePop ) whenever( @@ -824,16 +761,16 @@ class TestDocumentOfferInteractor { model = config, parser = SuccessUIConfig.Parser ) - ).thenReturn(mockedArguments) + ).thenReturn(mockedRouteArguments) interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Success( - successRoute = "SUCCESS?successConfig=$mockedArguments" + successRoute = "SUCCESS?successConfig=$mockedRouteArguments" ) // Then @@ -852,22 +789,21 @@ class TestDocumentOfferInteractor { fun `Given Case 8, When issueDocuments is called, Then Case 8 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedDocumentId = "mockedDocumentId" - val mockedSubtitle = "mocked subtitle" val mockedDeferredPendingDocId1 = mockedPidDocType val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType val nonIssuedDeferredDocuments: Map = mapOf( mockedDeferredPendingDocId1 to mockDeferredPendingType1 ) - whenever(resourceProvider.getString(R.string.pid)).thenReturn(mockedDocUiNamePid) + val nonIssuedDocsNames = mockedDocUiNamePid + whenever(resourceProvider.getString(R.string.pid)).thenReturn(nonIssuedDocsNames) whenever( resourceProvider.getString( R.string.issuance_document_offer_partial_success_subtitle, mockedIssuerName, nonIssuedDocsNames ) - ).thenReturn(mockedSubtitle) + ).thenReturn(mockedSuccessSubtitle) mockWalletDocumentsControllerIssueByUriEventEmission( event = IssueDocumentsPartialState.PartialSuccess( @@ -876,59 +812,26 @@ class TestDocumentOfferInteractor { ) ) - val mockedOfferSuccessTitle = "mocked offer success title" - val mockedContentDescription = "mocked content description" - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) - .thenReturn(mockedOfferSuccessTitle) - whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) - .thenReturn(mockedButtonText) + mockIssuanceDocumentOfferSuccessStrings() whenever(resourceProvider.getString(R.string.content_description_success)) - .thenReturn(mockedContentDescription) - - val mockedTripleObject = Triple( - first = SuccessUIConfig.HeaderConfig( - title = resourceProvider.getString(R.string.issuance_document_offer_success_title), - color = ThemeColors.success - ), - second = SuccessUIConfig.ImageConfig( - type = SuccessUIConfig.ImageConfig.Type.DEFAULT, - drawableRes = null, - tint = ThemeColors.success, - contentDescription = resourceProvider.getString(R.string.content_description_success) - ), - third = resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) - ) - - val config = SuccessUIConfig( - headerConfig = mockedTripleObject.first, - content = mockedSubtitle, - imageConfig = mockedTripleObject.second, - buttonConfig = listOf( - SuccessUIConfig.ButtonConfig( - text = mockedTripleObject.third, - style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, - navigation = mockedConfigNavigation - ) - ), - onBackScreenToNavigate = mockedConfigNavigation - ) + .thenReturn(mockedSuccessContentDescription) whenever( uiSerializer.toBase64( - model = config, + model = mockedSuccessUiConfig, parser = SuccessUIConfig.Parser ) - ).thenReturn(mockedArguments) + ).thenReturn(mockedRouteArguments) // When interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation, + navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Success( - successRoute = "SUCCESS?successConfig=$mockedArguments" + successRoute = "SUCCESS?successConfig=$mockedRouteArguments" ) // Then @@ -949,18 +852,16 @@ class TestDocumentOfferInteractor { // Given val failureResponse = IssueDocumentsPartialState.Failure(errorMessage = mockedPlainFailureMessage) - whenever( - walletCoreDocumentsController.issueDocumentsByOfferUri( - offerUri = mockedUriPath1, - txCode = null - ) - ).thenReturn(failureResponse.toFlow()) + mockWalletDocumentsControllerIssueByUriEventEmission( + event = failureResponse, + txCode = null + ) // When interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigation + navigation = mockedConfigNavigationTypePop ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Failure( errorMessage = mockedPlainFailureMessage @@ -1086,11 +987,14 @@ class TestDocumentOfferInteractor { .thenReturn(event.toFlow()) } - private fun mockWalletDocumentsControllerIssueByUriEventEmission(event: IssueDocumentsPartialState) { + private fun mockWalletDocumentsControllerIssueByUriEventEmission( + event: IssueDocumentsPartialState, + txCode: String? = mockedTxCode + ) { whenever( walletCoreDocumentsController.issueDocumentsByOfferUri( offerUri = mockedUriPath1, - txCode = mockedTxCode + txCode = txCode ) ).thenReturn(event.toFlow()) } @@ -1102,20 +1006,102 @@ class TestDocumentOfferInteractor { bioAvailability(response) } } + + private fun mockDeferredDocumentsMap(): Map { + val mockDeferredPendingDocId1 = mockedPendingPidUi.documentId + val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType + + val mockDeferredPendingDocId2 = mockedPendingMdlUi.documentId + val mockDeferredPendingType2 = mockedPendingMdlUi.documentIdentifier.docType + + return mapOf( + mockDeferredPendingDocId1 to mockDeferredPendingType1, + mockDeferredPendingDocId2 to mockDeferredPendingType2 + ) + } + + private fun mockIssuanceDocumentOfferSuccessStrings() { + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_title)) + .thenReturn(mockedSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text)) + .thenReturn(mockedPrimaryButtonText) + } + + private fun mockIssuanceDocumentOfferDeferredSuccessStrings() { + whenever(resourceProvider.getString(R.string.issuance_document_offer_deferred_success_title)) + .thenReturn(mockedSuccessTitle) + whenever(resourceProvider.getString(R.string.issuance_document_offer_deferred_success_primary_button_text)) + .thenReturn(mockedPrimaryButtonText) + } + + private fun mockOffer( + issuerName: String, + offeredDocuments: List = listOf(), + txCodeSpec: TxCodeSpec? = mockOfferTxCodeSpec() + ): Offer { + return mock(Offer::class.java).apply { + whenever(this.issuerName).thenReturn(issuerName) + whenever(this.offeredDocuments).thenReturn(offeredDocuments) + whenever(this.txCodeSpec).thenReturn(txCodeSpec) + } + } + + private fun mockOfferedDocument( + name: String = mockedOfferedDocumentName, + docType: String = mockedOfferedDocumentDocType + ): Offer.OfferedDocument { + return mock(Offer.OfferedDocument::class.java).apply { + whenever(this.name).thenReturn(name) + whenever(this.docType).thenReturn(docType) + } + } + + private fun mockOfferTxCodeSpec( + inputMode: TxCodeSpec.InputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + length: Int? = mockedTxCodeSpecFourDigits, + description: String? = null + ): TxCodeSpec { + return TxCodeSpec(inputMode, length, description) + } //endregion //region mocked objects - private val mockedOffer = mock() - private val mockedOfferedDocument = mock() - private val mockedOfferDocumentName = "offerDocumentName" - private val mockedDocType = "mockedDocType" - private val mockedInvalidCodeFormatMessage = "mocked invalid code format message" - private val mockedWalletActivationErrorMessage = "mocked wallet activation error message" - private val mockedArguments = "mockedArguments" - private val mockedTxCode = "mockedTxCode" - private val mockedButtonText = "mocked button text" - private val mockedErrorMessage = "mocked error message" - private val mockedSampleDocumentType = DocumentIdentifier.SAMPLE.docType - private val mockedConfigNavigation = ConfigNavigation(navigationType = NavigationType.Pop) + private val mockedOfferedDocumentsList = + listOf( + mockOfferedDocument(docType = DocumentIdentifier.SAMPLE.docType) + ) + + private val mockedTripleObject by lazy { + Triple( + first = SuccessUIConfig.HeaderConfig( + title = resourceProvider.getString(R.string.issuance_document_offer_success_title), + color = ThemeColors.success + ), + second = SuccessUIConfig.ImageConfig( + type = SuccessUIConfig.ImageConfig.Type.DEFAULT, + drawableRes = null, + tint = ThemeColors.success, + contentDescription = resourceProvider.getString(R.string.content_description_success) + ), + third = + resourceProvider.getString(R.string.issuance_document_offer_success_primary_button_text) + ) + } + + private val mockedSuccessUiConfig by lazy { + SuccessUIConfig( + headerConfig = mockedTripleObject.first, + content = mockedSuccessSubtitle, + imageConfig = mockedTripleObject.second, + buttonConfig = listOf( + SuccessUIConfig.ButtonConfig( + text = mockedTripleObject.third, + style = SuccessUIConfig.ButtonConfig.Style.PRIMARY, + navigation = mockedConfigNavigationTypePop + ) + ), + onBackScreenToNavigate = mockedConfigNavigationTypePop + ) + } //endregion } \ No newline at end of file From e946378328ac4a7e18979a5746f535229facb46b Mon Sep 17 00:00:00 2001 From: Stylianos Tzouvaras Date: Tue, 15 Oct 2024 18:53:54 +0300 Subject: [PATCH 08/14] gradle update --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8b191e76c..c270c05e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanist = "0.34.0" androidDesugarJdkLibs = "2.1.2" -androidGradlePlugin = "8.7.0" +androidGradlePlugin = "8.7.1" androidxActivity = "1.9.2" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" @@ -37,7 +37,7 @@ kotlinxCoroutines = "1.8.1" kotlinxDatetime = "0.4.1" kotlinxSerializationJson = "1.7.1" ksp = "2.0.10-1.0.24" -lint = "31.7.0" +lint = "31.7.1" okhttp = "4.12.0" protobuf = "3.24.0" protobufPlugin = "0.9.4" From 65519d1202ec3ec49ca3de6e31b4c88499e72b13 Mon Sep 17 00:00:00 2001 From: Stylianos Tzouvaras Date: Wed, 16 Oct 2024 00:14:01 +0300 Subject: [PATCH 09/14] ThemeManager should not throw runtime exception if its not set, it should initialize it self with the default values. Only if someone wants to override the default values can take control with the builder outside the ThemeManager. --- .../eu/europa/ec/assemblylogic/Application.kt | 20 -------------- .../kotlin/AndroidTestConventionPlugin.kt | 2 -- .../europa/ec/commonfeature/util/TestsData.kt | 3 +-- .../document/TestDocumentOfferInteractor.kt | 4 +-- .../ec/resourceslogic/theme/ThemeManager.kt | 18 ++++++++++--- .../ec/testlogic/base/TestApplication.kt | 26 +------------------ .../uilogic/component/preview/PreviewTheme.kt | 15 ----------- .../ec/uilogic/component/snackbar/Snackbar.kt | 1 - .../ec/uilogic/component/wrap/WrapIcon.kt | 1 - 9 files changed, 18 insertions(+), 72 deletions(-) diff --git a/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt b/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt index 46547fe81..9422434aa 100644 --- a/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt +++ b/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt @@ -21,11 +21,6 @@ import eu.europa.ec.analyticslogic.controller.AnalyticsController import eu.europa.ec.assemblylogic.di.setupKoin import eu.europa.ec.corelogic.config.WalletCoreConfig import eu.europa.ec.eudi.wallet.EudiWallet -import eu.europa.ec.resourceslogic.theme.ThemeManager -import eu.europa.ec.resourceslogic.theme.templates.ThemeDimensTemplate -import eu.europa.ec.resourceslogic.theme.values.ThemeColors -import eu.europa.ec.resourceslogic.theme.values.ThemeShapes -import eu.europa.ec.resourceslogic.theme.values.ThemeTypography import org.koin.android.ext.android.inject class Application : Application() { @@ -38,27 +33,12 @@ class Application : Application() { setupKoin() initializeReporting() initializeEudiWallet() - initializeTheme() } private fun initializeReporting() { analyticsController.initialize(this) } - private fun initializeTheme() { - ThemeManager.Builder() - .withLightColors(ThemeColors.lightColors) - .withDarkColors(ThemeColors.darkColors) - .withTypography(ThemeTypography.typo) - .withShapes(ThemeShapes.shapes) - .withDimensions( - ThemeDimensTemplate( - screenPadding = 10.0 - ) - ) - .build() - } - private fun initializeEudiWallet() { EudiWallet.init( applicationContext, diff --git a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt index 30e7929d9..166e35d4f 100644 --- a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt @@ -20,7 +20,6 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.kotlin -import project.convention.logic.config.LibraryModule import project.convention.logic.configureGradleManagedDevices import project.convention.logic.libs @@ -52,7 +51,6 @@ class AndroidTestConventionPlugin : Plugin { add("api", libs.findLibrary("mockito-kotlin").get()) add("api", libs.findLibrary("mockito-inline").get()) add("api", libs.findLibrary("robolectric").get()) - add("implementation", project(LibraryModule.ResourcesLogic.path)) } } } diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt index 01b24204f..f63374a4e 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt @@ -34,7 +34,6 @@ import eu.europa.ec.eudi.iso18013.transfer.DocItem import eu.europa.ec.eudi.iso18013.transfer.DocRequest import eu.europa.ec.eudi.iso18013.transfer.ReaderAuth import eu.europa.ec.eudi.iso18013.transfer.RequestDocument -import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec import eu.europa.ec.uilogic.component.AppIcons import eu.europa.ec.uilogic.component.InfoTextWithNameAndImageData @@ -506,7 +505,7 @@ object TestsData { val mockedOfferTxCodeSpecFourDigits = TxCodeSpec( - inputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + inputMode = TxCodeSpec.InputMode.NUMERIC, length = mockedTxCodeSpecFourDigits ) diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index 0d0a4efda..78b1825a7 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -253,7 +253,7 @@ class TestDocumentOfferInteractor { issuerName = mockedIssuerName, offeredDocuments = mockedOfferedDocumentsList, txCodeSpec = mockOfferTxCodeSpec( - inputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + inputMode = TxCodeSpec.InputMode.NUMERIC, length = mockedTxCodeSpecLength ) ) @@ -1057,7 +1057,7 @@ class TestDocumentOfferInteractor { } private fun mockOfferTxCodeSpec( - inputMode: TxCodeSpec.InputMode = Offer.TxCodeSpec.InputMode.NUMERIC, + inputMode: TxCodeSpec.InputMode = TxCodeSpec.InputMode.NUMERIC, length: Int? = mockedTxCodeSpecFourDigits, description: String? = null ): TxCodeSpec { diff --git a/resources-logic/src/main/java/eu/europa/ec/resourceslogic/theme/ThemeManager.kt b/resources-logic/src/main/java/eu/europa/ec/resourceslogic/theme/ThemeManager.kt index 889ac04cc..c9e9a811b 100644 --- a/resources-logic/src/main/java/eu/europa/ec/resourceslogic/theme/ThemeManager.kt +++ b/resources-logic/src/main/java/eu/europa/ec/resourceslogic/theme/ThemeManager.kt @@ -36,6 +36,9 @@ import eu.europa.ec.resourceslogic.theme.templates.ThemeShapesTemplate import eu.europa.ec.resourceslogic.theme.templates.ThemeShapesTemplate.Companion.toShapes import eu.europa.ec.resourceslogic.theme.templates.ThemeTypographyTemplate import eu.europa.ec.resourceslogic.theme.templates.ThemeTypographyTemplate.Companion.toTypography +import eu.europa.ec.resourceslogic.theme.values.ThemeColors +import eu.europa.ec.resourceslogic.theme.values.ThemeShapes +import eu.europa.ec.resourceslogic.theme.values.ThemeTypography class ThemeManager { /** @@ -106,9 +109,16 @@ class ThemeManager { val instance: ThemeManager get() { if (this::_instance.isInitialized.not()) { - throw RuntimeException( - "Theme manager not initialized. Initialize via ThemeManager builder first." - ) + _instance = Builder() + .withLightColors(ThemeColors.lightColors) + .withDarkColors(ThemeColors.darkColors) + .withTypography(ThemeTypography.typo) + .withShapes(ThemeShapes.shapes) + .withDimensions( + ThemeDimensTemplate( + screenPadding = 10.0 + ) + ).build() } return _instance @@ -120,7 +130,7 @@ class ThemeManager { */ fun ThemeManager.build(builder: Builder): ThemeManager { set = ThemeSet( - isInDarkMode = builder.isInDarkMode ?: false, + isInDarkMode = builder.isInDarkMode == true, lightColors = builder.lightColors.toColorScheme(), darkColors = builder.darkColors.toColorScheme(), typo = builder.typography.toTypography(), diff --git a/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt b/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt index fbdf800e4..57034e28d 100644 --- a/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt +++ b/test-logic/src/main/java/eu/europa/ec/testlogic/base/TestApplication.kt @@ -17,29 +17,5 @@ package eu.europa.ec.testlogic.base import android.app.Application -import eu.europa.ec.resourceslogic.theme.ThemeManager -import eu.europa.ec.resourceslogic.theme.templates.ThemeDimensTemplate -import eu.europa.ec.resourceslogic.theme.values.ThemeColors -import eu.europa.ec.resourceslogic.theme.values.ThemeShapes -import eu.europa.ec.resourceslogic.theme.values.ThemeTypography -class TestApplication : Application() { - override fun onCreate() { - super.onCreate() - initializeTheme() - } - - private fun initializeTheme() { - ThemeManager.Builder() - .withLightColors(ThemeColors.lightColors) - .withDarkColors(ThemeColors.darkColors) - .withTypography(ThemeTypography.typo) - .withShapes(ThemeShapes.shapes) - .withDimensions( - ThemeDimensTemplate( - screenPadding = 10.0 - ) - ) - .build() - } -} \ No newline at end of file +class TestApplication : Application() \ No newline at end of file diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/preview/PreviewTheme.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/preview/PreviewTheme.kt index eef61fd59..4593f0dd4 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/preview/PreviewTheme.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/preview/PreviewTheme.kt @@ -18,25 +18,10 @@ package eu.europa.ec.uilogic.component.preview import androidx.compose.runtime.Composable import eu.europa.ec.resourceslogic.theme.ThemeManager -import eu.europa.ec.resourceslogic.theme.templates.ThemeDimensTemplate -import eu.europa.ec.resourceslogic.theme.values.ThemeColors -import eu.europa.ec.resourceslogic.theme.values.ThemeShapes -import eu.europa.ec.resourceslogic.theme.values.ThemeTypography @Composable fun PreviewTheme( content: @Composable () -> Unit ) { - ThemeManager.Builder() - .withLightColors(ThemeColors.lightColors) - .withDarkColors(ThemeColors.darkColors) - .withTypography(ThemeTypography.typo) - .withShapes(ThemeShapes.shapes) - .withDimensions( - ThemeDimensTemplate( - screenPadding = 10.0 - ) - ) - .build() ThemeManager.instance.Theme { content() } } \ No newline at end of file diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/snackbar/Snackbar.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/snackbar/Snackbar.kt index 9c58f2d62..481508ce9 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/snackbar/Snackbar.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/snackbar/Snackbar.kt @@ -30,7 +30,6 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/wrap/WrapIcon.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/wrap/WrapIcon.kt index 3a350d12d..9a02f6716 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/wrap/WrapIcon.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/wrap/WrapIcon.kt @@ -18,7 +18,6 @@ package eu.europa.ec.uilogic.component.wrap import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape From a865b10ad82ca2a9cb816e66798efd627c1cd7c5 Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Wed, 16 Oct 2024 16:50:28 +0300 Subject: [PATCH 10/14] Review corrections --- .../europa/ec/commonfeature/util/TestsData.kt | 8 +- .../document/TestAddDocumentInteractor.kt | 44 +- .../document/TestDocumentOfferInteractor.kt | 456 ++++++++++-------- 3 files changed, 288 insertions(+), 220 deletions(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt index f63374a4e..994697b38 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsData.kt @@ -66,7 +66,6 @@ object TestsData { const val mockedMdlDocName = "mDL" const val mockedPidId = "000001" const val mockedMdlId = "000002" - const val mockedDocumentId = "document_id" const val mockedAgeVerificationId = "000003" const val mockedPhotoId = "000004" const val mockedUserFirstName = "JAN" @@ -500,8 +499,11 @@ object TestsData { route = DashboardScreens.Dashboard.screenRoute ) ) - val mockedConfigNavigationTypePopToScreen = - ConfigNavigation(navigationType = NavigationType.PopTo(screen = DashboardScreens.Dashboard)) + val mockedConfigNavigationTypePopToScreen = ConfigNavigation( + navigationType = NavigationType.PopTo( + screen = DashboardScreens.Dashboard + ) + ) val mockedOfferTxCodeSpecFourDigits = TxCodeSpec( diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt index 7150eca64..6a79df9cf 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt @@ -30,8 +30,12 @@ import eu.europa.ec.commonfeature.util.TestsData.mockedMdlOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedPhotoIdOptionItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedPidId import eu.europa.ec.commonfeature.util.TestsData.mockedPidOptionItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedPrimaryButtonText import eu.europa.ec.commonfeature.util.TestsData.mockedRouteArguments import eu.europa.ec.commonfeature.util.TestsData.mockedSampleDataOptionItemUi +import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessContentDescription +import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessSubtitle +import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessTitle import eu.europa.ec.commonfeature.util.TestsData.mockedUriPath1 import eu.europa.ec.corelogic.controller.AddSampleDataPartialState import eu.europa.ec.corelogic.controller.IssuanceMethod @@ -46,7 +50,6 @@ import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedGenericErrorMessage import eu.europa.ec.testfeature.mockedPlainFailureMessage -import eu.europa.ec.testlogic.base.TestApplication import eu.europa.ec.testlogic.extension.runFlowTest import eu.europa.ec.testlogic.extension.runTest import eu.europa.ec.testlogic.extension.toFlow @@ -58,7 +61,6 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -66,11 +68,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestApplication::class) class TestAddDocumentInteractor { @get:Rule @@ -310,7 +308,7 @@ class TestAddDocumentInteractor { // Case 1 Expected Result: // deviceAuthenticationInteractor.authenticateWithBiometrics called once. @Test - fun `Given case 1, When handleUserAuth is called, Then Case 1 expected result is returned`() { + fun `Given Case 1, When handleUserAuth is called, Then Case 1 expected result is returned`() { // Given mockBiometricsAvailabilityResponse( response = BiometricsAvailability.CanAuthenticate @@ -335,7 +333,7 @@ class TestAddDocumentInteractor { // Case 2 Expected Result: // deviceAuthenticationInteractor.authenticateWithBiometrics called once. @Test - fun `Given case 2, When handleUserAuth is called, Then Case 2 expected result is returned`() { + fun `Given Case 2, When handleUserAuth is called, Then Case 2 expected result is returned`() { // Given mockBiometricsAvailabilityResponse( response = BiometricsAvailability.NonEnrolled @@ -360,7 +358,7 @@ class TestAddDocumentInteractor { // Case 3 Expected Result: // resultHandler.onAuthenticationFailure called once. @Test - fun `Given case 3, When handleUserAuth is called, Then Case 3 expected result is returned`() { + fun `Given Case 3, When handleUserAuth is called, Then Case 3 expected result is returned`() { // Given val mockedOnAuthenticationFailure: () -> Unit = {} whenever(resultHandler.onAuthenticationFailure) @@ -386,15 +384,12 @@ class TestAddDocumentInteractor { //endregion //region buildGenericSuccessRouteForDeferred - // + // Case 1: // 1. ConfigNavigation with NavigationType.PushRoute // 2. string resources mocked - // - // when buildGenericSuccessRouteForDeferred is called on the interactor - // the expected string result is generated for route definition @Test - fun `When buildGenericSuccessRouteForDeferred is called, then the expected string result is returned`() { + fun `Given Case 1, When buildGenericSuccessRouteForDeferred is called, Then the expected string result is returned`() { // Given mockDocumentIssuanceStrings() @@ -420,19 +415,18 @@ class TestAddDocumentInteractor { ).thenReturn(mockedRouteArguments) val flowType = IssuanceFlowUiConfig.NO_DOCUMENT + + // When val result = interactor.buildGenericSuccessRouteForDeferred(flowType = flowType) + // Then val expectedResult = "SUCCESS?successConfig=$mockedRouteArguments" assertEquals(expectedResult, result) } - // // Case 2: // 1. ConfigNavigation with NavigationType.PopRoute // 2. string resources mocked - - // when buildGenericSuccessRouteForDeferred is called on the interactor - // the expected string result is generated for route definition @Test fun `When buildGenericSuccessRouteForDeferred (PopRoute) is called, then the expected string result is returned`() { // Given @@ -460,15 +454,18 @@ class TestAddDocumentInteractor { ).thenReturn(mockedRouteArguments) val flowType = IssuanceFlowUiConfig.EXTRA_DOCUMENT + + // When val result = interactor.buildGenericSuccessRouteForDeferred(flowType = flowType) + // Then val expectedResult = "SUCCESS?successConfig=$mockedRouteArguments" assertEquals(expectedResult, result) } //endregion //region resumeOpenId4VciWithAuthorization - // + // Case of resumeOpenId4VciWithAuthorization being called on the interactor // the expected result is the resumeOpenId4VciWithAuthorization function to be executed on // the walletCoreDocumentsController @@ -492,17 +489,12 @@ class TestAddDocumentInteractor { } private fun mockDocumentIssuanceStrings() { - val mockedSuccessTitle = "mocked success title" - val mockedSuccessSubtitle = "mocked success subtitle" - val mockedButtonText = "mocked button text" - val mockedContentDescriptionId = "mocked content description id" - whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_title)) .thenReturn(mockedSuccessTitle) whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_primary_button_text)) - .thenReturn(mockedButtonText) + .thenReturn(mockedPrimaryButtonText) whenever(resourceProvider.getString(AppIcons.ClockTimer.contentDescriptionId)) - .thenReturn(mockedContentDescriptionId) + .thenReturn(mockedSuccessContentDescription) whenever(resourceProvider.getString(R.string.issuance_add_document_deferred_success_subtitle)) .thenReturn(mockedSuccessSubtitle) } diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt index 78b1825a7..e0dca4f2b 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentOfferInteractor.kt @@ -25,7 +25,6 @@ import eu.europa.ec.commonfeature.interactor.DeviceAuthenticationInteractor import eu.europa.ec.commonfeature.ui.request.model.DocumentItemUi import eu.europa.ec.commonfeature.util.TestsData.mockedConfigNavigationTypePop import eu.europa.ec.commonfeature.util.TestsData.mockedDocUiNamePid -import eu.europa.ec.commonfeature.util.TestsData.mockedDocumentId import eu.europa.ec.commonfeature.util.TestsData.mockedInvalidCodeFormatMessage import eu.europa.ec.commonfeature.util.TestsData.mockedIssuanceErrorMessage import eu.europa.ec.commonfeature.util.TestsData.mockedIssuerName @@ -34,6 +33,7 @@ import eu.europa.ec.commonfeature.util.TestsData.mockedOfferedDocumentDocType import eu.europa.ec.commonfeature.util.TestsData.mockedOfferedDocumentName import eu.europa.ec.commonfeature.util.TestsData.mockedPendingMdlUi import eu.europa.ec.commonfeature.util.TestsData.mockedPendingPidUi +import eu.europa.ec.commonfeature.util.TestsData.mockedPidId import eu.europa.ec.commonfeature.util.TestsData.mockedPrimaryButtonText import eu.europa.ec.commonfeature.util.TestsData.mockedRouteArguments import eu.europa.ec.commonfeature.util.TestsData.mockedSuccessContentDescription @@ -59,9 +59,9 @@ import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedGenericErrorMessage import eu.europa.ec.testfeature.mockedMainPid +import eu.europa.ec.testfeature.mockedPidDocName import eu.europa.ec.testfeature.mockedPidDocType import eu.europa.ec.testfeature.mockedPlainFailureMessage -import eu.europa.ec.testlogic.base.TestApplication import eu.europa.ec.testlogic.extension.runFlowTest import eu.europa.ec.testlogic.extension.runTest import eu.europa.ec.testlogic.extension.toFlow @@ -73,7 +73,6 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @@ -81,11 +80,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestApplication::class) class TestDocumentOfferInteractor { @get:Rule @@ -136,14 +131,14 @@ class TestDocumentOfferInteractor { } //region resolveDocumentOffer - // + // Case 1: - // 1. walletCoreDocumentsController.resolveDocumentOffer() returns - // ResolveDocumentOfferPartialState.Success + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns ResolveDocumentOfferPartialState.Success with: + // - empty response.offer.offeredDocuments // Case 1 Expected Result: // ResolveDocumentOfferInteractorPartialState.NoDocument state with: - // - the issuer name string + // - the issuer name @Test fun `Given Case 1, When resolveDocumentOffer is called, Then Case 1 Expected Result is returned`() = coroutineRule.runTest { @@ -171,40 +166,46 @@ class TestDocumentOfferInteractor { } // Case 2: - // 1. walletCoreDocumentsController.resolveDocumentOffer() returns - // ResolveDocumentOfferPartialState.Success with: - // an Offer item holding a document list of Offer.OfferedDocument + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns ResolveDocumentOfferPartialState.Success with: + // - valid response.offer.txCodeSpec?.inputMode (TxCodeSpec.InputMode.NUMERIC), + // - invalid response.offer.txCodeSpec?.length (2), and + // - response.offer.offeredDocuments has only one Offer.OfferedDocument item that its docType is not supported. // Case 2 Expected Result: - // ResolveDocumentOfferInteractorPartialState.Success state, with: - // - DocumentUiItem list - // - issuer name (string) - // - and txCodeLength (int) + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - an invalid code format error message @Test fun `Given Case 2, When resolveDocumentOffer is called, Then Case 2 Expected Result is returned`() = coroutineRule.runTest { // Given + val mockedTxCodeSpecLength = 2 val mockedOffer = mockOffer( issuerName = mockedIssuerName, offeredDocuments = mockedOfferedDocumentsList, - txCodeSpec = mockedOfferTxCodeSpecFourDigits - ) - mockGetMainPidDocumentCall( - mainPid = mockedMainPid + txCodeSpec = mockOfferTxCodeSpec( + inputMode = TxCodeSpec.InputMode.NUMERIC, + length = mockedTxCodeSpecLength + ) ) + + val codeMinLength = 4 + val codeMaxLength = 6 + whenever( + resourceProvider.getString( + R.string.issuance_document_offer_error_invalid_txcode_format, + codeMinLength, + codeMaxLength + ) + ).thenReturn(mockedInvalidCodeFormatMessage) + mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - val expectedList = mockedOfferedDocumentsList.map { - DocumentItemUi(title = mockedOfferedDocumentName) - } - val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( - documents = expectedList, - issuerName = mockedIssuerName, - txCodeLength = mockedOfferTxCodeSpecFourDigits.length + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedInvalidCodeFormatMessage ) // Then assertEquals(expectedResult, awaitItem()) @@ -212,48 +213,24 @@ class TestDocumentOfferInteractor { } // Case 3: - // 1. walletCoreDocumentsController.resolveDocumentOffer() throws: - // a RuntimeException without message + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns ResolveDocumentOfferPartialState.Success with: + // - invalid response.offer.txCodeSpec?.inputMode (TxCodeSpec.InputMode.TEXT), + // - valid response.offer.txCodeSpec?.length (4), and + // - response.offer.offeredDocuments has only one Offer.OfferedDocument item that its docType is not supported. // Case 3 Expected Result: // ResolveDocumentOfferInteractorPartialState.Failure state, with: - // - a generic error message - @Test - fun `Given Case 3, When resolveDocumentOffer is called, Then Case 3 Expected Result is returned`() = - coroutineRule.runTest { - // Given - whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)) - .thenThrow(mockedExceptionWithNoMessage) - - // When - interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - // Then - val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( - errorMessage = mockedGenericErrorMessage - ) - assertEquals(expectedResult, awaitItem()) - } - } - - // Case 4: - // 1. walletCoreDocumentsController.resolveDocumentOffer() returns - // ResolveDocumentOfferPartialState.Success with: - // an Offer item holding a document list of Offer.OfferedDocument items - // txCodeSpec with length of 2 (numeric type), less than given limits of 4 to 6 digits length - - // Case 4 Expected Result: - // ResolveDocumentOfferInteractorPartialState.Failure state, with: // - an invalid code format error message @Test - fun `Given Case 4, When resolveDocumentOffer is called, Then Case 4 Expected Result is returned`() = + fun `Given Case 3, When resolveDocumentOffer is called, Then Case 3 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedTxCodeSpecLength = 2 + val mockedTxCodeSpecLength = 4 val mockedOffer = mockOffer( issuerName = mockedIssuerName, offeredDocuments = mockedOfferedDocumentsList, txCodeSpec = mockOfferTxCodeSpec( - inputMode = TxCodeSpec.InputMode.NUMERIC, + inputMode = TxCodeSpec.InputMode.TEXT, length = mockedTxCodeSpecLength ) ) @@ -282,17 +259,63 @@ class TestDocumentOfferInteractor { } } + // Case 4: + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns ResolveDocumentOfferPartialState.Success with: + // - valid response.offer.txCodeSpec?.inputMode (TxCodeSpec.InputMode.NUMERIC), + // - valid response.offer.txCodeSpec?.length (4), and + // - response.offer.offeredDocuments has only one Offer.OfferedDocument item that its docType is not supported. + // 2. walletCoreDocumentsController.getMainPidDocument() returns not null (i.e. hasMainPid == true). + // 3. no PID in Offer (i.e hasPidInOffer == false). + + // Case 4 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Success state, with: + // - DocumentUiItem list, with non-localized document names + // - issuer name + // - and txCodeLength + @Test + fun `Given Case 4, When resolveDocumentOffer is called, Then Case 4 Expected Result is returned`() = + coroutineRule.runTest { + // Given + val mockedOffer = mockOffer( + issuerName = mockedIssuerName, + offeredDocuments = mockedOfferedDocumentsList, + txCodeSpec = mockedOfferTxCodeSpecFourDigits + ) + mockGetMainPidDocumentCall( + mainPid = mockedMainPid + ) + mockWalletDocumentsControllerResolveOfferEventEmission( + event = ResolveDocumentOfferPartialState.Success(mockedOffer) + ) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + val expectedList = mockedOfferedDocumentsList.map { + DocumentItemUi(title = mockedOfferedDocumentName) + } + val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( + documents = expectedList, + issuerName = mockedIssuerName, + txCodeLength = mockedOfferTxCodeSpecFourDigits.length + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + // Case 5: - // 1. walletCoreDocumentsController.resolveDocumentOffer() returns - // ResolveDocumentOfferPartialState.Success with: - // an Offer item holding a document list of Offer.OfferedDocument items - // one of the OfferedDocument list items with docType of "load_sample_documents" - // (isSupported() returns false in this case) - // txCodeSpec with length of 4 (numeric type) + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns ResolveDocumentOfferPartialState.Success with: + // - valid response.offer.txCodeSpec?.inputMode (TxCodeSpec.InputMode.NUMERIC), + // - valid response.offer.txCodeSpec?.length (4), and + // - response.offer.offeredDocuments has only one Offer.OfferedDocument item that its docType is supported. + // 2. walletCoreDocumentsController.getMainPidDocument() returns null (i.e. hasMainPid == false). + // 3. a PID in Offer (i.e hasPidInOffer == true). // Case 5 Expected Result: - // ResolveDocumentOfferInteractorPartialState.Failure state, with: - // - an invalid wallet activation error message + // ResolveDocumentOfferInteractorPartialState.Success state, with: + // - DocumentUiItem list, with localized document names + // - issuer name + // - and txCodeLength @Test fun `Given Case 5, When resolveDocumentOffer is called, Then Case 5 Expected Result is returned`() = coroutineRule.runTest { @@ -302,40 +325,48 @@ class TestDocumentOfferInteractor { offeredDocuments = listOf( mockOfferedDocument( name = mockedOfferedDocumentName, - docType = DocumentIdentifier.SAMPLE.docType + docType = mockedPidDocType ) ), txCodeSpec = mockedOfferTxCodeSpecFourDigits ) + mockGetMainPidDocumentCall( + mainPid = null + ) + whenever(resourceProvider.getString(R.string.pid)) + .thenReturn(mockedPidDocName) - whenever(resourceProvider.getString(R.string.issuance_document_offer_error_missing_pid_text)) - .thenReturn(mockedWalletActivationErrorMessage) mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( - errorMessage = mockedWalletActivationErrorMessage + val expectedDocumentsUiList = listOf( + DocumentItemUi(mockedPidDocName) + ) + val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( + documents = expectedDocumentsUiList, + issuerName = mockedIssuerName, + txCodeLength = mockedOffer.txCodeSpec?.length ) + // Then assertEquals(expectedResult, awaitItem()) } } // Case 6: - // 1. walletCoreDocumentsController.resolveDocumentOffer() returns - // ResolveDocumentOfferPartialState.Success with: - // an Offer item holding a document list of Offer.OfferedDocument items - // one of the OfferedDocument list items with docType of PID - // txCodeSpec with length of 4 (numeric type) + // 1. walletCoreDocumentsController.resolveDocumentOffer() returns ResolveDocumentOfferPartialState.Success with: + // - valid response.offer.txCodeSpec?.inputMode (TxCodeSpec.InputMode.NUMERIC), + // - valid response.offer.txCodeSpec?.length (4), and + // - response.offer.offeredDocuments has only one Offer.OfferedDocument item that its docType is not supported. + // 2. walletCoreDocumentsController.getMainPidDocument() returns null (i.e. hasMainPid == false). + // 3. no PID in Offer (i.e hasPidInOffer == false). // Case 6 Expected Result: - // ResolveDocumentOfferInteractorPartialState.Success state, with: - // - the expected DocumentItemUi list - // - issuer name string and - // - txCodeLength (int) + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - an invalid wallet activation error message @Test fun `Given Case 6, When resolveDocumentOffer is called, Then Case 6 Expected Result is returned`() = coroutineRule.runTest { @@ -345,27 +376,21 @@ class TestDocumentOfferInteractor { offeredDocuments = mockedOfferedDocumentsList, txCodeSpec = mockedOfferTxCodeSpecFourDigits ) - whenever(resourceProvider.getString(R.string.pid)) - .thenReturn(mockedOfferedDocumentName) - mockGetMainPidDocumentCall( - mainPid = mockedMainPid + mainPid = null ) + + whenever(resourceProvider.getString(R.string.issuance_document_offer_error_missing_pid_text)) + .thenReturn(mockedWalletActivationErrorMessage) mockWalletDocumentsControllerResolveOfferEventEmission( event = ResolveDocumentOfferPartialState.Success(mockedOffer) ) // When interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { - val expectedDocumentsUiList = listOf( - DocumentItemUi(mockedOfferedDocumentName) - ) - val expectedResult = ResolveDocumentOfferInteractorPartialState.Success( - documents = expectedDocumentsUiList, - issuerName = mockedIssuerName, - txCodeLength = mockedOffer.txCodeSpec?.length + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedWalletActivationErrorMessage ) - // Then assertEquals(expectedResult, awaitItem()) } @@ -397,61 +422,74 @@ class TestDocumentOfferInteractor { } } - //endregion - - //region issueDocuments - // Case 1: - // 1. walletCoreDocumentsController.issueDocumentsByOfferUri() is called with: - // parameters being mocked - // 2. A mockedExceptionWithMessage (RuntimeException) is thrown with message + // Case 8: + // 1. walletCoreDocumentsController.resolveDocumentOffer() throws: + // a RuntimeException with a message - // Case 1 Expected Result: - // IssueDocumentsInteractorPartialState.Failure state, with: - // - errorMessage equal to the localized message of mockedExceptionWithMessage + // Case 8 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - the exception's localized message @Test - fun `Given Case 1, When issueDocuments is called, Then Case 1 Expected Result is returned`() = + fun `Given Case 8, When resolveDocumentOffer is called, Then Case 8 Expected Result is returned`() = coroutineRule.runTest { // Given - whenever( - walletCoreDocumentsController.issueDocumentsByOfferUri( - offerUri = mockedUriPath1, - txCode = mockedTxCode - ) - ).thenThrow(mockedExceptionWithMessage) + whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)) + .thenThrow(mockedExceptionWithMessage) // When - interactor.issueDocuments( - offerUri = mockedUriPath1, - issuerName = mockedIssuerName, - navigation = mockedConfigNavigationTypePop, - txCode = mockedTxCode - ).runFlowTest { - val expectedResult = IssueDocumentsInteractorPartialState.Failure( + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { + // Then + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( errorMessage = mockedExceptionWithMessage.localizedMessage!! ) + assertEquals(expectedResult, awaitItem()) + } + } + + // Case 9: + // 1. walletCoreDocumentsController.resolveDocumentOffer() throws: + // a RuntimeException without message + + // Case 9 Expected Result: + // ResolveDocumentOfferInteractorPartialState.Failure state, with: + // - the generic error message + @Test + fun `Given Case 9, When resolveDocumentOffer is called, Then Case 9 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever(walletCoreDocumentsController.resolveDocumentOffer(mockedUriPath1)) + .thenThrow(mockedExceptionWithNoMessage) + + // When + interactor.resolveDocumentOffer(mockedUriPath1).runFlowTest { // Then + val expectedResult = ResolveDocumentOfferInteractorPartialState.Failure( + errorMessage = mockedGenericErrorMessage + ) assertEquals(expectedResult, awaitItem()) } } + //endregion - // Case 2: - // 1. walletCoreDocumentsController.issueDocumentsByOfferUri() is called with: - // parameters being mocked - // 2. A mockedExceptionWithNoMessage (RuntimeException) is thrown + //region issueDocuments - // Case 2 Expected Result: + // Case 1: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits + // IssueDocumentsPartialState.Failure with: + // mockedPlainFailureMessage as the error message + + // Case 1 Expected Result: // IssueDocumentsInteractorPartialState.Failure state, with: - // - a mockedGenericErrorMessage + // - errorMessage equal to mockedPlainFailureMessage @Test - fun `Given Case 2, When issueDocuments is called, Then Case 2 Expected Result is returned`() = + fun `Given Case 1, When issueDocuments is called, Then Case 1 Expected Result is returned`() = coroutineRule.runTest { // Given - whenever( - walletCoreDocumentsController.issueDocumentsByOfferUri( - offerUri = mockedUriPath1, - txCode = mockedTxCode + mockWalletDocumentsControllerIssueByUriEventEmission( + event = IssueDocumentsPartialState.Failure( + errorMessage = mockedPlainFailureMessage ) - ).thenThrow(mockedExceptionWithNoMessage) + ) // When interactor.issueDocuments( @@ -460,58 +498,60 @@ class TestDocumentOfferInteractor { navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { + // Then val expectedResult = IssueDocumentsInteractorPartialState.Failure( - errorMessage = mockedGenericErrorMessage + errorMessage = mockedPlainFailureMessage ) - // Then assertEquals(expectedResult, awaitItem()) } } - // Case 3: - // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // Case 2: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits // IssueDocumentsPartialState.Failure with: // mockedPlainFailureMessage as the error message + // 2. The controller issueDocumentsByOfferUri is called with a mocked offerUri and null txCode - // Case 3 Expected Result: + // Case 2 Expected Result: // IssueDocumentsInteractorPartialState.Failure state, with: - // - errorMessage equal to mockedPlainFailureMessage + // - errorMessage equal to mockedPlainFailureMessage. @Test - fun `Given Case 3, When issueDocuments is called, Then Case 3 Expected Result is returned`() = + fun `Given Case 2, When issueDocuments is called, Then Case 2 Expected Result is returned`() = coroutineRule.runTest { // Given + val failureResponse = IssueDocumentsPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) mockWalletDocumentsControllerIssueByUriEventEmission( - event = IssueDocumentsPartialState.Failure( - errorMessage = mockedPlainFailureMessage - ) + event = failureResponse, + txCode = null ) // When interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigationTypePop, - txCode = mockedTxCode + navigation = mockedConfigNavigationTypePop ).runFlowTest { - val expectedResult = IssueDocumentsInteractorPartialState.Failure( errorMessage = mockedPlainFailureMessage ) + // Then assertEquals(expectedResult, awaitItem()) } } - // Case 4: - // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // Case 3: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits // IssueDocumentsPartialState.UserAuthRequired with: // biometricCrypto object and resultHandler as DeviceAuthenticationResult // 2. required arguments are mocked - // Case 4 Expected Result: + // Case 3 Expected Result: // IssueDocumentsInteractorPartialState.UserAuthRequired state, with parameters of: // - biometricCrypto object and resultHandler as DeviceAuthenticationResult @Test - fun `Given Case 4, When issueDocuments is called, Then Case 4 Expected Result is returned`() = + fun `Given Case 3, When issueDocuments is called, Then Case 3 Expected Result is returned`() = coroutineRule.runTest { // Given whenever(resourceProvider.getString(R.string.issuance_generic_error)) @@ -537,6 +577,7 @@ class TestDocumentOfferInteractor { navigation = mockedConfigNavigationTypePop, txCode = mockedTxCode ).runFlowTest { + // Then val expectedResult = IssueDocumentsInteractorPartialState.UserAuthRequired( crypto = biometricCrypto, resultHandler = resultHandler @@ -546,23 +587,22 @@ class TestDocumentOfferInteractor { } } - // Case 5: - // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // Case 4: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits // IssueDocumentsPartialState.Success with: - // biometricCrypto object and resultHandler as DeviceAuthenticationResult - // 2. required strings are mocked - // 3. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedArguments + // 1. required strings are mocked + // 2. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedArguments - // Case 5 Expected Result: + // Case 4 Expected Result: // IssueDocumentsInteractorPartialState.Success state, with: // - successRoute equal to "SUCCESS?successConfig=mockedArguments" @Test - fun `Given Case 5, When issueDocuments is called, Then Case 5 Expected Result is returned`() = + fun `Given Case 4, When issueDocuments is called, Then Case 4 Expected Result is returned`() = coroutineRule.runTest { // Given mockWalletDocumentsControllerIssueByUriEventEmission( event = IssueDocumentsPartialState.Success( - documentIds = listOf(mockedDocumentId) + documentIds = listOf(mockedPidId) ) ) @@ -611,19 +651,19 @@ class TestDocumentOfferInteractor { } } - // Case 6: - // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // Case 5: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits // IssueDocumentsPartialState.DeferredSuccess with: // mocked deferred documents // 2. required strings are mocked // 3. triple object with warning tint color // 4. uiSerializer.toBase64() serializes the mockedSuccessUiConfig into mockedRouteArguments - // Case 6 Expected Result: + // Case 5 Expected Result: // IssueDocumentsInteractorPartialState.DeferredSuccess state, with: // - successRoute equal to "SUCCESS?successConfig=mockedArguments" @Test - fun `Given Case 6, When issueDocuments is called, Then Case 6 Expected Result is returned`() = + fun `Given Case 5, When issueDocuments is called, Then Case 5 Expected Result is returned`() = coroutineRule.runTest { // Given whenever( @@ -691,10 +731,10 @@ class TestDocumentOfferInteractor { } } - // Case 7: - // 1. mockWalletDocumentsControllerIssueByUriEventEmission() emits + // Case 6: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits // IssueDocumentsPartialState.PartialSuccess with: - // - documentIds containing mockedDocumentId. + // - successfully issued documentIds. // - nonIssuedDocuments map containing mockDeferredPendingDocId1 to mockDeferredPendingType1 // and mockDeferredPendingDocId2 to mockDeferredPendingType2. // 2. nonIssuedDocsNames is formed by combining the document types of non-issued documents: @@ -702,13 +742,15 @@ class TestDocumentOfferInteractor { // 3. mocked string resources // 7. uiSerializer.toBase64() serializes the SuccessUIConfig object into mockedArguments. - // Case 7 Expected Result: + // Case 6 Expected Result: // IssueDocumentsInteractorPartialState.Success state, with: // - successRoute equal to "SUCCESS?successConfig=mockedArguments" @Test - fun `Given Case 7, When issueDocuments is called, Then Case 7 Expected Result is returned`() = + fun `Given Case 6, When issueDocuments is called, Then Case 6 Expected Result is returned`() = coroutineRule.runTest { // Given + val mockSuccessfullyIssuedDocId = "0000" + val mockDeferredPendingDocId1 = mockedPendingPidUi.documentId val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType @@ -733,7 +775,7 @@ class TestDocumentOfferInteractor { mockWalletDocumentsControllerIssueByUriEventEmission( event = IssueDocumentsPartialState.PartialSuccess( - documentIds = listOf(mockedDocumentId), + documentIds = listOf(mockSuccessfullyIssuedDocId), nonIssuedDocuments = nonIssuedDeferredDocuments ) ) @@ -778,21 +820,23 @@ class TestDocumentOfferInteractor { } } - // Case 8: - // 1. IssueDocumentsPartialState.PartialSuccess is returned by issueDocumentsByOfferUri + // Case 7: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri emits IssueDocumentsPartialState.PartialSuccess // 2. The interactor is called with the given offerUri, issuerName, navigation and txCode. - // Case 8 Expected Result: + // Case 7 Expected Result: // IssueDocumentsInteractorPartialState.Success state, with: // - successRoute equal to "SUCCESS?successConfig=mockedArguments". @Test - fun `Given Case 8, When issueDocuments is called, Then Case 8 Expected Result is returned`() = + fun `Given Case 7, When issueDocuments is called, Then Case 7 Expected Result is returned`() = coroutineRule.runTest { // Given - val mockedDeferredPendingDocId1 = mockedPidDocType + val mockSuccessfullyIssuedDocId = "0000" + + val mockDeferredPendingDocId1 = mockedPidDocType val mockDeferredPendingType1 = mockedPendingPidUi.documentIdentifier.docType val nonIssuedDeferredDocuments: Map = mapOf( - mockedDeferredPendingDocId1 to mockDeferredPendingType1 + mockDeferredPendingDocId1 to mockDeferredPendingType1 ) val nonIssuedDocsNames = mockedDocUiNamePid @@ -807,7 +851,7 @@ class TestDocumentOfferInteractor { mockWalletDocumentsControllerIssueByUriEventEmission( event = IssueDocumentsPartialState.PartialSuccess( - documentIds = listOf(mockedDocumentId), + documentIds = listOf(mockSuccessfullyIssuedDocId), nonIssuedDocuments = nonIssuedDeferredDocuments ) ) @@ -839,38 +883,69 @@ class TestDocumentOfferInteractor { } } + // Case 8: + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri throws an exception with a message. + + // Case 8 Expected Result: + // IssueDocumentsInteractorPartialState.Failure state, with: + // - errorMessage equal to exception's localized message. + @Test + fun `Given Case 8, When issueDocuments is called, Then Case 8 Expected Result is returned`() = + coroutineRule.runTest { + // Given + whenever( + walletCoreDocumentsController.issueDocumentsByOfferUri( + offerUri = mockedUriPath1, + txCode = mockedTxCode + ) + ).thenThrow(mockedExceptionWithMessage) + + // When + interactor.issueDocuments( + offerUri = mockedUriPath1, + issuerName = mockedIssuerName, + navigation = mockedConfigNavigationTypePop, + txCode = mockedTxCode + ).runFlowTest { + val expectedResult = IssueDocumentsInteractorPartialState.Failure( + errorMessage = mockedExceptionWithMessage.localizedMessage!! + ) + // Then + assertEquals(expectedResult, awaitItem()) + } + } + // Case 9: - // 1. IssueDocumentsPartialState.Failure is returned by issueDocumentsByOfferUri - // 2. The controller issueDocumentsByOfferUri is called with a mocked offerUri and null txCode + // 1. walletCoreDocumentsController.issueDocumentsByOfferUri() throws an exception with no message. // Case 9 Expected Result: // IssueDocumentsInteractorPartialState.Failure state, with: - // - errorMessage equal to mockedPlainFailureMessage. + // - the generic error message. @Test fun `Given Case 9, When issueDocuments is called, Then Case 9 Expected Result is returned`() = coroutineRule.runTest { // Given - val failureResponse = - IssueDocumentsPartialState.Failure(errorMessage = mockedPlainFailureMessage) - mockWalletDocumentsControllerIssueByUriEventEmission( - event = failureResponse, - txCode = null - ) + whenever( + walletCoreDocumentsController.issueDocumentsByOfferUri( + offerUri = mockedUriPath1, + txCode = mockedTxCode + ) + ).thenThrow(mockedExceptionWithNoMessage) // When interactor.issueDocuments( offerUri = mockedUriPath1, issuerName = mockedIssuerName, - navigation = mockedConfigNavigationTypePop + navigation = mockedConfigNavigationTypePop, + txCode = mockedTxCode ).runFlowTest { val expectedResult = IssueDocumentsInteractorPartialState.Failure( - errorMessage = mockedPlainFailureMessage + errorMessage = mockedGenericErrorMessage ) // Then assertEquals(expectedResult, awaitItem()) } } - //endregion //region handleUserAuthentication @@ -882,7 +957,7 @@ class TestDocumentOfferInteractor { // Case 1 Expected Result: // deviceAuthenticationInteractor.authenticateWithBiometrics called once. @Test - fun `Given case 1, When handleUserAuthentication is called, Then Case 1 expected result is returned`() { + fun `Given Case 1, When handleUserAuthentication is called, Then Case 1 expected result is returned`() { // Given mockBiometricsAvailabilityResponse( response = BiometricsAvailability.CanAuthenticate @@ -911,7 +986,7 @@ class TestDocumentOfferInteractor { // Case 2 Expected Result: // deviceAuthenticationInteractor.authenticateWithBiometrics called once. @Test - fun `Given case 2, When handleUserAuthentication is called, Then Case 2 expected result is returned`() { + fun `Given Case 2, When handleUserAuthentication is called, Then Case 2 expected result is returned`() { // Given mockBiometricsAvailabilityResponse( response = BiometricsAvailability.NonEnrolled @@ -940,7 +1015,7 @@ class TestDocumentOfferInteractor { // Case 3 Expected Result: // resultHandler.onAuthenticationFailure called once. @Test - fun `Given case 3, When handleUserAuthentication is called, Then Case 3 expected result is returned`() { + fun `Given Case 3, When handleUserAuthentication is called, Then Case 3 expected result is returned`() { // Given val mockedOnAuthenticationFailure: () -> Unit = {} whenever(resultHandler.onAuthenticationFailure) @@ -1066,10 +1141,9 @@ class TestDocumentOfferInteractor { //endregion //region mocked objects - private val mockedOfferedDocumentsList = - listOf( - mockOfferedDocument(docType = DocumentIdentifier.SAMPLE.docType) - ) + private val mockedOfferedDocumentsList = listOf( + mockOfferedDocument(docType = DocumentIdentifier.SAMPLE.docType) + ) private val mockedTripleObject by lazy { Triple( From d5962527ed97f8aa61ff5f64fd2821ed77c7c0ed Mon Sep 17 00:00:00 2001 From: Petteri Stenius Date: Thu, 17 Oct 2024 13:46:17 +0300 Subject: [PATCH 11/14] uses QRCodeReader directly --- .../ui/qr_scan/component/QrCodeAnalyzer.kt | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt index eeb7d6a4d..a9b12277f 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt @@ -19,12 +19,10 @@ package eu.europa.ec.commonfeature.ui.qr_scan.component import android.graphics.ImageFormat import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy -import com.google.zxing.BarcodeFormat import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType -import com.google.zxing.MultiFormatReader import com.google.zxing.PlanarYUVLuminanceSource import com.google.zxing.common.HybridBinarizer +import com.google.zxing.qrcode.QRCodeReader import java.nio.ByteBuffer class QrCodeAnalyzer( @@ -52,15 +50,7 @@ class QrCodeAnalyzer( ) val binaryBmp = BinaryBitmap(HybridBinarizer(source)) try { - val result = MultiFormatReader().apply { - setHints( - mapOf( - DecodeHintType.POSSIBLE_FORMATS to arrayListOf( - BarcodeFormat.QR_CODE - ) - ) - ) - }.decode(binaryBmp) + val result = QRCodeReader().decode(binaryBmp) onQrCodeScanned(result.text) } catch (e: Exception) { e.printStackTrace() From e49b0968ba56333d7ca1427e9cd8eb464b0492dc Mon Sep 17 00:00:00 2001 From: Petteri Stenius Date: Thu, 17 Oct 2024 13:46:57 +0300 Subject: [PATCH 12/14] for PlanarYUVLuminanceSource set dataWidth to rowStride --- .../ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt index a9b12277f..741a5a906 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt @@ -37,10 +37,11 @@ class QrCodeAnalyzer( override fun analyze(image: ImageProxy) { if (image.format in supportedImageFormats) { - val bytes = image.planes.first().buffer.toByteArray() + val plane = image.planes.first(); + val bytes = plane.buffer.toByteArray() val source = PlanarYUVLuminanceSource( bytes, - image.width, + plane.rowStride, image.height, 0, 0, From 56941205cef069cb28e547e48570e233f75f4b74 Mon Sep 17 00:00:00 2001 From: Stilianos Tzouvaras Date: Mon, 21 Oct 2024 14:54:21 +0300 Subject: [PATCH 13/14] Dependency updates --- gradle/libs.versions.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c270c05e2..839196d2a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,19 +2,19 @@ accompanist = "0.34.0" androidDesugarJdkLibs = "2.1.2" androidGradlePlugin = "8.7.1" -androidxActivity = "1.9.2" +androidxActivity = "1.9.3" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" -androidxComposeBom = "2024.09.03" -androidxComposeRuntimeTracing = "1.7.3" +androidxComposeBom = "2024.10.00" +androidxComposeRuntimeTracing = "1.7.4" androidxCore = "1.13.1" androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.1.1" androidxEspresso = "3.6.1" androidxLifecycle = "2.8.6" -androidxMacroBenchmark = "1.3.2" +androidxMacroBenchmark = "1.3.3" androidxMetrics = "1.0.0-beta01" -androidxNavigation = "2.8.2" +androidxNavigation = "2.8.3" androidxProfileinstaller = "1.4.1" androidxTestCore = "1.6.1" androidxTestExt = "1.2.1" @@ -64,7 +64,7 @@ material3 = "1.3.0" appCenter = "5.0.4" kover = "0.7.5" sonar = "5.0.0.4638" -baselineprofile = "1.3.2" +baselineprofile = "1.3.3" timber = "5.0.1" treessence = "1.1.2" From 50e1bce7cd00d5e53839e9e1b98be5ad87391a67 Mon Sep 17 00:00:00 2001 From: Petteri Stenius Date: Tue, 22 Oct 2024 14:44:48 +0300 Subject: [PATCH 14/14] typo - remove extra colon --- .../ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt index 741a5a906..a1516b4cc 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/qr_scan/component/QrCodeAnalyzer.kt @@ -37,7 +37,7 @@ class QrCodeAnalyzer( override fun analyze(image: ImageProxy) { if (image.format in supportedImageFormats) { - val plane = image.planes.first(); + val plane = image.planes.first() val bytes = plane.buffer.toByteArray() val source = PlanarYUVLuminanceSource( bytes,