diff --git a/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt b/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt index 2cbb09b422..025c523ab1 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt @@ -4,6 +4,7 @@ object TestConstants { const val TOTAL_BALANCE = "$3,299.18" const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" + const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p" const val WAIT_UNTIL_TIMEOUT = 20_000L diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt index daace0f71a..2c2fd2e178 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt @@ -25,7 +25,7 @@ fun BaseTestCase.pullToRefresh() { direction = SwipeDirection.DOWN, startHeightRatio = 0.2f, endHeightRatio = 0.8f, - steps = 2000 + steps = 1000 ) } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SendConfirmPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SendConfirmPageObject.kt index 900ead781c..32772d416d 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SendConfirmPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SendConfirmPageObject.kt @@ -4,11 +4,14 @@ import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase import com.tangem.core.ui.R import com.tangem.core.ui.test.BaseButtonTestTags +import com.tangem.core.ui.test.NotificationTestTags import com.tangem.core.ui.test.TopAppBarTestTags import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText +import com.tangem.common.ui.R as CommonUiR class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { @@ -24,6 +27,41 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider useUnmergedTree = true } + + val minimumSendAmountErrorTitle: KNode = child { + hasTestTag(NotificationTestTags.TITLE) + hasText(getResourceString(CommonUiR.string.send_notification_invalid_amount_title)) + useUnmergedTree = true + } + + fun minimumSendAmountErrorIcon(amount: String): KNode = child { + hasAnySibling( + withText( + getResourceString( + CommonUiR.string.send_notification_invalid_minimum_amount_text, + amount, + amount, + ) + ) + ) + hasTestTag(NotificationTestTags.ICON) + useUnmergedTree = true + } + + fun minimumSendAmountErrorMessage( + amount: String, + ): KNode = child { + hasTestTag(NotificationTestTags.MESSAGE) + hasText( + getResourceString( + CommonUiR.string.send_notification_invalid_minimum_amount_text, + amount, + amount, + ) + ) + useUnmergedTree = true + } + } internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) = diff --git a/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt index 744837a668..1c3fa1fbb1 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt @@ -132,6 +132,18 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide useUnmergedTree = true } + val topUpYourWalletNotificationTitle: KNode = child { + hasTestTag(NotificationTestTags.TITLE) + hasText(getResourceString(R.string.warning_no_account_title)) + useUnmergedTree = true + } + + val topUpYourWalletNotificationIcon: KNode = child { + hasAnySibling(withText(getResourceString(R.string.warning_no_account_title))) + hasTestTag(NotificationTestTags.ICON) + useUnmergedTree = true + } + fun networkFeeNotificationMessage( currencyName: String, networkName: String, @@ -152,6 +164,23 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide useUnmergedTree = true } + fun topUpYourWalletNotificationMessage( + networkName: String, + amount: String, + currencySymbol: String, + ): KNode = child { + hasTestTag(NotificationTestTags.MESSAGE) + hasText( + getResourceString( + R.string.no_account_generic, + networkName, + amount, + currencySymbol, + ) + ) + useUnmergedTree = true + } + fun goToBuyCurrencyButton(feeCurrencySymbol: String): KNode = child { hasTestTag(BaseButtonTestTags.TEXT) hasText(getResourceString(R.string.common_buy_currency, feeCurrencySymbol)) diff --git a/app/src/androidTest/kotlin/com/tangem/tests/BlockchainTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/BlockchainTest.kt new file mode 100644 index 0000000000..9204686cf7 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/BlockchainTest.kt @@ -0,0 +1,209 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.extensions.pullToRefresh +import com.tangem.common.utils.resetWireMockScenarioState +import com.tangem.common.utils.setWireMockScenarioState +import com.tangem.scenarios.openMainScreen +import com.tangem.scenarios.synchronizeAddresses +import com.tangem.screens.* +import com.tangem.screens.onMainScreen +import com.tangem.screens.onSendAddressScreen +import com.tangem.screens.onSendScreen +import com.tangem.screens.onTokenDetailsScreen +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Test + +@HiltAndroidTest +class BlockchainTest : BaseTestCase() { + + @AllureId("3644") + @DisplayName("ADA: Checking the min amount/change = 1ADA") + @Test + fun adaCheckMinAmountTest() { + val tokenName = "Cardano" + val balance = "$0.00" + val errorSendAmount = "0.1" + val validSendAmount = "10" + val minAmount = "ADA 1.00" + val address = CARDANO_ADDRESS + val scenarioName = "user_tokens_api" + val scenarioState = "Cardano" + + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(scenarioName) + } + ).run { + step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") { + setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState) + } + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses(balance) + } + step("Click on token with name: '$tokenName'") { + onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() } + } + step("Click on 'Send' button") { + onTokenDetailsScreen { sendButton.performClick() } + } + step("Type '$errorSendAmount' in input text field") { + onSendScreen { + amountInputTextField.performClick() + amountInputTextField.performTextReplacement(errorSendAmount) + } + } + step("Click on 'Next' button") { + onSendScreen { nextButton.clickWithAssertion() } + } + step("Type address in input text field") { + onSendAddressScreen { addressTextField.performTextReplacement(address) } + } + step("Click on 'Next' button") { + onSendAddressScreen { nextButton.clickWithAssertion() } + } + step("Assert 'Invalid amount' error title is displayed") { + onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() } + } + step("Assert 'Invalid amount' error icon is displayed") { + onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() } + } + step("Assert 'Invalid amount' error message is displayed") { + onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsDisplayed() } + } + step("Press system 'Back' button") { + device.uiDevice.pressBack() + } + step("Assert address text field is displayed") { + onSendAddressScreen { addressTextField.assertIsDisplayed() } + } + step("Press system 'Back' button") { + device.uiDevice.pressBack() + } + step("Type '$validSendAmount' in input text field") { + onSendScreen { + amountInputTextField.performClick() + amountInputTextField.performTextReplacement(errorSendAmount) + } + } + step("Click on 'Next' button") { + onSendScreen { nextButton.clickWithAssertion() } + } + step("Assert address text field is displayed") { + onSendAddressScreen { addressTextField.assertIsDisplayed() } + } + step("Click on 'Next' button") { + onSendAddressScreen { nextButton.clickWithAssertion() } + } + step("Assert 'Invalid amount' error title is not displayed") { + onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsNotDisplayed() } + } + step("Assert 'Invalid amount' error icon is not displayed") { + onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsNotDisplayed() } + } + step("Assert 'Invalid amount' error message is not displayed") { + onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsNotDisplayed() } + } + } + } + + @AllureId("990") + @DisplayName("XRP: Check warning") + @Test + fun xrpCheckWarningTest() { + val tokenName = "XRP Ledger" + val amount = "1.00" + val currencySymbol = "XRP" + val balance = "$0.00" + val userTokensScenarioName = "user_tokens_api" + val userTokensScenarioState = "XRP" + val ripplePostServerStateScenarioName = "ripple_post_server_state_android" + val ripplePostServerStateScenarioState = "Started" + val rippleAccountInfoScenarioName = "ripple_account_info_android" + val rippleAccountInfoStartedState = "Started" + val rippleAccountInfoErrorState = "AccountNotFound" + val rippleAccountLinesScenarioName = "ripple_account_lines_android" + val rippleAccountLinesStartedState = "Started" + val rippleAccountLinesErrorState = "AccountNotFound" + + setupHooks( + additionalAfterSection = { + resetWireMockScenarioState(userTokensScenarioName) + resetWireMockScenarioState(ripplePostServerStateScenarioName) + resetWireMockScenarioState(rippleAccountInfoScenarioName) + resetWireMockScenarioState(rippleAccountLinesScenarioName) + } + ).run { + step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") { + setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState) + } + step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") { + setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoErrorState) + } + step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") { + setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState) + } + step("Open 'Main Screen'") { + openMainScreen() + } + step("Synchronize addresses") { + synchronizeAddresses(balance) + } + step("Click on token with name: '$tokenName'") { + onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() } + } + step("Assert 'Top up wallet' notification title is displayed") { + onTokenDetailsScreen { topUpYourWalletNotificationTitle.assertIsDisplayed() } + } + step("Assert 'Top up wallet' notification message is displayed") { + onTokenDetailsScreen { + topUpYourWalletNotificationMessage( + networkName = tokenName, + amount = amount, + currencySymbol = currencySymbol, + ).assertIsDisplayed() + } + } + step("Assert 'Top up wallet' notification icon is displayed") { + onTokenDetailsScreen { topUpYourWalletNotificationIcon.assertIsDisplayed() } + } + step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") { + setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState) + } + step("Set WireMock scenario: '$ripplePostServerStateScenarioName' to state: '$ripplePostServerStateScenarioState'") { + setWireMockScenarioState(scenarioName = ripplePostServerStateScenarioName, state = ripplePostServerStateScenarioState) + } + step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") { + setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoStartedState) + } + step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") { + setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState) + } + step("Pull to refresh") { + pullToRefresh() + } + step("Assert 'Top up wallet' notification title is not displayed") { + onTokenDetailsScreen { topUpYourWalletNotificationTitle.assertIsNotDisplayed() } + } + step("Assert 'Top up wallet' notification message not is displayed") { + onTokenDetailsScreen { + topUpYourWalletNotificationMessage( + networkName = tokenName, + amount = amount, + currencySymbol = currencySymbol, + ).assertIsNotDisplayed() + } + } + step("Assert 'Top up wallet' notification icon not is displayed") { + onTokenDetailsScreen { topUpYourWalletNotificationIcon.assertIsNotDisplayed() } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt index 1ce637b289..23afd7ef06 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt @@ -17,6 +17,7 @@ import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.domain.sdk.mocks.MockContent import java.util.Date +@Suppress("LargeClass") object WalletMockContent : MockContent { private val primaryCard = PrimaryCard( @@ -120,6 +121,14 @@ object WalletMockContent : MockContent { publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), ), + DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), ), extendedPublicKey = ExtendedPublicKey( publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), @@ -219,8 +228,38 @@ object WalletMockContent : MockContent { parentFingerprint = byteArrayOf(0, 0, 0, 0), childNumber = 0, ), + DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey( // XRP + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), ), ), + ByteArrayKey( + byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), ) @@ -257,6 +296,35 @@ object WalletMockContent : MockContent { parentFingerprint = byteArrayOf(0, 0, 0, 0), childNumber = 0, ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // xrp + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ByteArrayKey( + byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), ), ), ), diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/ProviderTypeIdMapping.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/ProviderTypeIdMapping.kt index 4bc0458651..4060529933 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/ProviderTypeIdMapping.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/ProviderTypeIdMapping.kt @@ -11,6 +11,7 @@ internal enum class ProviderTypeIdMapping(val id: String, val providerType: Prov BitcoinBlockcypher(id = "blockcypher", providerType = ProviderType.BitcoinLike.Blockcypher), CardanoAdalite(id = "adalite", providerType = ProviderType.Cardano.Adalite), CardanoRosetta(id = "tangemRosetta", providerType = ProviderType.Cardano.Rosetta), + CardanoMock(id = "mock", providerType = ProviderType.Mock), ChiaFireAcademy(id = "fireAcademy", providerType = ProviderType.Chia.FireAcademy), ChiaTangem(id = "tangemChia", providerType = ProviderType.Chia.Tangem), ChiaTangemNew(id = "tangemChia3", providerType = ProviderType.Chia.TangemNew),