Skip to content

Commit 5e6fc3f

Browse files
authored
Introduce 'advanceChainAndWaitForBoxes' for application integration test suite to account for box persistence needs for paginated API. (#802)
1 parent 563a21d commit 5e6fc3f

File tree

2 files changed

+76
-10
lines changed

2 files changed

+76
-10
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- run: mvn test
2525
integration-test:
2626
machine:
27-
image: "ubuntu-2204:2022.10.2"
27+
image: "ubuntu-2404:2024.11.1"
2828
resource_class: medium
2929
steps:
3030
- checkout

src/test/java/com/algorand/algosdk/integration/Applications.java

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.algorand.algosdk.util.Digester;
1010
import com.algorand.algosdk.util.Encoder;
1111
import com.algorand.algosdk.v2.client.Utils;
12+
import com.algorand.algosdk.v2.client.common.AlgodClient;
1213
import com.algorand.algosdk.v2.client.common.Response;
1314
import com.algorand.algosdk.v2.client.model.*;
1415
import io.cucumber.java.en.Given;
@@ -290,23 +291,25 @@ private static void assertSetOfByteArraysEqual(Set<byte[]> expected, Set<byte[]>
290291

291292
@Then("according to {string}, the current application should have the following boxes {string}.")
292293
public void checkAppBoxes(String fromClient, String encodedBoxesRaw) throws Exception {
294+
final Set<byte[]> expectedNames = new HashSet<>();
295+
if (!encodedBoxesRaw.isEmpty()) {
296+
for (String s : encodedBoxesRaw.split(":")) {
297+
expectedNames.add(Encoder.decodeFromBase64(s));
298+
}
299+
}
300+
293301
Response<BoxesResponse> r;
294-
if (fromClient.equals("algod"))
302+
if (fromClient.equals("algod")) {
303+
advanceChainAndWaitForBoxes(base.aclv2, this.appId, this.transientAccount,
304+
expectedNames.size());
295305
r = base.aclv2.GetApplicationBoxes(this.appId).execute();
296-
else if (fromClient.equals("indexer"))
306+
} else if (fromClient.equals("indexer"))
297307
r = base.v2IndexerClient.searchForApplicationBoxes(this.appId).execute();
298308
else
299309
throw new IllegalArgumentException("expecting algod or indexer, got " + fromClient);
300310

301311
Assert.assertTrue(r.isSuccessful());
302312

303-
final Set<byte[]> expectedNames = new HashSet<>();
304-
if (!encodedBoxesRaw.isEmpty()) {
305-
for (String s : encodedBoxesRaw.split(":")) {
306-
expectedNames.add(Encoder.decodeFromBase64(s));
307-
}
308-
}
309-
310313
final Set<byte[]> actualNames = new HashSet<>();
311314
for (BoxDescriptor b : r.body().boxes) {
312315
actualNames.add(b.name);
@@ -315,6 +318,69 @@ else if (fromClient.equals("indexer"))
315318
assertSetOfByteArraysEqual(expectedNames, actualNames);
316319
}
317320

321+
/**
322+
* Advance the blockchain and wait for the expected number of application boxes to persist.
323+
*
324+
* @param algodClient Algod client for interacting with the blockchain.
325+
* @param appId Application ID to check for boxes.
326+
* @param sender Account sending dummy transactions.
327+
* @param expectedNumBoxes The expected number of boxes.
328+
* @throws Exception if the boxes do not persist within the specified attempts.
329+
*/
330+
public static void advanceChainAndWaitForBoxes(
331+
AlgodClient algodClient,
332+
long appId,
333+
TransientAccount sender,
334+
int expectedNumBoxes) throws Exception {
335+
336+
int waitRounds = 5;
337+
int maxAttempts = 50;
338+
// Get the current round
339+
long currentRound = algodClient.GetStatus().execute().body().lastRound;
340+
long targetRound = currentRound + waitRounds;
341+
342+
System.out.println("Current round: " + currentRound + ", waiting for round: " + targetRound);
343+
344+
int attempts = 0;
345+
while (attempts < maxAttempts) {
346+
// Send a dummy 0-Algo payment transaction to advance the blockchain
347+
TransactionParametersResponse params = algodClient.TransactionParams().execute().body();
348+
349+
Transaction txn = Transaction.PaymentTransactionBuilder()
350+
.sender(sender.transientAccount.getAddress())
351+
.amount(0)
352+
.receiver(sender.transientAccount.getAddress())
353+
.suggestedParams(params)
354+
.noteUTF8("Advance round for box persistence")
355+
.build();
356+
357+
SignedTransaction stxn = sender.transientAccount.signTransaction(txn);
358+
String txId = algodClient.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stxn)).execute().body().txId;
359+
360+
// Use Utils.waitForConfirmation to wait for the transaction to be confirmed
361+
Utils.waitForConfirmation(algodClient, txId, waitRounds);
362+
System.out.println("Dummy transaction confirmed. TxID: " + txId);
363+
364+
// Check the number of boxes
365+
Response<BoxesResponse> boxResponse = algodClient.GetApplicationBoxes(appId).execute();
366+
List<BoxDescriptor> boxes = boxResponse.body().boxes;
367+
368+
int actualNumBoxes = boxes != null ? boxes.size() : 0;
369+
System.out.println("Actual number of boxes: " + actualNumBoxes + ", Expected: " + expectedNumBoxes);
370+
371+
if (actualNumBoxes == expectedNumBoxes) {
372+
System.out.println("The expected number of boxes is now available.");
373+
return; // Exit once the condition is met
374+
}
375+
376+
// Wait for a second before the next attempt
377+
Thread.sleep(1000);
378+
attempts++;
379+
}
380+
381+
throw new Exception("Timeout waiting for " + expectedNumBoxes + " boxes to persist for application " + appId);
382+
}
383+
318384
@Then("according to {string}, with {long} being the parameter that limits results, the current application should have {int} boxes.")
319385
public void checkAppBoxesNum(String fromClient, Long limit, int expected_num) throws Exception {
320386
Response<BoxesResponse> r;

0 commit comments

Comments
 (0)