Initial commit

This initial commit includes HUSH specific changes starting at this commit:
d14637012c
This commit is contained in:
fekt
2022-11-29 20:49:44 -05:00
commit 4adbc901a0
355 changed files with 31799 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
package cash.z.ecc.android
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.delay
import org.junit.Ignore
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@Ignore("It'd need additional implementation changes to have this one working.")
@RunWith(AndroidJUnit4::class)
// @RunWith(Parameterized::class)
class MemoTest(val input: String, val output: String) {
// @Test
// fun testExtractValidAddress() = runBlocking {
// val result = MemoUtil.findAddressInMemo(input, ::validateMemo)
// assertEquals(output, result)
// }
suspend fun validateMemo(memo: String): Boolean {
delay(20)
return true
}
companion object {
val validTaddr = "tmWGKMEpxSUf97H12MmGtgiER1drVbGjzWM"
val validZaddr = "ztestsapling1ukadr59p0hxcl2pq8mfagnfx3h74nsusdkm59gkys7hxze92whxj54mfdn3n37zusum7w4jlj35"
val invalidAddr = "ztestsaplinn9ukadr59p0hxcl2pq8mfagnfx3h74nsusdkm59gkys7hxze92whxj54mfdn3n37zusum7w4jlj35"
@JvmStatic
@Parameterized.Parameters
fun data() = listOf(
arrayOf(
"thanks for the food reply-to: $validZaddr",
validZaddr
),
arrayOf(
"thanks for the food reply-to: $validTaddr",
validTaddr
)
)
}
}

View File

@@ -0,0 +1,138 @@
package cash.z.ecc.android.integration
import androidx.test.ext.junit.runners.AndroidJUnit4
import cash.z.ecc.android.ext.WalletZecFormmatter
import cash.z.ecc.android.sdk.model.Zatoshi
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ConversionsTest {
@Test
fun testToZatoshi() {
val input = "1"
val result = WalletZecFormmatter.toZatoshi(input)
Assert.assertEquals(100_000_000L, result)
}
@Test
fun testToZecString_short() {
val input = Zatoshi(112_340_000L)
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.1234", result)
}
@Test
fun testToZecString_shortRoundUp() {
val input = Zatoshi(112_355_600L)
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.1236", result)
}
@Test
fun testToZecString_shortRoundDown() {
val input = Zatoshi(112_343_999L)
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.1234", result)
}
@Test
fun testToZecString_shortRoundHalfEven() {
val input = Zatoshi(112_345_000L)
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.1234", result)
}
@Test
fun testToZecString_shortRoundHalfOdd() {
val input = Zatoshi(112_355_000L)
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.1236", result)
}
@Test
fun testToBigDecimal_noCommas() {
val input = "1000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_thousandComma() {
val input = "1,000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_thousandCommaWithDecimal() {
val input = "1,000.00"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_oneDecimal() {
val input = "1.000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1, result.longValueExact())
}
@Test
fun testToBigDecimal_thousandWithThinSpace() {
val input = "1000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_oneWithThinSpace() {
val input = "1.000000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1, result.longValueExact())
}
@Test
fun testToBigDecimal_oneDecimalWithComma() {
val input = "1.000,00"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1, result.longValueExact())
}
@Test
fun testToZecString_full() {
val input = Zatoshi(112_341_123L)
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12341123", result)
}
@Test
fun testToZecString_fullRoundUp() {
val input = Zatoshi(112_355_678L)
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12355678", result)
}
@Test
fun testToZecString_fullRoundDown() {
val input = Zatoshi(112_349_999L)
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12349999", result)
}
@Test
fun testToZecString_fullRoundHalfEven() {
val input = Zatoshi(112_250_009L)
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12250009", result)
}
@Test
fun testToZecString_fullRoundHalfOdd() {
val input = Zatoshi(112_350_004L)
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12350004", result)
}
}

View File

@@ -0,0 +1,122 @@
package cash.z.ecc.android.integration
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.kotlin.mnemonic.Mnemonics
import kotlinx.coroutines.test.runTest
import okio.Buffer
import okio.GzipSink
import okio.buffer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class IntegrationTest {
private lateinit var appContext: Context
private val network = ZcashNetwork.Testnet
private val mnemonics = Mnemonics()
private val phrase =
"human pulse approve subway climb stairs mind gentle raccoon warfare fog roast sponsor" +
" under absorb spirit hurdle animal original honey owner upper empower describe"
@Before
fun start() {
appContext = InstrumentationRegistry.getInstrumentation().targetContext
}
@Test
fun testSeed_generation() {
val seed = mnemonics.toSeed(phrase.toCharArray())
assertEquals(
"Generated incorrect BIP-39 seed!",
"f4e3d38d9c244da7d0407e19a93c80429614ee82dcf62c141235751c9f1228905d12a1f275f" +
"5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe",
seed.toHex()
)
}
@Test
fun testSeed_storage() {
val seed = mnemonics.toSeed(phrase.toCharArray())
val lb = LockBox(appContext)
lb.setBytes("seed", seed)
assertTrue(seed.contentEquals(lb.getBytes("seed")!!))
}
@Test
fun testPhrase_storage() {
val lb = LockBox(appContext)
val phraseChars = phrase.toCharArray()
lb.setCharsUtf8("phrase", phraseChars)
assertTrue(phraseChars.contentEquals(lb.getCharsUtf8("phrase")!!))
}
@Test
fun testPhrase_maxLengthStorage() {
val lb = LockBox(appContext)
// find and expose the max length
var acceptedSize = 256
while (acceptedSize > 0) {
try {
lb.setCharsUtf8("temp", nextString(acceptedSize).toCharArray())
break
} catch (t: Throwable) {
}
acceptedSize--
}
val maxSeedPhraseLength = 8 * 24 + 23 // 215 (max length of each word is 8)
assertTrue(
"LockBox does not support the maximum length seed phrase." +
" Expected: $maxSeedPhraseLength but was: $acceptedSize",
acceptedSize > maxSeedPhraseLength
)
}
@Test
@Ignore("It'd need additional implementation changes to have this one working.")
fun testAddress() = runTest {
val seed = mnemonics.toSeed(phrase.toCharArray())
val initializer = Initializer.new(appContext) { config ->
// config.newWallet(seed, network)
}
assertEquals(
"Generated incorrect z-address!",
"zs1gn2ah0zqhsxnrqwuvwmgxpl5h3ha033qexhsz8tems53fw877f4gug353eefd6z8z3n4zxty65c",
// initializer.rustBackend.getShieldedAddress()
)
initializer.erase()
}
private fun ByteArray.toHex(): String {
val sb = StringBuilder(size * 2)
for (b in this)
sb.append(String.format("%02x", b))
return sb.toString()
}
fun String.gzip(): ByteArray {
val result = Buffer()
val sink = GzipSink(result).buffer()
sink.use {
sink.write(toByteArray())
}
return result.readByteArray()
}
fun nextString(length: Int): String {
val allowedChars = "ACGT"
return (1..length)
.map { allowedChars.random() }
.joinToString("")
}
}

View File

@@ -0,0 +1,73 @@
package cash.z.ecc.android.integration
import android.content.Context
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.lockbox.LockBox
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
class LockBoxTest {
lateinit var lockBox: LockBox
lateinit var appContext: Context
private val hex = ('a'..'f') + ('0'..'9')
private val iterations = 50
@Before
fun setUp() {
appContext = InstrumentationRegistry.getInstrumentation().targetContext
lockBox = LockBox(appContext)
lockBox.clear()
}
@Test
@LargeTest
@Ignore("This test is extremely slow")
fun testLongString() {
var successCount = 0
repeat(iterations) {
val sampleHex = List(500) { hex.random() }.joinToString("")
lockBox["longStr"] = sampleHex
val actual: String = lockBox["longStr"]!!
if (sampleHex == actual) successCount++
lockBox.clear()
}
assertEquals(iterations, successCount)
}
@Test
@LargeTest
@Ignore("This test is extremely slow")
fun testShortString() {
var successCount = 0
repeat(iterations) {
val sampleHex = List(50) { hex.random() }.joinToString("")
lockBox["shortStr"] = sampleHex
val actual: String = lockBox["shortStr"]!!
if (sampleHex == actual) successCount++
lockBox.clear()
}
assertEquals(iterations, successCount)
}
@Test
@LargeTest
@Ignore("This test is extremely slow")
fun testGiantString() {
var successCount = 0
repeat(iterations) {
val sampleHex = List(2500) { hex.random() }.joinToString("")
lockBox["giantStr"] = sampleHex
val actual: String = lockBox["giantStr"]!!
if (sampleHex == actual) successCount++
lockBox.clear()
}
assertEquals(iterations, successCount)
}
}

View File

@@ -0,0 +1,41 @@
package cash.z.ecc.android.preference
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import java.lang.reflect.Modifier
import kotlin.reflect.full.memberProperties
@RunWith(AndroidJUnit4::class)
class PreferenceKeysTest {
@SmallTest
@Test
@Throws(IllegalAccessException::class)
fun fields_public_static_and_final() {
PreferenceKeys::class.java.fields.forEach {
val modifiers = it.modifiers
assertThat(Modifier.isFinal(modifiers), equalTo(true))
assertThat(Modifier.isStatic(modifiers), equalTo(true))
assertThat(Modifier.isPublic(modifiers), equalTo(true))
}
}
// This test is primary to prevent copy-paste errors in preference keys
@SmallTest
@Test
fun key_values_unique() {
val fieldValueSet = mutableSetOf<String>()
PreferenceKeys::class.memberProperties
.map { it.getter.call() }
.map { it as String }
.forEach {
assertThat("Duplicate key $it", fieldValueSet.contains(it), equalTo(false))
fieldValueSet.add(it)
}
}
}

View File

@@ -0,0 +1,46 @@
package cash.z.ecc.android.preference
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import cash.z.ecc.android.preference.model.DefaultValue
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import java.lang.reflect.Modifier
import kotlin.reflect.full.memberProperties
@RunWith(AndroidJUnit4::class)
class PreferencesTest {
@SmallTest
@Test
@Throws(IllegalAccessException::class)
fun fields_public_static_and_final() {
Preferences::class.java.fields.forEach {
val modifiers = it.modifiers
assertThat(Modifier.isFinal(modifiers), equalTo(true))
assertThat(Modifier.isStatic(modifiers), equalTo(true))
assertThat(Modifier.isPublic(modifiers), equalTo(true))
}
}
// This test is primary to prevent copy-paste errors in preference keys
@SmallTest
@Test
fun key_values_unique() {
val fieldValueSet = mutableSetOf<String>()
Preferences::class.memberProperties
.map { it.getter.call(Preferences) }
.map { it as DefaultValue<*> }
.forEach {
assertThat(
"Duplicate key ${it.key}",
fieldValueSet.contains(it.key),
equalTo(false)
)
fieldValueSet.add(it.key)
}
}
}

View File

@@ -0,0 +1,32 @@
package cash.z.ecc.android.test
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.testing.FragmentScenario
import androidx.navigation.Navigation
import androidx.navigation.testing.TestNavHostController
import androidx.test.core.app.ApplicationProvider
data class FragmentNavigationScenario<T : Fragment>(
val fragmentScenario: FragmentScenario<T>,
val navigationController: TestNavHostController
) {
companion object {
fun <T : Fragment> new(
fragmentScenario: FragmentScenario<T>,
@IdRes currentDestination: Int
): FragmentNavigationScenario<T> {
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
fragmentScenario.onFragment {
navController.setGraph(cash.z.ecc.android.R.navigation.mobile_navigation)
navController.setCurrentDestination(currentDestination)
Navigation.setViewNavController(it.requireView(), navController)
}
return FragmentNavigationScenario(fragmentScenario, navController)
}
}
}

View File

@@ -0,0 +1,29 @@
package cash.z.ecc.android.test
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.os.PowerManager
import androidx.test.core.app.ApplicationProvider
import org.junit.Before
import java.lang.AssertionError
/**
* Subclass this for UI tests to ensure they run correctly. This helps when developers run tests
* against a physical device that might have gone to sleep.
*/
open class UiTestPrerequisites {
@Before
fun verifyScreenOn() {
if (!isScreenOn()) {
throw AssertionError("Screen must be on for UI tests to run") // $NON-NLS
}
}
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
private fun isScreenOn(): Boolean {
val powerService = ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
return powerService.isInteractive
}
}

View File

@@ -0,0 +1,154 @@
package cash.z.ecc.android.ui.home
import android.content.ComponentName
import android.content.Context
import androidx.fragment.app.testing.FragmentScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import cash.z.ecc.android.preference.Preferences
import cash.z.ecc.android.preference.SharedPreferenceFactory
import cash.z.ecc.android.preference.model.get
import cash.z.ecc.android.test.FragmentNavigationScenario
import cash.z.ecc.android.test.UiTestPrerequisites
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AutoshieldingInformationFragmentTest : UiTestPrerequisites() {
@Test
@MediumTest
fun dismiss_returns_home_when_autoshield_not_available() {
val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false)
onView(withId(cash.z.ecc.android.R.id.button_autoshield_dismiss)).also {
it.perform(ViewActions.click())
}
assertThat(
fragmentNavigationScenario.navigationController.currentDestination?.id,
equalTo(cash.z.ecc.android.R.id.nav_home)
)
}
@Test
@MediumTest
fun dismiss_starts_autoshield_when_autoshield_available() {
val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = true)
onView(withId(cash.z.ecc.android.R.id.button_autoshield_dismiss)).also {
it.perform(ViewActions.click())
}
assertThat(
fragmentNavigationScenario.navigationController.currentDestination?.id,
equalTo(cash.z.ecc.android.R.id.nav_shield_final)
)
}
@Test
@MediumTest
fun clicking_more_info_launches_browser() {
val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false)
onView(withId(cash.z.ecc.android.R.id.button_autoshield_more_info)).also {
it.perform(ViewActions.click())
}
assertThat(
fragmentNavigationScenario.navigationController.currentDestination?.id,
equalTo(cash.z.ecc.android.R.id.nav_autoshielding_info_details)
)
// Note: it is difficult to verify that the browser is launched, because of how the
// navigation component works.
}
@Test
@MediumTest
fun starting_fragment_does_not_launch_activities() {
Intents.init()
try {
val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false)
// The test framework launches an Activity to host the Fragment under test
// Since the class name is not a public API, this could break in the future with newer
// versions of the AndroidX Test libraries.
intended(
hasComponent(
ComponentName(
ApplicationProvider.getApplicationContext(),
"androidx.test.core.app.InstrumentationActivityInvoker\$BootstrapActivity"
)
)
)
// Verifying that no other Activities (e.g. the link view) are launched without explicit
// user interaction
Intents.assertNoUnverifiedIntents()
assertThat(
fragmentNavigationScenario.navigationController.currentDestination?.id,
equalTo(cash.z.ecc.android.R.id.nav_autoshielding_info)
)
} finally {
Intents.release()
}
}
@Test
@MediumTest
fun display_fragment_sets_preference() {
newScenario(isAutoshieldAvailable = false)
assertThat(
Preferences.isAcknowledgedAutoshieldingInformationPrompt.get(ApplicationProvider.getApplicationContext<Context>()),
equalTo(true)
)
}
@Test
@MediumTest
fun back_navigates_home() {
val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false)
fragmentNavigationScenario.fragmentScenario.onFragment {
// Probably closest we can come to simulating back with the navigation test framework
fragmentNavigationScenario.navigationController.navigateUp()
}
assertThat(
fragmentNavigationScenario.navigationController.currentDestination?.id,
equalTo(cash.z.ecc.android.R.id.nav_home)
)
}
companion object {
private fun newScenario(isAutoshieldAvailable: Boolean): FragmentNavigationScenario<AutoshieldingInformationFragment> {
// Clear preferences for each scenario, as this most closely reflects how this fragment
// is used in the app, as it is displayed usually on first launch
SharedPreferenceFactory.getSharedPreferences(ApplicationProvider.getApplicationContext())
.edit().clear().apply()
val scenario = FragmentScenario.launchInContainer(
AutoshieldingInformationFragment::class.java,
HomeFragmentDirections.actionNavHomeToAutoshieldingInfo(isAutoshieldAvailable).arguments,
cash.z.ecc.android.R.style.ZcashTheme,
null
)
return FragmentNavigationScenario.new(
scenario,
cash.z.ecc.android.R.id.nav_autoshielding_info
)
}
}
}