diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt new file mode 100644 index 00000000..637626c8 --- /dev/null +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt @@ -0,0 +1,108 @@ +package cash.z.ecc.android.sdk + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import cash.z.ecc.android.sdk.tool.WalletBirthdayTool +import cash.z.ecc.android.sdk.type.ZcashNetwork +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AssetTest { + @Test + @SmallTest + fun birthday_height_from_filename() { + assertEquals(123, WalletBirthdayTool.birthdayHeight("123.json")) + } + + @Test + @SmallTest + fun validate_mainnet_assets() { + val network = ZcashNetwork.Mainnet + val assets = listAssets(network) + + assertFilesExist(assets) + assertFilenames(assets) + assertFileContents(network, assets) + } + + @Test + @SmallTest + fun validate_testnet_assets() { + val network = ZcashNetwork.Testnet + val assets = listAssets(network) + + assertFilesExist(assets) + assertFilenames(assets) + assertFileContents(network, assets) + } + + private fun assertFilesExist(files: Array?) { + assertFalse(files.isNullOrEmpty()) + } + + private fun assertFilenames(files: Array?) { + files?.forEach { + val split = it.split('.') + assertEquals(2, split.size) + + val intString = split.first() + val extensionString = split.last() + + // Will throw exception if cannot be parsed + intString.toInt() + + assertEquals("json", extensionString) + } + } + + private fun assertFileContents(network: ZcashNetwork, files: Array?) { + files?.map { filename -> + val filePath = "${WalletBirthdayTool.birthdayDirectory(network)}/$filename" + ApplicationProvider.getApplicationContext().assets.open(filePath) + .use { inputSteam -> + inputSteam.bufferedReader().use { bufferedReader -> + val slurped = bufferedReader.readText() + + JsonFile(JSONObject(slurped), filename) + } + } + }?.forEach { + val jsonObject = it.jsonObject + assertTrue(jsonObject.has("network")) + assertTrue(jsonObject.has("height")) + assertTrue(jsonObject.has("hash")) + assertTrue(jsonObject.has("time")) + assertTrue(jsonObject.has("tree")) + + val expectedNetworkName = when (network) { + ZcashNetwork.Mainnet -> "main" + ZcashNetwork.Testnet -> "test" + } + assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network")) + + assertEquals( + "File: ${it.filename}", + WalletBirthdayTool.birthdayHeight(it.filename), + jsonObject.getInt("height") + ) + + // In the future, additional validation of the JSON can be added + } + } + + private data class JsonFile(val jsonObject: JSONObject, val filename: String) + + companion object { + fun listAssets(network: ZcashNetwork) = WalletBirthdayTool.listBirthdayDirectoryContents( + ApplicationProvider.getApplicationContext(), + WalletBirthdayTool.birthdayDirectory(network) + ) + } +} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/WalletBirthdayTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/WalletBirthdayTool.kt index 2b8860b9..76bc6c63 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/WalletBirthdayTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/WalletBirthdayTool.kt @@ -1,15 +1,16 @@ package cash.z.ecc.android.sdk.tool import android.content.Context +import androidx.annotation.VisibleForTesting import cash.z.ecc.android.sdk.exception.BirthdayException import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.type.WalletBirthday import cash.z.ecc.android.sdk.type.ZcashNetwork import com.google.gson.Gson import com.google.gson.stream.JsonReader +import java.io.IOException import java.io.InputStreamReader import java.util.Arrays -import java.util.Locale /** * Tool for loading checkpoints for the wallet, based on the height at which the wallet was born. @@ -17,7 +18,7 @@ import java.util.Locale * @param appContext needed for loading checkpoints from the app's assets directory. */ class WalletBirthdayTool(appContext: Context) { - val context = appContext.applicationContext + private val context = appContext.applicationContext /** * Load the nearest checkpoint to the given birthday height. If null is given, then this @@ -51,13 +52,23 @@ class WalletBirthdayTool(appContext: Context) { ) } + // TODO: This method performs disk IO; convert to suspending function + // Converting this to suspending will then propagate + @Throws(IOException::class) + internal fun listBirthdayDirectoryContents(context: Context, directory: String) = + context.assets.list(directory) + /** * Returns the directory within the assets folder where birthday data * (i.e. sapling trees for a given height) can be found. */ - private fun birthdayDirectory(network: ZcashNetwork): String { - return "saplingtree/${network.networkName.toLowerCase(Locale.US)}" - } + @VisibleForTesting + internal fun birthdayDirectory(network: ZcashNetwork) = + "saplingtree/${network.networkName.lowercase()}" + + internal fun birthdayHeight(fileName: String) = fileName.split('.').first().toInt() + + private fun Array.sortDescending() = apply { sortByDescending { birthdayHeight(it) } } /** * Load the given birthday file from the assets of the given context. When no height is @@ -76,16 +87,7 @@ class WalletBirthdayTool(appContext: Context) { ): WalletBirthday { twig("loading birthday from assets: $birthdayHeight") val directory = birthdayDirectory(network) - val treeFiles = - context.assets.list(directory)?.apply { - sortByDescending { fileName -> - try { - fileName.split('.').first().toInt() - } catch (t: Throwable) { - network.saplingActivationHeight - } - } - } + val treeFiles = listBirthdayDirectoryContents(context, directory)?.sortDescending() if (treeFiles.isNullOrEmpty()) throw BirthdayException.MissingBirthdayFilesException( directory ) @@ -94,7 +96,7 @@ class WalletBirthdayTool(appContext: Context) { try { file = if (birthdayHeight == null) treeFiles.first() else { treeFiles.first { - it.split(".").first().toInt() <= birthdayHeight + birthdayHeight(it) <= birthdayHeight } } } catch (t: Throwable) {