mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2025-05-12 21:20:42 +01:00
lib: add E2E test
This commit is contained in:
parent
46637358db
commit
ab28a03ce4
11 changed files with 279 additions and 8 deletions
|
@ -15,4 +15,5 @@ androidx-test-runner = { module = "androidx.test:runner", version.ref = "android
|
|||
androidx-test-truth = { module = "androidx.test.ext:truth", version.ref = "androidx-test-truth" }
|
||||
junit = { module = "junit:junit", version.ref = "junit" }
|
||||
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
||||
truth = { module = "com.google.truth:truth", version.ref = "truth" }
|
||||
|
|
|
@ -116,5 +116,5 @@ dependencies {
|
|||
androidTestImplementation(testLibs.androidx.test.truth)
|
||||
androidTestImplementation(testLibs.androidx.test.rules)
|
||||
androidTestImplementation(testLibs.androidx.test.runner)
|
||||
androidTestImplementation(testLibs.mockk)
|
||||
androidTestImplementation(testLibs.mockk.android)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package im.molly.monero
|
||||
|
||||
import com.google.common.truth.FailureMetadata
|
||||
import com.google.common.truth.Subject
|
||||
import com.google.common.truth.Truth.assertAbout
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
class MoneroWalletSubject private constructor(
|
||||
metadata: FailureMetadata,
|
||||
private val actual: MoneroWallet,
|
||||
) : Subject(metadata, actual) {
|
||||
|
||||
companion object {
|
||||
fun assertThat(wallet: MoneroWallet): MoneroWalletSubject {
|
||||
return assertAbout(factory).that(wallet)
|
||||
}
|
||||
|
||||
private val factory = Factory { metadata, actual: MoneroWallet ->
|
||||
MoneroWalletSubject(metadata, actual)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun matchesStateOf(expected: MoneroWallet) {
|
||||
with(actual) {
|
||||
check("publicAddress").that(publicAddress).isEqualTo(expected.publicAddress)
|
||||
check("ledger").that(ledger().first()).isEqualTo(expected.ledger().first())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import kotlin.random.Random
|
|||
class SecretKeyParcelableTest {
|
||||
|
||||
@Test
|
||||
fun testParcel() {
|
||||
fun secretKeyIsParcelable() {
|
||||
val secret = Random.nextBytes(32)
|
||||
val originalKey = SecretKey(secret)
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package im.molly.monero.e2etest
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.ServiceTestRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.molly.monero.InMemoryWalletDataStore
|
||||
import im.molly.monero.MoneroNetwork
|
||||
import im.molly.monero.MoneroWallet
|
||||
import im.molly.monero.MoneroWalletSubject
|
||||
import im.molly.monero.WalletProvider
|
||||
import im.molly.monero.internal.IWalletService
|
||||
import im.molly.monero.internal.WalletServiceClient
|
||||
import im.molly.monero.service.BaseWalletService
|
||||
import im.molly.monero.service.InProcessWalletService
|
||||
import im.molly.monero.service.SandboxedWalletService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@LargeTest
|
||||
//@RunWith(Parameterized::class)
|
||||
abstract class MoneroWalletTest(private val serviceClass: Class<out BaseWalletService>) {
|
||||
|
||||
@get:Rule
|
||||
val walletServiceRule = ServiceTestRule()
|
||||
|
||||
private lateinit var walletProvider: WalletProvider
|
||||
|
||||
private val context: Context by lazy { InstrumentationRegistry.getInstrumentation().context }
|
||||
|
||||
private fun bindService(): IWalletService {
|
||||
val binder = walletServiceRule.bindService(Intent(context, serviceClass))
|
||||
return IWalletService.Stub.asInterface(binder)
|
||||
}
|
||||
|
||||
private fun unbindService() {
|
||||
walletServiceRule.unbindService()
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val walletService = bindService()
|
||||
walletProvider = WalletServiceClient.withBoundService(context, walletService)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
walletProvider.disconnect()
|
||||
unbindService()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSaveToMultipleStores() = runTest {
|
||||
val defaultStore = InMemoryWalletDataStore()
|
||||
val wallet = walletProvider.createNewWallet(MoneroNetwork.Mainnet, defaultStore)
|
||||
wallet.save()
|
||||
|
||||
val newStore = InMemoryWalletDataStore()
|
||||
wallet.save(newStore)
|
||||
|
||||
assertThat(defaultStore.toByteArray()).isEqualTo(newStore.toByteArray())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateAccountAndSave() = runTest {
|
||||
val wallet = walletProvider.createNewWallet(MoneroNetwork.Mainnet)
|
||||
val newAccount = wallet.createAccount()
|
||||
|
||||
withReopenedWallet(wallet) { original, reopened ->
|
||||
MoneroWalletSubject.assertThat(reopened).matchesStateOf(original)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun withReopenedWallet(
|
||||
wallet: MoneroWallet,
|
||||
action: suspend (original: MoneroWallet, reopened: MoneroWallet) -> Unit,
|
||||
) {
|
||||
walletProvider.openWallet(
|
||||
network = wallet.network,
|
||||
dataStore = InMemoryWalletDataStore().apply {
|
||||
wallet.save(targetStore = this)
|
||||
},
|
||||
).use { reopened ->
|
||||
action(wallet, reopened)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MoneroWalletInProcessTest : MoneroWalletTest(InProcessWalletService::class.java)
|
||||
class MoneroWalletSandboxedTest : MoneroWalletTest(SandboxedWalletService::class.java)
|
|
@ -0,0 +1,116 @@
|
|||
package im.molly.monero.internal
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.molly.monero.WalletDataStore
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.junit4.MockKRule
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
class DataStoreAdapterTest {
|
||||
|
||||
@get:Rule
|
||||
val mockkRule = MockKRule(this)
|
||||
|
||||
private val dataStore = mockk<WalletDataStore>(relaxed = true)
|
||||
private val testDispatcher: TestDispatcher = StandardTestDispatcher()
|
||||
private val testIOException = IOException("Test IO Exception")
|
||||
|
||||
private lateinit var adapter: DataStoreAdapter
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
adapter = DataStoreAdapter(dataStore, ioDispatcher = testDispatcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun overwriteIsPassedToDataStore() = runTest(testDispatcher) {
|
||||
coEvery { dataStore.save(any(), any()) } returns Unit
|
||||
|
||||
listOf(true, false).forEach {
|
||||
adapter.saveWithFd(overwrite = it) {}
|
||||
coVerify(exactly = 1) { dataStore.save(any(), overwrite = it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagatesIOExceptionWhenLoadFails() = runTest(testDispatcher) {
|
||||
coEvery { dataStore.load() } throws testIOException
|
||||
|
||||
val exception = runCatching {
|
||||
adapter.loadWithFd {}
|
||||
}.exceptionOrNull()
|
||||
|
||||
assertThat(exception).isEqualTo(testIOException)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagatesIOExceptionWhenSaveFails() = runTest(testDispatcher) {
|
||||
coEvery { dataStore.save(any(), any()) } throws testIOException
|
||||
|
||||
val exception = runCatching {
|
||||
adapter.saveWithFd(overwrite = true) {}
|
||||
}.exceptionOrNull()
|
||||
|
||||
assertThat(exception).isEqualTo(testIOException)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pipeIsAlwaysClosedAfterLoad() = runTest(testDispatcher) {
|
||||
val (readFd, writeFd) = mockPipe()
|
||||
|
||||
coEvery { dataStore.load() } returns ByteArrayInputStream(byteArrayOf(1, 2, 3))
|
||||
|
||||
adapter.loadWithFd({})
|
||||
coVerify { readFd.close() }
|
||||
coVerify { writeFd.close() }
|
||||
|
||||
clearAllMocks(answers = false)
|
||||
|
||||
runCatching {
|
||||
adapter.loadWithFd({ throw RuntimeException() })
|
||||
}
|
||||
coVerify { readFd.close() }
|
||||
coVerify { writeFd.close() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pipeIsAlwaysClosedAfterSave() = runTest(testDispatcher) {
|
||||
val (readFd, writeFd) = mockPipe()
|
||||
|
||||
coEvery { dataStore.save(any(), any()) } returns Unit
|
||||
|
||||
adapter.saveWithFd(overwrite = true, {})
|
||||
coVerify { readFd.close() }
|
||||
coVerify { writeFd.close() }
|
||||
|
||||
clearAllMocks(answers = false)
|
||||
|
||||
runCatching {
|
||||
adapter.saveWithFd(overwrite = true, { throw RuntimeException() })
|
||||
}
|
||||
coVerify { readFd.close() }
|
||||
coVerify { writeFd.close() }
|
||||
}
|
||||
|
||||
private fun mockPipe(): Pair<ParcelFileDescriptor, ParcelFileDescriptor> {
|
||||
val readFd = mockk<ParcelFileDescriptor>(relaxed = true)
|
||||
val writeFd = mockk<ParcelFileDescriptor>(relaxed = true)
|
||||
|
||||
mockkStatic(ParcelFileDescriptor::class)
|
||||
coEvery { ParcelFileDescriptor.createPipe() } returns arrayOf(readFd, writeFd)
|
||||
|
||||
return readFd to writeFd
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ class NativeWalletTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun atGenesisBalanceIsZero() = runTest {
|
||||
fun balanceIsZeroAtGenesis() = runTest {
|
||||
with(
|
||||
NativeWallet.localSyncWallet(
|
||||
networkId = MoneroNetwork.Mainnet.id,
|
||||
|
|
|
@ -20,7 +20,7 @@ class MoneroMnemonicTest {
|
|||
)
|
||||
|
||||
@Test
|
||||
fun testKnownMnemonics() {
|
||||
fun knownMnemonics() {
|
||||
testCases.forEach {
|
||||
validateMnemonicGeneration(it)
|
||||
validateEntropyRecovery(it)
|
||||
|
@ -28,22 +28,22 @@ class MoneroMnemonicTest {
|
|||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testEmptyEntropy() {
|
||||
fun emptyEntropy() {
|
||||
MoneroMnemonic.generateMnemonic(ByteArray(0))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testInvalidEntropy() {
|
||||
fun invalidEntropy() {
|
||||
MoneroMnemonic.generateMnemonic(ByteArray(2))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testEmptyWords() {
|
||||
fun emptyWords() {
|
||||
MoneroMnemonic.recoverEntropy("")
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testInvalidLanguage() {
|
||||
fun invalidLanguage() {
|
||||
MoneroMnemonic.generateMnemonic(ByteArray(32), Locale("ZZ"))
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package im.molly.monero.service
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class WalletServiceSandboxingTest {
|
||||
|
||||
private val context: Context by lazy { InstrumentationRegistry.getInstrumentation().context }
|
||||
|
||||
@Test
|
||||
fun inProcessWalletServiceIsNotIsolated() = runTest {
|
||||
InProcessWalletService.connect(context).use { walletProvider ->
|
||||
assertThat(walletProvider.isServiceIsolated()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sandboxedWalletServiceIsIsolated() = runTest {
|
||||
SandboxedWalletService.connect(context).use { walletProvider ->
|
||||
assertThat(walletProvider.isServiceIsolated()).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ interface WalletProvider : Closeable {
|
|||
client: MoneroNodeClient? = null,
|
||||
): MoneroWallet
|
||||
|
||||
fun isServiceIsolated(): Boolean
|
||||
|
||||
fun disconnect()
|
||||
|
||||
override fun close() {
|
||||
|
|
|
@ -146,6 +146,8 @@ internal class WalletServiceClient(
|
|||
}
|
||||
}
|
||||
|
||||
override fun isServiceIsolated(): Boolean = service.isRemote()
|
||||
|
||||
override fun disconnect() {
|
||||
context.unbindService(serviceConnection ?: return)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue