Updated on 2026-08-14
This commit is contained in:
parent
9b0305efe9
commit
2453d63633
12 changed files with 702 additions and 5 deletions
|
|
@ -71,8 +71,9 @@ When the user asks to **port** an iOS test to Android:
|
|||
strings inside `step(...)`.
|
||||
- **Each click is its own** `step("Click on '$x' button")`. Combining clicks into one step hides which
|
||||
click failed in the Allure report.
|
||||
- **Step naming**: `Click on 'X' button` (not "Tap X"); `Assert <thing> is displayed` (not "Check X
|
||||
visible"). Keep it consistent with the existing suite.
|
||||
- **Step naming**: `Click on 'X' button` (not "Tap X"); `Assert <thing> is displayed` / `is not displayed`.
|
||||
Reviewers reject `is visible`, `does not exist`, `Check X visible` — the convention is **`is displayed` /
|
||||
`is not displayed`** even though older tests in the file may still use the old phrasing (don't copy it).
|
||||
- **No conditional `if (foo.isDisplayedSafely()) foo.performClick()`** for elements that are
|
||||
deterministically present after `pm clear` — the `if` is dead code. Use a straight `performClick()`.
|
||||
|
||||
|
|
@ -131,5 +132,6 @@ Delete anything explaining WHAT a step does.
|
|||
- **`reference/compose-traps.md`** — read when the screen uses `PullToRefreshBox`,
|
||||
`TangemHoldToConfirmButton`, a Decompose model that fetches in `init {}`, or a hot-wallet import with
|
||||
an access code. These have silent failure modes that look like passing tests.
|
||||
- **`reference/running-and-debugging.md`** — read when building, installing, running a single test,
|
||||
interpreting CLI/Allure output, using `@Ignore`, or driving WireMock scenarios.
|
||||
- **`reference/running-and-debugging.md`** — read when building, installing, running tests (orchestrator
|
||||
vs. raw `am instrument`), running against a local WireMock, interpreting CLI/Allure output, using
|
||||
`@Ignore`, or driving WireMock scenarios.
|
||||
|
|
@ -41,6 +41,30 @@ so the hold gesture is silently swallowed: the button looks fine, the user holds
|
|||
3. Snapshot again — byte-identical trees mean `onConfirm` didn't run.
|
||||
4. Or check WireMock request stats for the downstream API call expected after `onConfirm`.
|
||||
|
||||
## Asserting enabled/disabled on a `Modifier.clickable` row
|
||||
|
||||
When a settings/list row puts `Modifier.clickable(enabled = isClickable, ...)` on the row **container**
|
||||
(not the title `Text`), the enabled/disabled state lives on that container; the child Texts only carry
|
||||
`testTag`/text. So `assertIsEnabled()` / `assertIsNotEnabled()` must target the container, matched by a
|
||||
descendant text — not the title node itself.
|
||||
|
||||
Match the container in BOTH states with **click-action OR disabled-semantics**. Do NOT rely on
|
||||
`hasClickAction()` alone: depending on the Compose version a `clickable(enabled = false)` row may not
|
||||
expose an onClick action, so a `hasClickAction()`-only matcher finds no node and `assertIsNotEnabled()`
|
||||
fails with "No node found".
|
||||
|
||||
```kotlin
|
||||
import androidx.compose.ui.test.hasClickAction as withClickAction
|
||||
import androidx.compose.ui.test.isNotEnabled as withDisabled
|
||||
|
||||
val row: KNode = child {
|
||||
addSemanticsMatcher(withClickAction() or withDisabled()) // matches enabled AND disabled rows
|
||||
hasAnyDescendant(withText(getResourceString(R.string.row_title))) // narrows to the specific row
|
||||
useUnmergedTree = true
|
||||
}
|
||||
// enabled card: row.assertIsEnabled() ; disabled card: row.assertIsNotEnabled()
|
||||
```
|
||||
|
||||
## `assertTextContains(x)` defaults to exact-segment match, not substring
|
||||
|
||||
`SemanticsNodeInteraction.assertTextContains(value, substring = false, ignoreCase = false)` defaults to
|
||||
|
|
|
|||
|
|
@ -29,6 +29,77 @@ adb shell am instrument -w \
|
|||
com.tangem.wallet.mocked.test/com.tangem.common.HiltTestRunner
|
||||
```
|
||||
|
||||
## Harness: orchestrator vs. raw `am instrument`
|
||||
|
||||
The app is configured `execution = "ANDROIDX_TEST_ORCHESTRATOR"` (`app/build.gradle.kts`). The orchestrator
|
||||
runs **each test method in its own process** (and can clear app data between them). It is still 100%
|
||||
local — it runs on the same emulator; nothing remote about it.
|
||||
|
||||
Raw `adb shell am instrument` runs **all selected tests in one shared process**, which has two failure
|
||||
modes that look like test bugs but aren't:
|
||||
|
||||
- Running several tests in one invocation → `IllegalStateException: There are multiple DataStores active
|
||||
for the same file` mid-run. Run them one at a time (with `pm clear` between) if you must use raw
|
||||
`am instrument`.
|
||||
- Tests that re-scan the card inside **Card/Device Settings** (the "Scan card or ring" gate) →
|
||||
`IllegalStateException: Tangem SDK is null after re-registering with foreground activity`. The existing
|
||||
`ResetCardTest` crashes identically under raw `am instrument`. These only pass via the orchestrator.
|
||||
|
||||
**Prefer the orchestrator** (it's what CI/Marathon use). Run a class or method through Gradle:
|
||||
|
||||
```bash
|
||||
./gradlew :app:connectedGoogleMockedAndroidTest \
|
||||
-Pandroid.testInstrumentationRunnerArguments.class=com.tangem.tests.DetailsTest
|
||||
# or a single method: ...class=com.tangem.tests.DetailsTest#someTest
|
||||
# or several classes: ...class=com.tangem.tests.DetailsTest,com.tangem.tests.SecurityModeTest
|
||||
```
|
||||
|
||||
Gradle installs both APKs, runs via the orchestrator, then **uninstalls them** — so a following raw
|
||||
`am instrument` reports `Unable to find instrumentation info`; reinstall both APKs first. Read results
|
||||
from the JUnit XML (authoritative pass/fail counts), not just stdout:
|
||||
|
||||
```bash
|
||||
ls -t app/build/outputs/androidTest-results/connected/mocked/flavors/google/*.xml | head -1
|
||||
# inspect tests="…" failures="…" errors="…" skipped="…" and the <testcase>/<failure> nodes
|
||||
```
|
||||
|
||||
## Running against local WireMock
|
||||
|
||||
Every instrumentation test runs with `ApiEnvironment.MOCK` (forced in `BaseTestCase.setupHooks`), so the
|
||||
app's API base URLs point at `wiremock.tests-d.com` — i.e. tests **always** talk to WireMock, never the
|
||||
real backend. By default that's the **remote** WireMock at `wiremock.tests-d.com`. To use a **local**
|
||||
WireMock instead, pass `wiremockBaseUrl`: `WireMockRedirectInterceptor` then rewrites every
|
||||
`wiremock.tests-d.com` request to your local instance.
|
||||
|
||||
Emulator addressing matters — `localhost` inside an emulator is the **emulator itself**, not your host:
|
||||
|
||||
- Use the host alias **`http://10.0.2.2:8081`** (no extra setup), **or**
|
||||
- `http://localhost:8081` **with** `adb reverse tcp:8081 tcp:8081` run first.
|
||||
|
||||
Pass it through the orchestrator (recommended):
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:8081/__admin/scenarios/reset # start clean
|
||||
./gradlew :app:connectedGoogleMockedAndroidTest \
|
||||
-Pandroid.testInstrumentationRunnerArguments.class=com.tangem.tests.DetailsTest \
|
||||
-Pandroid.testInstrumentationRunnerArguments.wiremockBaseUrl=http://10.0.2.2:8081
|
||||
```
|
||||
|
||||
(Raw `am instrument` equivalent: `-e wiremockBaseUrl http://10.0.2.2:8081` — subject to the harness
|
||||
caveats above.)
|
||||
|
||||
**If a screen hangs / you get `ComposeNotIdleException` (infinite recomposition):** that usually means a
|
||||
request the app made wasn't served (endless retry/loading), *not* a test bug. Ask WireMock what it
|
||||
didn't match — this is the smoking gun:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8081/__admin/requests/unmatched | jq '.requests[] | "\(.method) \(.url)"'
|
||||
```
|
||||
|
||||
`unmatched: 0` means the URL plumbing is correct and local WireMock served everything — look elsewhere
|
||||
(harness/emulator) for the hang. A non-empty list names exactly which mapping (or scenario state) the
|
||||
local instance is missing.
|
||||
|
||||
## Classify the result — Allure noise vs. real failure
|
||||
|
||||
After `pm clear`, `/data/user/0/<pkg>/files/original_screenshots` doesn't exist →
|
||||
|
|
@ -51,7 +122,8 @@ Distinguish:
|
|||
|
||||
## WireMock cheatsheet
|
||||
|
||||
Local override is detected; otherwise hits remote. Default local port: `8081`.
|
||||
Without a `wiremockBaseUrl` arg the app hits the **remote** WireMock (`wiremock.tests-d.com`); pass the
|
||||
arg to redirect to a local instance (see "Running against local WireMock"). Default local port: `8081`.
|
||||
|
||||
```bash
|
||||
# Set a scenario state — PUT, not POST
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import com.tangem.common.extensions.clickWithAssertion
|
|||
import com.tangem.screens.onDeviceSettingsScreen
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.scanCardInDeviceSettings() {
|
||||
step("Click on 'Scan card or ring' button") {
|
||||
onDeviceSettingsScreen { scanCardOrRingButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openResetCardScreen(withBackup: Boolean = false) {
|
||||
step("Click on 'Scan card or ring' button") {
|
||||
onDeviceSettingsScreen { scanCardOrRingButton.clickWithAssertion() }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
|
|||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasClickAction as withClickAction
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import androidx.compose.ui.test.isNotEnabled as withDisabled
|
||||
|
||||
class DeviceSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DeviceSettingsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
|
@ -46,6 +49,19 @@ class DeviceSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val securityModeRowTitle: KNode = child {
|
||||
hasTestTag(DeviceSettingsScreenTestTags.ITEM_TITLE)
|
||||
hasText(getResourceString(R.string.card_settings_security_mode))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
// Match the row container (not the title Text): enabled exposes a click action, disabled exposes disabled semantics.
|
||||
val securityModeRow: KNode = child {
|
||||
addSemanticsMatcher(withClickAction() or withDisabled())
|
||||
hasAnyDescendant(withText(getResourceString(R.string.card_settings_security_mode)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun resetToFactorySettingsButtonSubtitle(withBackup: Boolean = false): KNode = child {
|
||||
hasTestTag(DeviceSettingsScreenTestTags.ITEM_SUBTITLE)
|
||||
useUnmergedTree = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.wallet.R
|
||||
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
|
||||
|
||||
class SecurityModePageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SecurityModePageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
// Description only appears on the Security Mode screen — unambiguous "screen opened" signal.
|
||||
val longTapOptionDescription: KNode = child {
|
||||
hasText(getResourceString(R.string.details_manage_security_long_tap_description))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val saveChangesButton: KNode = child {
|
||||
hasText(getResourceString(R.string.common_save_changes))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSecurityModeScreen(function: SecurityModePageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -6,9 +6,13 @@ import com.tangem.domain.models.scan.ProductType
|
|||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.domain.sdk.mocks.content.Firmware412MockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.S2CMockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.SingleCurrencyMockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.V3MockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
|
|
@ -183,6 +187,144 @@ class DetailsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
|
||||
@AllureId("838")
|
||||
@DisplayName("Details: (v3 multicurrency) fields")
|
||||
@Test
|
||||
fun v3MultiCurrencyDetailsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(mockContent = V3MockContent)
|
||||
}
|
||||
onMainScreenTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is displayed") {
|
||||
walletConnectButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Buy Tangem card' button is displayed") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'App settings' button is displayed") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Contact support' button is displayed") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Terms of service' button is displayed") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert app version is displayed") {
|
||||
versionName.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9832")
|
||||
@DisplayName("Details: (single currency) fields")
|
||||
@Test
|
||||
fun singleCurrencyDetailsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(mockContent = SingleCurrencyMockContent)
|
||||
}
|
||||
onMainScreenTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is not displayed") {
|
||||
walletConnectButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert 'Buy Tangem card' button is displayed") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'App settings' button is displayed") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Contact support' button is displayed") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Terms of service' button is displayed") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert app version is displayed") {
|
||||
versionName.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("841")
|
||||
@DisplayName("Details: (S2C) fields")
|
||||
@Test
|
||||
fun s2cDetailsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(mockContent = S2CMockContent)
|
||||
}
|
||||
onMainScreenTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is not displayed") {
|
||||
walletConnectButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert 'Buy Tangem card' button is displayed") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'App settings' button is displayed") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Contact support' button is displayed") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Terms of service' button is displayed") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert app version is displayed") {
|
||||
versionName.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parked: createWalletActions adds Sell for single-wallet cards with no isStart2Coin() check.
|
||||
@Ignore("[REDACTED_JIRA]")
|
||||
@AllureId("2869")
|
||||
@DisplayName("Details: (S2C) no trade buttons and standard details")
|
||||
@Test
|
||||
fun s2cNoTradeButtonsDetailsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(mockContent = S2CMockContent)
|
||||
}
|
||||
onMainScreen {
|
||||
step("Assert 'Buy' button is not displayed") {
|
||||
buyButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert 'Sell' button is not displayed") {
|
||||
sellButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert 'Swap' button is not displayed") {
|
||||
swapButton.assertIsNotDisplayed()
|
||||
}
|
||||
}
|
||||
onMainScreenTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is not displayed") {
|
||||
walletConnectButton.assertIsNotDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3647")
|
||||
@DisplayName("Referral program: validate screen")
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.openDeviceSettingsScreen
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.scanCardInDeviceSettings
|
||||
import com.tangem.screens.*
|
||||
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 SecurityModeTest : BaseTestCase() {
|
||||
|
||||
@AllureId("2267")
|
||||
@DisplayName("Security Mode: available for Twin cards")
|
||||
@Test
|
||||
fun securityModeOpensForTwinsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(productType = ProductType.Twins, isTwinsCard = true)
|
||||
}
|
||||
step("Open 'Device settings' screen") {
|
||||
openDeviceSettingsScreen()
|
||||
}
|
||||
step("Scan card in 'Device settings'") {
|
||||
scanCardInDeviceSettings()
|
||||
}
|
||||
step("Assert 'Security mode' row is enabled") {
|
||||
onDeviceSettingsScreen { securityModeRow.assertIsEnabled() }
|
||||
}
|
||||
step("Click on 'Security mode' button") {
|
||||
onDeviceSettingsScreen { securityModeRow.performClick() }
|
||||
}
|
||||
onSecurityModeScreen {
|
||||
step("Assert 'Long tap' option is displayed") {
|
||||
longTapOptionDescription.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Save changes' button is displayed") {
|
||||
saveChangesButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("9831")
|
||||
@DisplayName("Security Mode: unavailable for single-capability cards")
|
||||
@Test
|
||||
fun securityModeRowDisabledForOtherCardsTest() =
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Open 'Device settings' screen") {
|
||||
openDeviceSettingsScreen()
|
||||
}
|
||||
step("Scan card in 'Device settings'") {
|
||||
scanCardInDeviceSettings()
|
||||
}
|
||||
step("Assert 'Security mode' row title is displayed") {
|
||||
onDeviceSettingsScreen { securityModeRowTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Security mode' row is disabled") {
|
||||
onDeviceSettingsScreen { securityModeRow.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,9 @@ object MockProvider {
|
|||
MockOption("Backup Wallet") { BackupWalletMockContent },
|
||||
MockOption("Dev Wallet") { DevWalletMockContent },
|
||||
MockOption("Firmware 4.12") { Firmware412MockContent },
|
||||
MockOption("V3 Multicurrency") { V3MockContent },
|
||||
MockOption("Single Currency") { SingleCurrencyMockContent },
|
||||
MockOption("Start2Coin") { S2CMockContent },
|
||||
MockOption("Cobrand") { showCobrandConfigDialog(it) },
|
||||
)
|
||||
|
||||
|
|
@ -99,6 +102,7 @@ object MockProvider {
|
|||
ProductType.Note -> NoteMockContent
|
||||
ProductType.Ring -> RingMockContent
|
||||
ProductType.Twins -> TwinsMockContent
|
||||
ProductType.Start2Coin -> S2CMockContent
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.*
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import java.util.Date
|
||||
|
||||
// Start2Coin (S2C): issuer "Start2Coin" trips isStart2Coin → single currency, WalletConnect hidden.
|
||||
object S2CMockContent : MockContent {
|
||||
|
||||
override val cardDto = CardDTO(
|
||||
cardId = "1198724260000000",
|
||||
batchId = "CD04",
|
||||
cardPublicKey = byteArrayOf(2, 102, 3, -106, -14, -87, -118, 120, 10, 93, 17, 55, 26, -44, 5, 115, 88, 35, 49, -88, -69, 116, 0, -72, -27, 57, 50, -55, 80, -16, 39, -70, 119),
|
||||
firmwareVersion = CardDTO.FirmwareVersion(
|
||||
major = 4,
|
||||
minor = 52,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Release,
|
||||
),
|
||||
manufacturer = CardDTO.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1671494400000),
|
||||
signature = byteArrayOf(),
|
||||
),
|
||||
issuer = CardDTO.Issuer(
|
||||
name = "Start2Coin",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
settings = CardDTO.Settings(
|
||||
securityDelay = 15000,
|
||||
maxWalletsCount = 1,
|
||||
isSettingAccessCodeAllowed = false,
|
||||
isSettingPasscodeAllowed = false,
|
||||
isResettingUserCodesAllowed = true,
|
||||
isLinkedTerminalEnabled = true,
|
||||
isBackupAllowed = false,
|
||||
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||
isFilesAllowed = false,
|
||||
isHDWalletAllowed = false,
|
||||
isKeysImportAllowed = false,
|
||||
),
|
||||
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
|
||||
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||
isAccessCodeSet = false,
|
||||
isPasscodeSet = false,
|
||||
supportedCurves = listOf(EllipticCurve.Secp256k1),
|
||||
wallets = listOf(
|
||||
CardDTO.Wallet(
|
||||
publicKey = byteArrayOf(2, 106, 7, -77, -109, 39, 3, 80, 99, 31, 50, -40, -113, -81, -76, -21, 123, -60, 0, -121, -56, 126, 2, 123, 111, 80, 47, -37, 40, 119, -22, 33, 32),
|
||||
chainCode = byteArrayOf(),
|
||||
curve = EllipticCurve.Secp256k1,
|
||||
settings = CardWallet.Settings(isPermanent = true),
|
||||
totalSignedHashes = 1,
|
||||
remainingSignatures = 999999,
|
||||
index = 0,
|
||||
hasBackup = false,
|
||||
derivedKeys = emptyMap(),
|
||||
extendedPublicKey = null,
|
||||
isImported = false,
|
||||
),
|
||||
),
|
||||
attestation = Attestation(
|
||||
cardKeyAttestation = Attestation.Status.Verified,
|
||||
walletKeysAttestation = Attestation.Status.Skipped,
|
||||
firmwareAttestation = Attestation.Status.Skipped,
|
||||
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||
),
|
||||
backupStatus = CardDTO.BackupStatus.NoBackup,
|
||||
)
|
||||
|
||||
override val scanResponse = ScanResponse(
|
||||
card = cardDto,
|
||||
productType = ProductType.Start2Coin,
|
||||
walletData = WalletData(blockchain = "BTC", token = null),
|
||||
secondTwinPublicKey = null,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val derivationTaskResponse = DerivationTaskResponse(entries = emptyMap())
|
||||
|
||||
override val extendedPublicKey
|
||||
get() = error("Available only for wallet+?")
|
||||
|
||||
override val successResponse = SuccessResponse(cardId = "1198724260000000")
|
||||
|
||||
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||
card = cardDto,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||
get() = error("Available only for Wallet 2")
|
||||
|
||||
override val createFirstTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val createSecondTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val finalizeTwinResponse: ScanResponse
|
||||
get() = error("Available only for Twin")
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.*
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import java.util.Date
|
||||
|
||||
// Single-currency card (XLM/ed25519, pre-4.0 firmware) → isMultiwalletAllowed false → WalletConnect hidden.
|
||||
object SingleCurrencyMockContent : MockContent {
|
||||
|
||||
override val cardDto = CardDTO(
|
||||
cardId = "0052000000000000",
|
||||
batchId = "0052",
|
||||
cardPublicKey = byteArrayOf(2, 102, 3, -106, -14, -87, -118, 120, 10, 93, 17, 55, 26, -44, 5, 115, 88, 35, 49, -88, -69, 116, 0, -72, -27, 57, 50, -55, 80, -16, 39, -70, 119),
|
||||
firmwareVersion = CardDTO.FirmwareVersion(
|
||||
major = 3,
|
||||
minor = 5,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Release,
|
||||
),
|
||||
manufacturer = CardDTO.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1649635200000),
|
||||
signature = byteArrayOf(),
|
||||
),
|
||||
issuer = CardDTO.Issuer(
|
||||
name = "TANGEM AG",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
settings = CardDTO.Settings(
|
||||
securityDelay = 15000,
|
||||
maxWalletsCount = 1,
|
||||
isSettingAccessCodeAllowed = false,
|
||||
isSettingPasscodeAllowed = false,
|
||||
isResettingUserCodesAllowed = true,
|
||||
isLinkedTerminalEnabled = true,
|
||||
isBackupAllowed = false,
|
||||
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||
isFilesAllowed = false,
|
||||
isHDWalletAllowed = false,
|
||||
isKeysImportAllowed = false,
|
||||
),
|
||||
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = false),
|
||||
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||
isAccessCodeSet = false,
|
||||
isPasscodeSet = false,
|
||||
supportedCurves = listOf(EllipticCurve.Ed25519),
|
||||
wallets = listOf(
|
||||
CardDTO.Wallet(
|
||||
publicKey = 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),
|
||||
chainCode = byteArrayOf(),
|
||||
curve = EllipticCurve.Ed25519,
|
||||
settings = CardWallet.Settings(isPermanent = false),
|
||||
totalSignedHashes = 1,
|
||||
remainingSignatures = null,
|
||||
index = 0,
|
||||
hasBackup = false,
|
||||
derivedKeys = emptyMap(),
|
||||
extendedPublicKey = null,
|
||||
isImported = false,
|
||||
),
|
||||
),
|
||||
attestation = Attestation(
|
||||
cardKeyAttestation = Attestation.Status.Verified,
|
||||
walletKeysAttestation = Attestation.Status.Skipped,
|
||||
firmwareAttestation = Attestation.Status.Skipped,
|
||||
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||
),
|
||||
backupStatus = CardDTO.BackupStatus.NoBackup,
|
||||
)
|
||||
|
||||
override val scanResponse = ScanResponse(
|
||||
card = cardDto,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = WalletData(blockchain = "XLM", token = null),
|
||||
secondTwinPublicKey = null,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val derivationTaskResponse = DerivationTaskResponse(entries = emptyMap())
|
||||
|
||||
override val extendedPublicKey
|
||||
get() = error("Available only for wallet+?")
|
||||
|
||||
override val successResponse = SuccessResponse(cardId = "0052000000000000")
|
||||
|
||||
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||
card = cardDto,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||
get() = error("Available only for Wallet 2")
|
||||
|
||||
override val createFirstTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val createSecondTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val finalizeTwinResponse: ScanResponse
|
||||
get() = error("Available only for Twin")
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.*
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import java.util.Date
|
||||
|
||||
// v3 multicurrency card: single secp256k1 wallet on pre-4.0 firmware → isMultiwalletAllowed via the secp branch.
|
||||
object V3MockContent : MockContent {
|
||||
|
||||
override val cardDto = CardDTO(
|
||||
cardId = "0045000000000000",
|
||||
batchId = "0045",
|
||||
cardPublicKey = byteArrayOf(2, 102, 3, -106, -14, -87, -118, 120, 10, 93, 17, 55, 26, -44, 5, 115, 88, 35, 49, -88, -69, 116, 0, -72, -27, 57, 50, -55, 80, -16, 39, -70, 119),
|
||||
firmwareVersion = CardDTO.FirmwareVersion(
|
||||
major = 3,
|
||||
minor = 5,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Release,
|
||||
),
|
||||
manufacturer = CardDTO.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1649635200000),
|
||||
signature = byteArrayOf(),
|
||||
),
|
||||
issuer = CardDTO.Issuer(
|
||||
name = "TANGEM AG",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
settings = CardDTO.Settings(
|
||||
securityDelay = 15000,
|
||||
maxWalletsCount = 1,
|
||||
isSettingAccessCodeAllowed = false,
|
||||
isSettingPasscodeAllowed = false,
|
||||
isResettingUserCodesAllowed = true,
|
||||
isLinkedTerminalEnabled = true,
|
||||
isBackupAllowed = false,
|
||||
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||
isFilesAllowed = false,
|
||||
isHDWalletAllowed = false,
|
||||
isKeysImportAllowed = false,
|
||||
),
|
||||
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = false),
|
||||
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||
isAccessCodeSet = false,
|
||||
isPasscodeSet = false,
|
||||
supportedCurves = listOf(EllipticCurve.Secp256k1),
|
||||
wallets = listOf(
|
||||
CardDTO.Wallet(
|
||||
publicKey = byteArrayOf(2, -27, -117, 23, 68, -3, 21, -109, 18, -67, -107, -42, -44, -16, -127, -53, 46, -109, -46, -51, 89, 119, 79, 111, 78, 62, -125, 72, 109, 8, 45, 59, 117),
|
||||
chainCode = byteArrayOf(),
|
||||
curve = EllipticCurve.Secp256k1,
|
||||
settings = CardWallet.Settings(isPermanent = false),
|
||||
totalSignedHashes = 1,
|
||||
remainingSignatures = null,
|
||||
index = 0,
|
||||
hasBackup = false,
|
||||
derivedKeys = emptyMap(),
|
||||
extendedPublicKey = null,
|
||||
isImported = false,
|
||||
),
|
||||
),
|
||||
attestation = Attestation(
|
||||
cardKeyAttestation = Attestation.Status.Verified,
|
||||
walletKeysAttestation = Attestation.Status.Skipped,
|
||||
firmwareAttestation = Attestation.Status.Skipped,
|
||||
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||
),
|
||||
backupStatus = CardDTO.BackupStatus.NoBackup,
|
||||
)
|
||||
|
||||
override val scanResponse = ScanResponse(
|
||||
card = cardDto,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = WalletData(blockchain = "BTC", token = null),
|
||||
secondTwinPublicKey = null,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val derivationTaskResponse = DerivationTaskResponse(entries = emptyMap())
|
||||
|
||||
override val extendedPublicKey
|
||||
get() = error("Available only for wallet+?")
|
||||
|
||||
override val successResponse = SuccessResponse(cardId = "0045000000000000")
|
||||
|
||||
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||
card = cardDto,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||
get() = error("Available only for Wallet 2")
|
||||
|
||||
override val createFirstTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val createSecondTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val finalizeTwinResponse: ScanResponse
|
||||
get() = error("Available only for Twin")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue