diff --git a/.test-env b/.test-env new file mode 100644 index 000000000..3b17bb191 --- /dev/null +++ b/.test-env @@ -0,0 +1,14 @@ +# Configs for testing repo download: +SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" +SDK_TESTING_BRANCH="master" +SDK_TESTING_HARNESS="test-harness" + +VERBOSE_HARNESS=0 + +# WARNING: If set to 1, new features will be LOST when downloading the test harness. +# REGARDLESS: modified features are ALWAYS overwritten. +REMOVE_LOCAL_FEATURES=0 + +# WARNING: Be careful when turning on the next variable. +# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` +OVERWRITE_TESTING_ENVIRONMENT=0 diff --git a/Makefile b/Makefile index 9e4d4850a..be60d45d2 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,24 @@ +UNIT_TAGS := "$(subst :, or ,$(shell awk '{print $2}' src/test/unit.tags | paste -s -d: -))" +INTEGRATION_TAGS := "$(subst :, or ,$(shell awk '{print $2}' src/test/integration.tags | paste -s -d: -))" + unit: - mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" + mvn test -Dcucumber.filter.tags=$(UNIT_TAGS) integration: - mvn test \ - -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \ - -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey_v1 or @applications.verified or @applications or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c or @compile.sourcemap" + mvn test -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest -Dcucumber.filter.tags=$(INTEGRATION_TAGS) + +display-all-java-steps: + find src/test/java/com/algorand/algosdk -name "*.java" | xargs grep "io.cucumber.java.en" 2>/dev/null | grep -v Binary | cut -d: -f1 | sort | uniq | xargs grep -E "@(Given|Then|When)" + +harness: + ./test-harness.sh + +docker-javasdk-build: + # Build SDK testing environment + docker build -t java-sdk-testing . + +docker-javasdk-run: + # Launch SDK testing + docker run -it --network host java-sdk-testing:latest -docker-test: - ./run_integration_tests.sh +docker-test: harness docker-javasdk-build docker-javasdk-run diff --git a/README.md b/README.md index b57d49bfa..71df35d52 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,13 @@ There is also a special integration test environment, and shared tests. To run t ~$ make docker-test ``` +To stand up the test harness, without running the entire test suite use the Makefile: +``` +~$ make harness +``` +You can then run specific cucumber-based unit and integration tests directly. + + ## deploying artifacts The generated pom file provides maven compatibility and deploy capabilities. diff --git a/run_integration_tests.sh b/run_integration_tests.sh deleted file mode 100755 index 0fcdccce7..000000000 --- a/run_integration_tests.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bash -set -e - -rootdir=`dirname $0` -pushd $rootdir - -SKIP_TEST_CONTAINER=0 -UPDATE_FEATURE_FILES_ONLY=0 -TEST_BRANCH=master - -function help { - echo "Options:" - echo " -local skip launching the test container." - echo " -feature-only don't bring up test environment or launch test container." - echo " -test-branch install feature files from this branch instead of the default branch." -} - -function my_exit { - popd - exit $1 -} - -while [ "$1" != "" ]; do - case "$1" in - -local) - SKIP_TEST_CONTAINER=1 - ;; - -feature-only) - UPDATE_FEATURE_FILES_ONLY=1 - ;; - -test-branch) - shift - TEST_BRANCH=$1 - ;; - *) - echo "Unknown option $1" - help - my_exit 0 - ;; - esac - shift -done - -# Reset test harness -rm -rf test-harness -git clone --single-branch --branch ${TEST_BRANCH} https://github.com/algorand/algorand-sdk-testing.git test-harness - -## Copy feature files into the project resources - -rm -rf src/test/resources/com/algorand/algosdk/integration -rm -rf src/test/resources/com/algorand/algosdk/unit -mkdir -p src/test/resources/com/algorand/algosdk/integration -mkdir -p src/test/resources/com/algorand/algosdk/unit - -# The Java implementation of these is too tightly coupled with the -# integration tests, so add them to the integration tests instead. -mv test-harness/features/unit/offline.feature test-harness/features/integration/ - -cp -r test-harness/features/integration/* src/test/resources/com/algorand/algosdk/integration -cp -r test-harness/features/unit/* src/test/resources/com/algorand/algosdk/unit -cp -r test-harness/features/resources/* src/test/resources/ - -if [ "${UPDATE_FEATURE_FILES_ONLY}" == "1" ]; then - my_exit 1 -fi - -# Start test harness environment -./test-harness/scripts/up.sh - -if [ "${SKIP_TEST_CONTAINER}" == "1" ]; then - my_exit 1 -fi - -# Build SDK testing environment -docker build -t java-sdk-testing -f Dockerfile "$(pwd)" - -# Launch SDK testing -docker run -it \ - --network host \ - java-sdk-testing:latest - -my_exit 0 diff --git a/src/test/integration.tags b/src/test/integration.tags new file mode 100644 index 000000000..c9421bf47 --- /dev/null +++ b/src/test/integration.tags @@ -0,0 +1,14 @@ +@abi +@algod +@applications +@applications.verified +@assets +@auction +@c2c +@compile +@compile.sourcemap +@dryrun +@kmd +@rekey_v1 +@send +@send.keyregtxn diff --git a/src/test/java/com/algorand/algosdk/integration/Clients.java b/src/test/java/com/algorand/algosdk/integration/Clients.java index 89a94cfff..4f4b61b22 100644 --- a/src/test/java/com/algorand/algosdk/integration/Clients.java +++ b/src/test/java/com/algorand/algosdk/integration/Clients.java @@ -15,9 +15,4 @@ public class Clients { public void an_algod_v2_client_connected_to_port_with_token(String host, Integer port, String token) { v2Client = new AlgodClient(host, port, token); } - - @Given("indexer client {int} at {string} port {int} with token {string}") - public void indexer_client_at_port_with_token(Integer index, String uri, Integer port, String token) { - indexerClients.put(index, new IndexerClient(uri, port, "")); - } } diff --git a/src/test/java/com/algorand/algosdk/integration/EvalDelta.java b/src/test/java/com/algorand/algosdk/integration/EvalDelta.java deleted file mode 100644 index 7bc9f8fd2..000000000 --- a/src/test/java/com/algorand/algosdk/integration/EvalDelta.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.algorand.algosdk.integration; - -import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.v2.client.model.*; -import io.cucumber.java.en.Then; -import org.assertj.core.api.Assertions; - -import java.math.BigInteger; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; - -public class EvalDelta { - private final Clients clients; - private final TransientAccount transientAccount; - private final Applications applications; - - public EvalDelta(Clients clients, TransientAccount transientAccount, Applications applications) { - this.clients = clients; - this.transientAccount = transientAccount; - this.applications = applications; - } - - @Then("the unconfirmed pending transaction by ID should have no apply data fields.") - public void the_unconfirmed_pending_transaction_by_ID_should_have_no_apply_data_fields() throws Exception { - PendingTransactionResponse r = clients.v2Client - .PendingTransactionInformation(applications.txId) - .execute() - .body(); - - // Just in case we missed the boat and the tx is now confirmed. - if (r.confirmedRound == null) { - assertThat(r.globalStateDelta).isEmpty(); - assertThat(r.localStateDelta).isEmpty(); - } - } - - private List getAccountDelta(Address addr, List data) { - return data.stream() - .filter(ad -> ad.address.equals(addr)) - .map(ad -> ad.delta) - .findAny() - .orElse(null); - } - - @Then("the confirmed pending transaction by ID should have a {string} state change for {string} to {string}, indexer {int} should also confirm this.") - public void checkConfirmedTransaction(String stateLocation, String key, String newValue, int indexer) throws Exception { - PendingTransactionResponse r = clients.v2Client - .PendingTransactionInformation(applications.txId) - .execute() - .body(); - - // Try fetching the transaction from indexer a few times to give indexer a chance to process the new block. - Transaction tx = null; - for (int i = 0; i < 5 && tx == null; i++) { - TransactionsResponse indexerResponse = clients.indexerClients.get(indexer) - .searchForTransactions() - .txid(applications.txId) - .execute() - .body(); - if (! indexerResponse.transactions.isEmpty()) { - tx = indexerResponse.transactions.get(0); - } else { - Thread.sleep(TimeUnit.SECONDS.toMillis(1)); - } - } - - assertThat(tx).as("Indexer was unable to return txid %s.", applications.txId).isNotNull(); - - // Grab local or global key/value deltas. - // Algod - List keyValuesAlgod = null; - // Indexer - List keyValuesIndexer = null; - switch(stateLocation) { - case "local": - Address localAccountKey = r.txn.tx.sender; - keyValuesAlgod = getAccountDelta(localAccountKey, r.localStateDelta); - keyValuesIndexer = getAccountDelta(localAccountKey, tx.localStateDelta); - break; - case "global": - keyValuesAlgod = r.globalStateDelta; - keyValuesIndexer = tx.globalStateDelta; - break; - default: - Assertions.fail("Unknown state location: %s", stateLocation); - } - - // Algod - assertThat(keyValuesAlgod).hasSize(1); - EvalDeltaKeyValue keyValueAlgod = keyValuesAlgod.get(0); - assertThat(keyValueAlgod.key).isEqualTo(key); - - // Indexer - assertThat(keyValuesIndexer).hasSize(1); - EvalDeltaKeyValue keyValueIndexer = keyValuesIndexer.get(0); - assertThat(keyValueIndexer.key).isEqualTo(key); - - // They should have the same action - assertThat(keyValueAlgod.value.action).isEqualTo(keyValueIndexer.value.action); - - // And the values should be equal, and should equal the expected value - switch(keyValueAlgod.value.action.toString()) { - case "1": - assertThat(keyValueAlgod.value.bytes).isEqualTo(newValue); - assertThat(keyValueAlgod.value.bytes).isEqualTo(keyValueIndexer.value.bytes); - break; - case "2": - assertThat(keyValueAlgod.value.uint).isEqualTo(BigInteger.valueOf(Long.parseLong(newValue))); - assertThat(keyValueAlgod.value.uint).isEqualTo(keyValueIndexer.value.uint); - break; - default: - Assertions.fail("Unknown value action %d.", keyValueAlgod.value.action); - } - } -} diff --git a/src/test/java/com/algorand/algosdk/integration/Indexer.java b/src/test/java/com/algorand/algosdk/integration/Indexer.java deleted file mode 100644 index 706af3716..000000000 --- a/src/test/java/com/algorand/algosdk/integration/Indexer.java +++ /dev/null @@ -1,492 +0,0 @@ -package com.algorand.algosdk.integration; - -import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.unit.utils.TestingUtils; -import com.algorand.algosdk.util.Encoder; -import com.algorand.algosdk.util.ResourceUtils; -import com.algorand.algosdk.v2.client.common.*; -import com.algorand.algosdk.v2.client.indexer.*; -import com.algorand.algosdk.v2.client.model.*; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.Assertions; - -import java.io.File; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -import static com.algorand.algosdk.unit.utils.TestingUtils.searchEnum; -import static org.assertj.core.api.Assertions.assertThat; - -public class Indexer { - Response healthResponse; - Response blockResponse; - Response accountResponse; - Response accountsResponse; - Response assetResponse; - Response assetBalancesResponse; - Response transactionsResponse; - Response assetsResponse; - Response applicationsResponse; - - Response response; - - private final Clients clients; - - public Indexer(Clients clients) { - this.clients = clients; - } - - @When("I use {int} to check the services health") - public void i_use_to_check_the_services_health(Integer index) throws Exception { - healthResponse = clients.indexerClients.get(index).makeHealthCheck().execute(); - } - - @Then("I receive status code {int}") - public void i_receive_status_code(Integer code) { - assertThat(healthResponse.code()).isEqualTo(code); - } - - @When("I use {int} to lookup block {long}") - public void i_request_block_with_indexer(Integer indexer, Long block) throws Exception { - blockResponse = clients.indexerClients.get(indexer).lookupBlock(block).execute(); - } - - @Then("The block was confirmed at {long}, contains {int} transactions, has the previous block hash {string}") - public void the_block_was_confirmed_at_contains_transactions_has_the_previous_block_hash(Long unixTimestamp, Integer numTransactions, String previousBlockHash) { - Block block = blockResponse.body(); - - assertThat(block.timestamp).isEqualTo(unixTimestamp); - assertThat(block.transactions).hasSize(numTransactions); - assertThat(block.previousBlockHash).isEqualTo(Encoder.decodeFromBase64(previousBlockHash)); - } - - @When("I use {int} to lookup account {string} at round {long}") - public void i_lookup_account_with(Integer indexer, String account, Long round) throws Exception { - LookupAccountByID query = clients.indexerClients.get(indexer).lookupAccountByID(new Address(account)); - if (round != 0) { - query.round(round); - } - accountResponse = query.execute(); - } - - @Then("The account has {int} assets, the first is asset {long} has a frozen status of {string} and amount {biginteger}.") - public void the_account_has_num_assets_asset_has_a_frozen_status_of_and_amount(Integer numAssets, Long assetId, String frozenStatus, BigInteger amount) { - AccountResponse response = accountResponse.body(); - - assertThat(response.account.assets).hasSize(numAssets); - AssetHolding holding = response.account.assets.get(0); - assertThat(holding.assetId).isEqualTo(assetId); - assertThat(holding.isFrozen).isEqualTo(Boolean.parseBoolean(frozenStatus)); - assertThat(holding.amount).isEqualTo(amount); - } - - @Then("The account has {long} μalgos and {int} assets, {long} has {biginteger}") - public void the_account_has_μalgos_and_assets(Long amount, Integer numAssets, Long assetId, BigInteger assetAmount) { - AccountResponse response = accountResponse.body(); - assertThat(response.account.assets).hasSize(numAssets); - assertThat(response.account.amount).as("μalgos").isEqualTo(amount); - - if (assetId != 0) { - response.account.assets.forEach(a -> { - if (a.assetId == assetId) { - assertThat(a.amount).as("assets").isEqualTo(assetAmount); - } - }); - } - } - - @Then("The account created {int} assets, the first is asset {long} is named {string} with a total amount of {biginteger} {string}") - public void the_account_created_assets_the_first_is_asset_is_named_with_a_total_amount_of(Integer numAssetsCreated, Long assetId, String assetName, BigInteger assetTotal, String assetUnit) { - AccountResponse response = accountResponse.body(); - assertThat(response.account.createdAssets).hasSize(numAssetsCreated); - Asset asset = response.account.createdAssets.get(0); - assertThat(asset.params.name).isEqualTo(assetName); - assertThat(asset.params.unitName).isEqualTo(assetUnit); - assertThat(asset.params.total).isEqualTo(assetTotal); - } - - @When("I use {int} to lookup asset {long}") - public void i_lookup_asset_with(Integer indexer, Long assetId) throws Exception { - assetResponse = clients.indexerClients.get(indexer).lookupAssetByID(assetId).execute(); - } - - @Then("The asset found has: {string}, {string}, {string}, {long}, {string}, {biginteger}, {string}") - public void the_asset_found_has(String name, String units, String creatorAddress, Long decimals, String defaultFrozen, BigInteger total, String clawbackAddress) { - AssetResponse response = assetResponse.body(); - AssetParams params = response.asset.params; - - assertThat(params.name).isEqualTo(name); - assertThat(params.unitName).isEqualTo(units); - assertThat(params.creator).isEqualTo(creatorAddress); - assertThat(params.decimals).isEqualTo(decimals); - assertThat(params.defaultFrozen).isEqualTo(Boolean.parseBoolean(defaultFrozen)); - assertThat(params.total).isEqualTo(total); - assertThat(params.clawback).isEqualTo(clawbackAddress); - } - - @When("I use {int} to lookup asset balances for {long} with {long}, {long}, {long} and token {string}") - public void i_lookup_asset_balances_for_with_with(Integer indexer, Long assetId, Long currencyGT, Long currencyLT, Long limit, String next) throws Exception { - LookupAssetBalances query = clients.indexerClients.get(indexer).lookupAssetBalances(assetId); - if (currencyGT != 0) { - query.currencyGreaterThan(currencyGT); - } - if (currencyLT != 0) { - query.currencyLessThan(currencyLT); - } - if (limit != 0) { - query.limit(limit); - } - if (StringUtils.isNotEmpty(next)) { - query.next(next); - } - assetBalancesResponse = query.execute(); - } - - @Then("There are {int} with the asset, the first is {string} has {string} and {biginteger}") - public void there_are_with_the_asset_the_first_is_has_and(Integer numResults, String account, String frozenState, BigInteger amount) { - AssetBalancesResponse response = assetBalancesResponse.body(); - - assertThat(response.balances).hasSize(numResults); - - MiniAssetHolding holding = response.balances.get(0); - - assertThat(holding.isFrozen).isEqualTo(Boolean.parseBoolean(frozenState)); - assertThat(holding.amount).isEqualTo(amount); - } - - @When("I get the next page using {int} to lookup asset balances for {long} with {long}, {long}, {long}") - public void i_get_the_next_page_using_to_search_for_asset_balances_with(Integer indexer, Long assetId, Long currencyGT, Long currencyLT, Long limit) throws Exception { - String token = assetBalancesResponse.body().nextToken; - i_lookup_asset_balances_for_with_with(indexer, assetId, currencyGT, currencyLT, limit, token); - } - - @When("I use {int} to search for an account with {long}, {long}, {long}, {long} and token {string}") - public void oldSearchForAccounts(Integer indexer, Long assetId, Long limit, Long gt, Long lt, String token) throws Exception { - searchForAccounts(indexer, assetId, limit, gt, lt, "", 0L, "false", token); - } - - @When("I use {int} to search for an account with {long}, {long}, {long}, {long}, {string}, {long} and token {string}") - public void oldSearchForAccounts2(Integer indexer, Long assetId, Long limit, Long gt, Long lt, String authAddr, Long applicationId, String token) throws Exception { - searchForAccounts(indexer, assetId, limit, gt, lt, authAddr, applicationId, "false", token); - } - - @When("I use {int} to search for an account with {long}, {long}, {long}, {long}, {string}, {long}, {string} and token {string}") - public void searchForAccounts(Integer indexer, Long assetId, Long limit, Long gt, Long lt, String authAddr, Long applicationId, String includeAll, String token) throws Exception { - SearchForAccounts query = clients.indexerClients.get(indexer).searchForAccounts(); - - if (assetId != 0) query.assetId(assetId); - if (limit != 0) query.limit(limit); - if (gt != 0) query.currencyGreaterThan(gt); - if (lt != 0) query.currencyLessThan(lt); - if (StringUtils.isNotEmpty(token)) query.next(token); - if (StringUtils.isNotEmpty(authAddr)) query.authAddr(new Address(authAddr)); - if (applicationId != 0) query.applicationId(applicationId); - if (Boolean.parseBoolean(includeAll)) query.includeAll(Boolean.parseBoolean(includeAll)); - - accountsResponse = query.execute(); - response = accountsResponse; - } - - @Then("There are {int}, the first has {long}, {long}, {long}, {long}, {string}, {long}, {string}, {string}") - public void there_are_the_first_has(Integer numAccounts, Long pendingRewards, Long rewardsBase, Long rewards, Long amountWithoutPendingRewards, String address, Long amount, String status, String type) { - AccountsResponse response = accountsResponse.body(); - - assertThat(response.accounts).hasSize(numAccounts); - - Account account = response.accounts.get(0); - assertThat(account.address.toString()).isEqualTo(address); - assertThat(account.pendingRewards).isEqualTo(pendingRewards); - assertThat(account.rewardBase).isEqualTo(rewardsBase); - assertThat(account.rewards).isEqualTo(rewards); - assertThat(account.amountWithoutPendingRewards).isEqualTo(amountWithoutPendingRewards); - assertThat(account.amount).isEqualTo(amount); - assertThat(account.status).isEqualTo(status); - if (account.sigType != null) { - assertThat(account.sigType).isEqualTo(searchEnum(Enums.SigType.class, type)); - } - } - - @Then("The first account is online and has {string}, {long}, {long}, {long}, {string}, {string}") - public void the_first_account_is_online_and_has(String address, Long keyDilution, Long firstValid, Long lastValid, String voteKey, String selectionKey) { - Account account = accountsResponse.body().accounts.get(0); - assertThat(account.address.toString()).isEqualTo(address); - - AccountParticipation participation = account.participation; - assertThat(participation).isNotNull(); - assertThat(participation.voteKeyDilution).isEqualTo(keyDilution); - assertThat(participation.voteFirstValid).isEqualTo(firstValid); - assertThat(participation.voteLastValid).isEqualTo(lastValid); - assertThat(participation.voteParticipationKey).isEqualTo(Encoder.decodeFromBase64(voteKey)); - assertThat(participation.selectionParticipationKey).isEqualTo(Encoder.decodeFromBase64(selectionKey)); - } - - @Then("I get the next page using {int} to search for an account with {long}, {long}, {long} and {long}") - public void i_get_the_next_page_using_to_search_for_an_account_with_and(Integer indexer, Long assetId, Long limit, Long gt, Long lt) throws Exception { - AccountsResponse response = accountsResponse.body(); - searchForAccounts(indexer, assetId, limit, gt, lt, "", 0L, "false", response.nextToken); - } - - @When("I use {int} to search for transactions with {long}, {string}, {string}, {string}, {string}, {long}, {long}, {long}, {long}, {string}, {string}, {long}, {long}, {string}, {string}, {string} and token {string}") - public void oldSearchForTransactions(Integer indexer, Long limit, String notePrefix, - String txType, String sigType, String txId, Long round, - Long minRound, Long maxRound, Long assetId, - String beforeTime, String afterTime, Long currencyGT, - Long currencyLT, String address, String addressRole, - String excludeCloseTo, String token - ) throws Exception { - searchForTransactions(indexer, limit, notePrefix, txType, sigType, txId, round, minRound, maxRound, assetId, - beforeTime, afterTime, currencyGT, currencyLT, address, addressRole, excludeCloseTo, 0L, - token); - } - - @When("I use {int} to search for transactions with {long}, {string}, {string}, {string}, {string}, {long}, {long}, {long}, {long}, {string}, {string}, {long}, {long}, {string}, {string}, {string}, {long} and token {string}") - public void searchForTransactions(Integer indexer, Long limit, String notePrefix, - String txType, String sigType, String txId, Long round, - Long minRound, Long maxRound, Long assetId, - String beforeTime, String afterTime, Long currencyGT, - Long currencyLT, String address, String addressRole, - String excludeCloseTo, Long applicaitonId, String token - ) throws Exception { - SearchForTransactions query = clients.indexerClients.get(indexer).searchForTransactions(); - - if (limit != 0) query.limit(limit); - if (StringUtils.isNotEmpty(notePrefix)) query.notePrefix(Encoder.decodeFromBase64(notePrefix)); - if (StringUtils.isNotEmpty(txType)) query.txType(searchEnum(Enums.TxType.class, txType)); - if (StringUtils.isNotEmpty(sigType)) query.sigType(searchEnum(Enums.SigType.class, sigType)); - if (StringUtils.isNotEmpty(txId)) query.txid(txId); - if (round != 0) query.round(round); - if (minRound != 0) query.minRound(minRound); - if (maxRound != 0) query.maxRound(maxRound); - if (assetId != 0) query.assetId(assetId); - if (StringUtils.isNotEmpty(beforeTime)) query.beforeTime(Utils.parseDate(beforeTime)); - if (StringUtils.isNotEmpty(afterTime)) query.afterTime(Utils.parseDate(afterTime)); - if (currencyGT != 0) query.currencyGreaterThan(currencyGT); - if (currencyLT != 0) query.currencyLessThan(currencyLT); - if (StringUtils.isNotEmpty(address)) query.address(new Address(address)); - if (StringUtils.isNotEmpty(addressRole)) query.addressRole(Enums.AddressRole.valueOf(addressRole.toUpperCase())); - if (StringUtils.isNotEmpty(excludeCloseTo)) query.excludeCloseTo(Boolean.parseBoolean(excludeCloseTo)); - if (StringUtils.isNotEmpty(token)) query.next(token); - if (applicaitonId != 0) query.applicationId(applicaitonId); - - // This step is followed by multiple 'then' steps, so save the state in two places. - transactionsResponse = query.execute(); - response = transactionsResponse; - } - - @Then("there are {int} transactions in the response, the first is {string}.") - public void there_are_transactions_in_the_response_the_first_is(Integer num, String txid) throws NoSuchAlgorithmException { - TransactionsResponse transactions = transactionsResponse.body(); - - assertThat(transactions.transactions).hasSize(num); - - // Don't check the txid if there weren't supposed to be any results. - if (num == 0) return; - - assertThat(transactions.transactions) - .first() - .hasFieldOrPropertyWithValue("id", txid); - } - - @And("Every transaction has tx-type {string}") - public void every_transaction_has_txtype(String type) { - TransactionsResponse transactions = transactionsResponse.body(); - - transactions.transactions.forEach(tx -> { - assertThat(tx.txType).isEqualTo(Enums.TxType.valueOf(type.toUpperCase())); - }); - } - - @And("Every transaction has sig-type {string}") - public void every_transaction_has_sigtype(String type) { - TransactionsResponse transactions = transactionsResponse.body(); - - transactions.transactions.forEach(tx -> { - switch (type) { - case "sig": - assertThat(tx.signature.sig).isNotEmpty(); - break; - case "msig": - assertThat(tx.signature.multisig).isNotNull(); - break; - case "lsig": - assertThat(tx.signature.logicsig).isNotNull(); - break; - } - ; - } - ); - } - - @And("Every transaction has round {long}") - public void every_transaction_has_round(Long round) { - TransactionsResponse transactions = transactionsResponse.body(); - - // Nothing to check for these results. - if (transactions.transactions.size() == 0) return; - - assertThat(transactions.transactions) - .extracting("confirmedRound") - .containsOnly(round); - } - - @And("Every transaction has round >= {long}") - public void every_transaction_has_round_greater_than(Long minRound) { - transactionsResponse.body().transactions.forEach(tx -> assertThat(tx.confirmedRound).isGreaterThanOrEqualTo(minRound)); - } - - @And("Every transaction has round <= {long}") - public void every_transaction_has_round_less_than(Long maxRound) { - transactionsResponse.body().transactions.forEach(tx -> assertThat(tx.confirmedRound).isLessThanOrEqualTo(maxRound)); - } - - @And("Every transaction works with asset-id {long}") - public void every_transaction_works_with_asset_id(Long assetId) { - transactionsResponse.body().transactions.forEach(tx -> { - if (tx.createdAssetIndex != null) { - assertThat(tx.createdAssetIndex).isEqualTo(assetId); - } - if (tx.assetTransferTransaction != null) { - assertThat(tx.assetTransferTransaction.assetId).isEqualTo(assetId); - } - }); - } - - @And("Every transaction is newer than {string}") - public void every_transaction_is_newer_than(String dateString) { - Instant i = Instant.parse(dateString); - transactionsResponse.body().transactions.forEach(tx -> { - assertThat(tx.roundTime).isGreaterThan(i.getEpochSecond()); - }); - } - - @And("Every transaction is older than {string}") - public void every_transaction_is_older_than(String dateString) { - Instant i = Instant.parse(dateString); - transactionsResponse.body().transactions.forEach(tx -> { - assertThat(tx.roundTime).isLessThan(i.getEpochSecond()); - }); - } - - @And("Every transaction moves between {biginteger} and {biginteger} currency") - public void every_transaction_moves_between_and_currency(BigInteger min, BigInteger max) { - // Normalize in case max isn't set. - BigInteger maxUnsignedLong = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.valueOf(2)); - final BigInteger normalizedMax = (max.longValue() == 0) ? maxUnsignedLong : max; - - transactionsResponse.body().transactions.forEach(tx -> { - switch (tx.txType) { - case PAY: - assertThat(BigInteger.valueOf(tx.paymentTransaction.amount)).isBetween(min, normalizedMax); - break; - case AXFER: - assertThat(tx.assetTransferTransaction.amount).isBetween(min, normalizedMax); - break; - default: - Assertions.fail("Only transactions that move currency should match this."); - } - }); - } - - @When("I use {int} to search for all {string} transactions") - public void i_use_to_search_for_all_transactions(Integer indexer, String account) throws Exception { - LookupAccountTransactions query = clients.indexerClients.get(indexer).lookupAccountTransactions(new Address(account)); - transactionsResponse = query.execute(); - } - - @When("I use {int} to search for all {long} asset transactions") - public void i_use_to_search_for_all_asset_transactions(Integer indexer, Long assetId) throws Exception { - LookupAssetTransactions query = clients.indexerClients.get(indexer).lookupAssetTransactions(assetId); - transactionsResponse = query.execute(); - } - - @And("I get the next page using {int} to search for transactions with {long} and {long}") - public void i_get_the_next_page_using_to_search_for_transactions_with_and(Integer indexer, Long limit, Long maxRound) throws Exception { - String next = transactionsResponse.body().nextToken; - - // Reuse the all-args wrapper, injecting the next token - searchForTransactions(indexer, limit, "", "", "", "", - 0L, 0L, maxRound, 0L, "", "", 0L, 0L, - "", "", "", 0L, next); - } - - @When("I use {int} to search for assets with {long}, {long}, {string}, {string}, {string}, and token {string}") - public void i_use_to_search_for_assets_with_and_token(Integer indexer, Long limit, Long assetId, String creator, String name, String unit, String token) throws Exception { - SearchForAssets query = clients.indexerClients.get(indexer).searchForAssets(); - - if (limit != 0) { - query.limit(limit); - } - if (assetId != 0) { - query.assetId(assetId); - } - if (StringUtils.isNotEmpty(creator)) { - query.creator(creator); - } - if (StringUtils.isNotEmpty(name)) { - query.name(name); - } - if (StringUtils.isNotEmpty(unit)) { - query.unit(unit); - } - if (StringUtils.isNotEmpty(token)) { - query.next(token); - } - assetsResponse = query.execute(); - } - - @Then("there are {int} assets in the response, the first is {long}.") - public void there_are_assets_in_the_response_the_first_is(Integer num, Long assetId) { - AssetsResponse response = assetsResponse.body(); - assertThat(response.assets).hasSize(num); - response.assets.forEach(a -> assertThat(a.index).isEqualTo(assetId)); - } - - - @When("I use {int} to search for applications with {long}, {long}, and token {string}") - public void i_use_to_search_for_applications_with_and_token_deprecated(Integer indexer, Long limit, Long applicationId, String token) throws Exception { - i_use_to_search_for_applications_with_and_token(indexer, limit, applicationId, "false", token); - } - - @When("I use {int} to search for applications with {long}, {long}, {string} and token {string}") - public void i_use_to_search_for_applications_with_and_token(Integer indexer, Long limit, Long applicationId, String includeAll, String token) throws Exception { - SearchForApplications query = clients.indexerClients.get(indexer).searchForApplications(); - - if (limit != 0) query.limit(limit); - if (applicationId != 0) query.applicationId(applicationId); - if (Boolean.parseBoolean(includeAll)) query.includeAll(Boolean.parseBoolean(includeAll)); - if (StringUtils.isNotEmpty(token)) query.next(token); - - response = query.execute(); - } - - @When("I use {int} to lookup application with {long}") - public void i_use_to_lookup_application_with(Integer indexer, Long applicationId) throws Exception { - i_use_to_lookup_application_with_and(indexer, applicationId, "false"); - } - - @When("I use {int} to lookup application with {long} and {string}") - public void i_use_to_lookup_application_with_and(Integer indexer, Long applicationId, String includeAll) throws Exception { - LookupApplicationByID query = clients.indexerClients.get(indexer).lookupApplicationByID(applicationId); - - if (Boolean.parseBoolean(includeAll)) query.includeAll(Boolean.parseBoolean(includeAll)); - - response = query.execute(); - } - - @Then("the parsed response should equal {string}.") - public void the_parsed_response_should_equal(String jsonFile) throws IOException { - File f = new File("src/test/resources/" + jsonFile); - assertThat(f).canRead(); - String json = response.toString(); - TestingUtils.verifyResponse(response, f); - } - -} diff --git a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java index 278878e03..c84c1f8bc 100644 --- a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java +++ b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java @@ -29,14 +29,11 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import org.threeten.bp.LocalDate; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; @@ -348,65 +345,12 @@ public void transactionParameters(int fee, int fv, int lv, String gh, String to, } } - @Given("key registration transaction parameters {int} {int} {int} {string} {string} {string} {int} {int} {int} {string} {string}") - public void keyregTxnParameters(int fee, int fv, int lv, String gh, String votepk, String vrfpk, int votefst, int votelst, int votekd, String gen, String note) throws GeneralSecurityException, NoSuchAlgorithmException{ - this.fee = BigInteger.valueOf(fee); - this.fv = BigInteger.valueOf(fv); - this.lv = BigInteger.valueOf(lv); - this.gh = new Digest(Encoder.decodeFromBase64(gh)); - this.votepk = new ParticipationPublicKey(Encoder.decodeFromBase64(votepk)); - this.vrfpk = new VRFPublicKey(Encoder.decodeFromBase64(vrfpk)); - this.votefst = BigInteger.valueOf(votefst); - this.votelst = BigInteger.valueOf(votelst); - this.votekd = BigInteger.valueOf(votekd); - if (!gen.equals("none")) { - this.gen = gen; - } - if (!note.equals("none")) { - this.note = Encoder.decodeFromBase64(note); - } - } - @Given("mnemonic for private key {string}") public void mn_for_sk(String mn) throws GeneralSecurityException{ account = new Account(mn); pk = account.getAddress(); } - @When("I create the payment transaction") - public void createPaytxn() throws NoSuchAlgorithmException, JsonProcessingException, IOException{ - txn = Transaction.PaymentTransactionBuilder() - .sender(pk) - .fee(fee) - .firstValid(fv) - .lastValid(lv) - .note(note) - .genesisID(gen) - .genesisHash(gh) - .amount(amt) - .receiver(to) - .closeRemainderTo(close) - .build(); - } - - @When("I create the key registration transaction") - public void createKeyregTxn() throws NoSuchAlgorithmException, JsonProcessingException, IOException{ - txn = Transaction.KeyRegistrationTransactionBuilder() - .sender(pk) - .fee(fee) - .firstValid(fv) - .lastValid(lv) - .note(note) - .genesisID(gen) - .genesisHash(gh) - .participationPublicKey(votepk) - .selectionPublicKey(vrfpk) - .voteFirst(votefst) - .voteLast(votelst) - .voteKeyDilution(votekd) - .build(); - } - @Given("default V2 key registration transaction {string}") public void default_v2_key_registration_transaction(String type) throws NoSuchAlgorithmException, JsonProcessingException, IOException{ getParams(); @@ -824,6 +768,7 @@ public void sendMsigTxn() throws JsonProcessingException, ApiException{ } } + // TODO: this needs to be modified/removed when v1 is no longer supported!!! @Then("the transaction should go through") public void checkTxn() throws Exception { String ans = acl.pendingTransactionInformation(txid).getFrom(); @@ -831,7 +776,7 @@ public void checkTxn() throws Exception { waitForAlgodTransactionProcessingToComplete(); String senderFromResponse = acl.transactionInformation(txn.sender.toString(), txid).getFrom(); assertThat(senderFromResponse).isEqualTo(txn.sender.toString()); - assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse); + // assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse); } /** @@ -843,12 +788,6 @@ private static void waitForAlgodTransactionProcessingToComplete() throws Excepti Thread.sleep(500); } - @Then("I can get the transaction by ID") - public void txnByID() throws Exception { - waitForAlgodTransactionProcessingToComplete(); - assertThat(acl.transaction(txid).getFrom()).isEqualTo(pk.toString()); - } - @Then("the transaction should not go through") public void txnFail() { assertThat(err).isTrue(); @@ -894,74 +833,6 @@ public void signMsigBothEqual() throws JsonProcessingException, com.algorand.alg kcl.deleteMultisig(req); } - @When("I read a transaction {string} from file {string}") - public void readTxn(String encodedTxn, String num) throws IOException { - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - this.num = num; - path = p.getParent() + "/temp/raw" + this.num + ".tx"; - FileInputStream inputStream = new FileInputStream(path); - File file = new File(path); - byte[] data = new byte[(int) file.length()]; - inputStream.read(data); - stx = Encoder.decodeFromMsgPack(data, SignedTransaction.class); - inputStream.close(); - } - - @When("I write the transaction to file") - public void writeTxn() throws JsonProcessingException, IOException{ - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - path = p.getParent() + "/temp/raw" + this.num + ".tx"; - byte[] data = Encoder.encodeToMsgPack(stx); - FileOutputStream out = new FileOutputStream(path); - out.write(data); - out.close(); - } - - @Then("the transaction should still be the same") - public void checkEnc() throws IOException{ - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - path = p.getParent() + "/temp/raw" + this.num + ".tx"; - FileInputStream inputStream = new FileInputStream(path); - File file = new File(path); - byte[] data = new byte[(int) file.length()]; - inputStream.read(data); - SignedTransaction stxnew = Encoder.decodeFromMsgPack(data, SignedTransaction.class); - inputStream.close(); - - path = p.getParent() + "/temp/old" + this.num + ".tx"; - inputStream = new FileInputStream(path); - file = new File(path); - data = new byte[(int) file.length()]; - inputStream.read(data); - SignedTransaction stxold = Encoder.decodeFromMsgPack(data, SignedTransaction.class); - inputStream.close(); - assertThat(stxnew).isEqualTo(stxold); - } - - @Then("I do my part") - public void signSaveTxn() throws IOException, JsonProcessingException, NoSuchAlgorithmException, com.algorand.algosdk.kmd.client.ApiException, Exception{ - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - path = p.getParent() + "/temp/txn.tx"; - FileInputStream inputStream = new FileInputStream(path); - File file = new File(path); - byte[] data = new byte[(int) file.length()]; - inputStream.read(data); - inputStream.close(); - - txn = Encoder.decodeFromMsgPack(data, Transaction.class); - exportKeyAndSetAccount(txn.sender); - - stx = account.signTransaction(txn); - data = Encoder.encodeToMsgPack(stx); - FileOutputStream out = new FileOutputStream(path); - out.write(data); - out.close(); - } - @Then("the node should be healthy") public void nodeHealth() throws ApiException{ acl.healthCheck(); @@ -979,20 +850,6 @@ public void txnsByAddrRound() throws ApiException{ //Assert.assertTrue(acl.transactions(addresses.get(0), BigInteger.valueOf(1), acl.getStatus().getLastRound(), null, null, BigInteger.valueOf(10)).getTransactions() instanceof List); } - @Then("I get transactions by address only") - public void txnsByAddrOnly() throws ApiException{ - assertThat(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(10)).getTransactions()) - .isInstanceOf(List.class); - //Assert.assertTrue(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(10)).getTransactions() instanceof List); - } - - @Then("I get transactions by address and date") - public void txnsByAddrDate() throws ApiException{ - assertThat(acl.transactions(addresses.get(0), null, null, LocalDate.now(), LocalDate.now(), BigInteger.valueOf(10)).getTransactions()) - .isInstanceOf(List.class); - //Assert.assertTrue(acl.transactions(addresses.get(0), null, null, LocalDate.now(), LocalDate.now(), BigInteger.valueOf(10)).getTransactions() instanceof List); - } - @Then("I get pending transactions") public void pendingTxns() throws ApiException{ assertThat(acl.getPendingTransactions(BigInteger.valueOf(10)).getTruncatedTxns()) @@ -1147,13 +1004,6 @@ public void newAccInfo() throws ApiException, NoSuchAlgorithmException, com.algo kcl.deleteKey(req); } - @When("I get recent transactions, limited by {int} transactions") - public void i_get_recent_transactions_limited_by_count(int cnt) throws ApiException { - assertThat(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(cnt)).getTransactions()) - .isInstanceOf(List.class); - //Assert.assertTrue(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(cnt)).getTransactions() instanceof List); - } - @Given("asset test fixture") public void asset_test_fixture() { // Implemented by the construction of Stepdefs; diff --git a/src/test/unit.tags b/src/test/unit.tags new file mode 100644 index 000000000..bfad8a343 --- /dev/null +++ b/src/test/unit.tags @@ -0,0 +1,25 @@ +@unit.abijson +@unit.abijson.byname +@unit.algod +@unit.algod.ledger_refactoring +@unit.applications +@unit.atomic_transaction_composer +@unit.dryrun +@unit.dryrun.trace.application +@unit.feetest +@unit.indexer +@unit.indexer.ledger_refactoring +@unit.indexer.logs +@unit.indexer.rekey +@unit.offline +@unit.rekey +@unit.responses +@unit.responses.231 +@unit.responses.messagepack +@unit.responses.messagepack.231 +@unit.responses.unlimited_assets +@unit.sourcemap +@unit.tealsign +@unit.transactions +@unit.transactions.keyreg +@unit.transactions.payment diff --git a/test-harness.sh b/test-harness.sh new file mode 100755 index 000000000..4878db0b9 --- /dev/null +++ b/test-harness.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +START=$(date "+%s") + +THIS=$(basename "$0") +ENV_FILE=".test-env" +TEST_DIR="src/test/resources/com/algorand/algosdk" +TEST_RESOURCES_DIR="src/test/resources/" + +set -a +source "$ENV_FILE" +set +a + +rootdir=$(dirname "$0") +pushd "$rootdir" + +## Reset test harness +if [ -d "$SDK_TESTING_HARNESS" ]; then + pushd "$SDK_TESTING_HARNESS" + ./scripts/down.sh + popd + rm -rf "$SDK_TESTING_HARNESS" +else + echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" +fi + +git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" + + +if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then + echo "$THIS: OVERWRITE replaced $SDK_TESTING_HARNESS/.env with $ENV_FILE:" + cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env +fi + +## Copy feature files into the project resources +if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then + echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features" + if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/integration && tree $TEST_DIR/unit && tree $TEST_RESOURCES_DIR && echo "$THIS: see the previous for files deleted" ) || true + fi + rm -rf $TEST_DIR/integration + rm -rf $TEST_DIR/unit + rm -rf $TEST_RESOURCES_DIR +fi +mkdir -p $TEST_DIR/integration +mkdir -p $TEST_DIR/unit +mkdir -p $TEST_RESOURCES_DIR + +# The Java implementation of these is too tightly coupled with the +# integration tests, so add them to the integration tests instead. +mv "$SDK_TESTING_HARNESS"/features/unit/offline.feature "$SDK_TESTING_HARNESS"/features/integration/ + +cp -r "$SDK_TESTING_HARNESS"/features/integration/* $TEST_DIR/integration +cp -r "$SDK_TESTING_HARNESS"/features/unit/* $TEST_DIR/unit +cp -r "$SDK_TESTING_HARNESS"/features/resources/* $TEST_RESOURCES_DIR + +if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/integration && tree $TEST_DIR/unit && tree $TEST_RESOURCES_DIR && echo "$THIS: see the previous for files copied over" ) || true +fi +echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" + +## Start test harness environment +pushd "$SDK_TESTING_HARNESS" +./scripts/up.sh +popd +echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s" +echo "" +echo "--------------------------------------------------------------------------------" +echo "|" +echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox " +echo "|" +echo "--------------------------------------------------------------------------------"