Skip to content

Commit

Permalink
Testing Refactor (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
Funkatronics authored Oct 17, 2022
1 parent b50a976 commit cae532f
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ jobs:
run: npx amman start

- name: Run Tests
run: ./gradlew test
run: ./gradlew :lib:testDebugUnitTest -PlocalValidator=true
48 changes: 39 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,12 +432,12 @@ The current identity of a `Metaplex` instance can be accessed via `metaplex.iden

This method returns an identity object with the following interface. All the methods require a Solana API instance.

```ts
public protocol IdentityDriver {
var publicKey: PublicKey { get }
func sendTransaction(serializedTransaction: String, onComplete: @escaping(Result<TransactionID, IdentityDriverError>) -> Void)
func signTransaction(transaction: Transaction, onComplete: @escaping (Result<Transaction, IdentityDriverError>) -> Void)
func signAllTransactions(transactions: [Transaction], onComplete: @escaping (Result<[Transaction?], IdentityDriverError>) -> Void)
```kotlin
interface IdentityDriver {
val publicKey: PublicKey
fun sendTransaction(transaction: Transaction, recentBlockHash: String? = null, onComplete: ((Result<String>) -> Unit))
fun signTransaction(transaction: Transaction, onComplete: (Result<Transaction>) -> Unit)
fun signAllTransactions(transactions: List<Transaction>, onComplete: (Result<List<Transaction?>>) -> Unit)
}
```

Expand All @@ -463,9 +463,9 @@ The `KeypairIdentityDriver` driver accepts a `PublicKey` object as a parameter.

You may access the current storage driver using `metaplex.storage()`, which will give you access to the following interface.

```swift
public protocol StorageDriver {
func download(url: URL, onComplete: @escaping(Result<NetworkingResponse, StorageDriverError>) -> Void)
```kotlin
interface StorageDriver {
suspend fun download(url: URL): Result<NetworkingResponse>
}
```

Expand All @@ -485,6 +485,36 @@ As mentioned above, this SDK is still in very early stages. We plan to add a lot
- Upload, Create NFTs to match JS-Next SDK.
- More documentation, tutorials, starter kits, etc.

## Development
### Local Testing & Custom RPCs
By default, the library tests will run against the [Solana devenet RPC endpoint](https://api.devnet.solana.com). Testing in this configuration can be problematic due to rate limiting, and it is therefore recommended to use your own RPC endpoint. This can be either a local validator node, or a dedicated RPC provider.

To specify your own RPC endpoint to be used for testing, you can add a gradle property with the name `rpcUrl` to the project/configuration. The test suite will attempt to use the value of `rpcUrl` if it is present.

For example, to run all tests using a custom RPC endpoint from the command line:
```
./gradlew test -PrpcUrl=http://my.rpc.endpoint/custom
```

If using Android Studio/Idea, this can also be added as a saved run configuration for easy access within the IDE UI.

Alternatively, to use the default local validator endpoint (http://127.0.0.1:8899), you can simply set the following gradle property:
```groovy
localValidator=true
```

We are continuing to improve our testing framework and will likely have more configuration options and better handling of devnet testing in the future.

### Opening a PR
We encourage anyone and everyone to contribute to this SDK by opening a pull request.

A few guidelines for contributor PRs
- target the `develop` branch, or a relevant feature branch. PRs should not target the `main` branch.
- should include a detailed and up to date description, and adhere to our [pull request template](https://github.com/metaplex-foundation/metaplex-android/blob/main/.github/pull_request_template.md)
- ensure that all unit tests are passing locally before submitting your PR for review

These guidelines serve to expedite our review of community contributions. We greatly appreciate your cooperation!

## Acknowledgment

The SDK is heavily inspired by the [JS-Next](https://github.com/metaplex-foundation/js-next) SDK. The objective of this is to have one Metaplex-wide interface for all NFTs. If you use the JS-Next SDK, this SDK should be familiar.
6 changes: 5 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
kotlin.code.style=official
# RPC URLs used for testing
testing.rpc.defaultUrl=https://api.devnet.solana.com
testing.rpc.localUrl=http://127.0.0.1:8899
localValidator=false
11 changes: 11 additions & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ android {

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"

// read RPC URL properties fro testing
def defaultRpcUrl = project.properties["testing.rpc.defaultUrl"]
def rpcUrl = project.properties["rpcUrl"] ?: defaultRpcUrl

def useLocalValidator = project.properties["localValidator"].toBoolean()
def localRpcUrl = project.properties["testing.rpc.localUrl"]
if (useLocalValidator && localRpcUrl != null) rpcUrl = localRpcUrl

// set RPC URL (used in testing)
buildConfigField 'String', 'RPC_URL', "\"${rpcUrl.toString().replace('"', '')}\""
}

buildTypes {
Expand Down
31 changes: 26 additions & 5 deletions lib/src/test/java/com/metaplex/lib/MetaplexTestUtils.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
package com.metaplex.lib

import com.metaplex.lib.drivers.indenty.KeypairIdentityDriver
import com.metaplex.lib.drivers.indenty.ReadOnlyIdentityDriver
import com.metaplex.lib.drivers.rpc.JdkRpcDriver
import com.metaplex.lib.drivers.solana.Commitment
import com.metaplex.lib.drivers.solana.Connection
import com.metaplex.lib.drivers.solana.SolanaConnectionDriver
import com.metaplex.lib.drivers.solana.TransactionOptions
import com.metaplex.lib.drivers.storage.MemoryStorageDriver
import com.metaplex.lib.drivers.storage.StorageDriver
import com.solana.core.Account
import com.solana.core.PublicKey
import com.solana.networking.RPCEndpoint
import java.net.URL

object MetaplexTestUtils {
const val RPC_URL = BuildConfig.RPC_URL

val DEFAULT_TRANSACTION_OPTIONS = TransactionOptions(Commitment.CONFIRMED, skipPreflight = true)

val TEST_ACCOUNT_PUBLICKEY = PublicKey(SolanaTestData.TEST_ACCOUNT_PUBLICKEY_STRING)
}

fun MetaplexTestUtils.generateMetaplexInstance(accountPublicKey: PublicKey = TEST_ACCOUNT_PUBLICKEY,
storageDriver: StorageDriver = MemoryStorageDriver())
: Metaplex {
fun MetaplexTestUtils.generateConnectionDriver(
rpcURL: URL = URL(RPC_URL), txOptions: TransactionOptions = DEFAULT_TRANSACTION_OPTIONS
) = SolanaConnectionDriver(JdkRpcDriver(rpcURL), txOptions)

fun MetaplexTestUtils.generateMetaplexInstance(
account: Account = Account(), connectionDriver: Connection = generateConnectionDriver(),
storageDriver: StorageDriver = MemoryStorageDriver()
): Metaplex {
val identityDriver = KeypairIdentityDriver(account, connectionDriver)
return Metaplex(connectionDriver, identityDriver, storageDriver)
}

val MetaplexTestUtils.readOnlyMainnetMetaplexInstance: Metaplex get() {
val solanaConnection = SolanaConnectionDriver(RPCEndpoint.mainnetBetaSolana)
val solanaIdentityDriver = ReadOnlyIdentityDriver(accountPublicKey, solanaConnection)
return Metaplex(solanaConnection, solanaIdentityDriver, storageDriver)
val solanaIdentityDriver = ReadOnlyIdentityDriver(TEST_ACCOUNT_PUBLICKEY, solanaConnection)
return Metaplex(solanaConnection, solanaIdentityDriver, MemoryStorageDriver())
}
4 changes: 2 additions & 2 deletions lib/src/test/java/com/metaplex/lib/MetaplexTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package com.metaplex.lib
import com.metaplex.lib.MetaplexTestUtils.TEST_ACCOUNT_PUBLICKEY
import com.solana.models.buffer.AccountInfo
import com.solana.models.buffer.BufferInfo
import com.solana.networking.RPCEndpoint
import org.junit.Assert
import org.junit.Test
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

class MetaplexTests {

val metaplex: Metaplex get() = MetaplexTestUtils.generateMetaplexInstance()

val metaplex: Metaplex get() = MetaplexTestUtils.readOnlyMainnetMetaplexInstance
@Test
fun testMetaplexSetUpReturnsValidInstance() {
Assert.assertNotNull(metaplex)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.metaplex.lib.drivers.identity

import com.metaplex.lib.SolanaTestData
import com.metaplex.lib.*
import com.metaplex.lib.drivers.indenty.KeypairIdentityDriver
import com.metaplex.lib.mnemonic
import com.metaplex.lib.publicKey
import com.metaplex.lib.drivers.solana.SolanaConnectionDriver
import com.solana.core.Account
import com.solana.core.DerivationPath
Expand All @@ -21,11 +19,12 @@ class KeypairIdentityDriverTests {
fun testSetUpKeypairIdentityDriverFromMnemonic() {
// given
val expectedPublicKey = SolanaTestData.TEST_ACCOUNT_MNEMONIC_PAIR.publicKey
val solanaConnection = SolanaConnectionDriver(RPCEndpoint.mainnetBetaSolana)
val solanaConnection = MetaplexTestUtils.generateConnectionDriver()
val account = Account.fromMnemonic(SolanaTestData.TEST_ACCOUNT_MNEMONIC_PAIR.mnemonic,
"", DerivationPath.BIP44_M_44H_501H_0H_OH)

// when
val keypairIdentityDriver = KeypairIdentityDriver(solanaConnection.solanaRPC,
Account.fromMnemonic(SolanaTestData.TEST_ACCOUNT_MNEMONIC_PAIR.mnemonic, "", DerivationPath.BIP44_M_44H_501H_0H_OH))
val keypairIdentityDriver = KeypairIdentityDriver(account, solanaConnection)

//then
Assert.assertEquals(expectedPublicKey, keypairIdentityDriver.publicKey.toBase58())
Expand All @@ -34,8 +33,11 @@ class KeypairIdentityDriverTests {
@Test
fun testSignTransactionReturnsTrxHash() {
// given
val account = Account.fromMnemonic(SolanaTestData.TEST_ACCOUNT_MNEMONIC_PAIR.mnemonic, "", DerivationPath.BIP44_M_44H_501H_0H_OH)
val expectedSignedTransaction = "AaHQ/obYLnD6GUFqxDKiiNkw2NYsLt+NZHa8ALB64uM0wpADNVQ5eWhzW38FcxfthDz6zXsJao58y5/fFovSoAABAAEC1J5StK6hI4+ERBMKkBUsHeIzegza3Eb/t7dwtSG4Q9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJrdSfHQAfBFYiaNQeEg3d9YB3F537Ex4K5dG79qBe0rAQECAAAMAgAAAOgDAAAAAAAA"
val account = Account.fromMnemonic(SolanaTestData.TEST_ACCOUNT_MNEMONIC_PAIR.mnemonic,
"", DerivationPath.BIP44_M_44H_501H_0H_OH)

val connectionDriver = MetaplexTestUtils.generateConnectionDriver()
val instruction = SystemProgram.transfer(account.publicKey, account.publicKey, 1000)
val transaction = Transaction().apply {
addInstruction(instruction)
Expand All @@ -44,8 +46,7 @@ class KeypairIdentityDriverTests {

// when
var result: Transaction? = null
val keypairIdentityDriver = KeypairIdentityDriver(
SolanaConnectionDriver(RPCEndpoint.mainnetBetaSolana).solanaRPC, account)
val keypairIdentityDriver = KeypairIdentityDriver(account, connectionDriver)

val lock = CountDownLatch(1)
keypairIdentityDriver.signTransaction(transaction){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package com.metaplex.lib.modules.candymachine

import com.metaplex.lib.MetaplexTestUtils
import com.metaplex.lib.drivers.indenty.IdentityDriver
import com.metaplex.lib.drivers.indenty.KeypairIdentityDriver
import com.metaplex.lib.drivers.indenty.ReadOnlyIdentityDriver
Expand All @@ -17,6 +18,7 @@ import com.metaplex.lib.drivers.solana.Commitment
import com.metaplex.lib.drivers.solana.Connection
import com.metaplex.lib.drivers.solana.SolanaConnectionDriver
import com.metaplex.lib.drivers.solana.TransactionOptions
import com.metaplex.lib.generateConnectionDriver
import com.metaplex.lib.modules.candymachines.CandyMachineClient
import com.metaplex.lib.modules.candymachines.models.CandyMachine
import com.metaplex.lib.modules.candymachines.models.CandyMachineItem
Expand Down Expand Up @@ -129,7 +131,7 @@ class CandyMachineClientTests {
fun testFindCandyMachineByAddressReturnsValidCandyMachine() = runTest {
// given
val cmAddress = PublicKey("4Add8hdxC44H3DcGfgWTvn2GNBfofk5uu2iatEW9LCYz")
val connection = SolanaConnectionDriver(JdkRpcDriver(RPCEndpoint.devnetSolana.url))
val connection = MetaplexTestUtils.generateConnectionDriver(RPCEndpoint.devnetSolana.url)
val client = CandyMachineClient(connection,
ReadOnlyIdentityDriver(Account().publicKey, connection))

Expand All @@ -144,11 +146,8 @@ class CandyMachineClientTests {
@Test
fun testCandyMachineCreateCreatesValidCandyMachine() = runTest {
// given
val rpcUrl = URL("http://127.0.0.1:8899")
// val rpcUrl = RPCEndpoint.devnetSolana.url
val signer = Account()
val connection = SolanaConnectionDriver(JdkRpcDriver(rpcUrl),
transactionOptions = TransactionOptions(Commitment.CONFIRMED, skipPreflight = true))
val connection = MetaplexTestUtils.generateConnectionDriver()
val identityDriver = KeypairIdentityDriver(signer, connection)
val client = CandyMachineClient(connection, identityDriver)

Expand All @@ -167,11 +166,8 @@ class CandyMachineClientTests {
@Test
fun testCandyMachineSetCollectionUpdatesCandyMachineCollection() = runTest {
// given
val rpcUrl = URL("http://127.0.0.1:8899")
// val rpcUrl = RPCEndpoint.devnetSolana.url
val signer = Account()
val connection = SolanaConnectionDriver(JdkRpcDriver(rpcUrl),
transactionOptions = TransactionOptions(Commitment.CONFIRMED, skipPreflight = true))
val connection = MetaplexTestUtils.generateConnectionDriver()
val identityDriver = KeypairIdentityDriver(signer, connection)
val client = CandyMachineClient(connection, identityDriver)

Expand All @@ -194,11 +190,8 @@ class CandyMachineClientTests {
@Test
fun testCandyMachineInsertItemsCanAddItemsToCandyMachine() = runTest {
// given
val rpcUrl = URL("http://127.0.0.1:8899")
// val rpcUrl = RPCEndpoint.devnetSolana.url
val signer = Account()
val connection = SolanaConnectionDriver(JdkRpcDriver(rpcUrl),
transactionOptions = TransactionOptions(Commitment.CONFIRMED, skipPreflight = true))
val connection = MetaplexTestUtils.generateConnectionDriver()
val identityDriver = KeypairIdentityDriver(signer, connection)
val client = CandyMachineClient(connection, identityDriver)

Expand All @@ -223,11 +216,8 @@ class CandyMachineClientTests {
@Test
fun testCandyMachineInsertItemsSequentiallyAddsItemsToCandyMachine() = runTest {
// given
val rpcUrl = URL("http://127.0.0.1:8899")
// val rpcUrl = RPCEndpoint.devnetSolana.url
val signer = Account()
val connection = SolanaConnectionDriver(JdkRpcDriver(rpcUrl),
transactionOptions = TransactionOptions(Commitment.CONFIRMED, skipPreflight = true))
val connection = MetaplexTestUtils.generateConnectionDriver()
val identityDriver = KeypairIdentityDriver(signer, connection)
val client = CandyMachineClient(connection, identityDriver)

Expand Down Expand Up @@ -256,11 +246,8 @@ class CandyMachineClientTests {
@Test
fun testCandyMachineMintNftMintsAndReturnsNft() = runTest {
// given
val rpcUrl = URL("http://127.0.0.1:8899")
// val rpcUrl = RPCEndpoint.devnetSolana.url
val signer = Account()
val connection = SolanaConnectionDriver(JdkRpcDriver(rpcUrl),
transactionOptions = TransactionOptions(Commitment.CONFIRMED, skipPreflight = true))
val connection = MetaplexTestUtils.generateConnectionDriver()
val identityDriver = KeypairIdentityDriver(signer, connection)
val client = CandyMachineClient(connection, identityDriver)

Expand Down
Loading

0 comments on commit cae532f

Please sign in to comment.