diff --git a/java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java b/java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java new file mode 100644 index 00000000..cee6b611 --- /dev/null +++ b/java-example/src/main/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnector.java @@ -0,0 +1,34 @@ +package org.onflow.examples.java.getAccountBalance; + +import org.onflow.flow.sdk.FlowAccessApi; +import org.onflow.flow.sdk.FlowAddress; + +public class GetAccountBalanceAccessAPIConnector { + private final FlowAccessApi accessAPI; + + public GetAccountBalanceAccessAPIConnector(FlowAccessApi accessAPI) { + this.accessAPI = accessAPI; + } + + public long getBalanceAtLatestBlock(FlowAddress address) { + FlowAccessApi.AccessApiCallResponse response = accessAPI.getAccountBalanceAtLatestBlock(address); + + if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) { + return ((FlowAccessApi.AccessApiCallResponse.Success) response).getData(); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response; + throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable()); + } + } + + public long getBalanceAtBlockHeight(FlowAddress address, long height) { + FlowAccessApi.AccessApiCallResponse response = accessAPI.getAccountBalanceAtBlockHeight(address, height); + + if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) { + return ((FlowAccessApi.AccessApiCallResponse.Success) response).getData(); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response; + throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable()); + } + } +} diff --git a/java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java b/java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java new file mode 100644 index 00000000..6b0eb15a --- /dev/null +++ b/java-example/src/test/java/org/onflow/examples/java/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.java @@ -0,0 +1,77 @@ +package org.onflow.examples.java.getAccountBalance; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onflow.flow.common.test.FlowEmulatorProjectTest; +import org.onflow.flow.common.test.FlowServiceAccountCredentials; +import org.onflow.flow.common.test.FlowTestClient; +import org.onflow.flow.common.test.TestAccount; +import org.onflow.flow.sdk.FlowAccessApi; +import org.onflow.flow.sdk.FlowAddress; +import org.onflow.flow.sdk.FlowBlock; + +@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json") +public class GetAccountBalanceAccessAPIConnectorTest { + + @FlowTestClient + private FlowAccessApi accessAPI; + + @FlowServiceAccountCredentials + private TestAccount serviceAccount; + + private GetAccountBalanceAccessAPIConnector balanceAPIConnector; + + @BeforeEach + public void setup() { + balanceAPIConnector = new GetAccountBalanceAccessAPIConnector(accessAPI); + } + + @Test + public void testCanFetchBalanceAtLatestBlock() { + FlowAddress address = serviceAccount.getFlowAddress(); + long balance = balanceAPIConnector.getBalanceAtLatestBlock(address); + + Assertions.assertTrue(balance >= 0, "Balance at the latest block should be non-negative"); + } + + @Test + public void testCanFetchBalanceAtSpecificBlockHeight() { + FlowAddress address = serviceAccount.getFlowAddress(); + + FlowAccessApi.AccessApiCallResponse latestBlockResponse = accessAPI.getLatestBlock(true); + + if (latestBlockResponse instanceof FlowAccessApi.AccessApiCallResponse.Success) { + FlowBlock latestBlock = ((FlowAccessApi.AccessApiCallResponse.Success) latestBlockResponse).getData(); + long blockHeight = latestBlock.getHeight(); + long balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight); + + Assertions.assertTrue(balanceAtHeight >= 0, "Balance at specific block height should be non-negative"); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) latestBlockResponse; + Assertions.fail("Failed to fetch the latest block: " + errorResponse.getMessage()); + } + } + + @Test + public void testBalancesAtLatestBlockAndSpecificHeightShouldMatch() { + FlowAddress address = serviceAccount.getFlowAddress(); + + long balanceAtLatest = balanceAPIConnector.getBalanceAtLatestBlock(address); + FlowAccessApi.AccessApiCallResponse latestBlockResponse = accessAPI.getLatestBlock(true); + + if (latestBlockResponse instanceof FlowAccessApi.AccessApiCallResponse.Success) { + FlowBlock latestBlock = ((FlowAccessApi.AccessApiCallResponse.Success) latestBlockResponse).getData(); + long blockHeight = latestBlock.getHeight(); + + // Fetch balance at the same block height + long balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight); + + // Ensure balances match + Assertions.assertEquals(balanceAtLatest, balanceAtHeight, "Balance at latest block and specific block height should match"); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) latestBlockResponse; + Assertions.fail("Failed to fetch the latest block: " + errorResponse.getMessage()); + } + } +} diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt new file mode 100644 index 00000000..89ce67cb --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnector.kt @@ -0,0 +1,19 @@ +package org.onflow.examples.kotlin.getAccountBalance + +import org.onflow.flow.sdk.* + +internal class GetAccountBalanceAccessAPIConnector( + private val accessAPI: FlowAccessApi +) { + fun getBalanceAtLatestBlock(address: FlowAddress): Long = + when (val response = accessAPI.getAccountBalanceAtLatestBlock(address)) { + is FlowAccessApi.AccessApiCallResponse.Success -> response.data + is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable) + } + + fun getBalanceAtBlockHeight(address: FlowAddress, height: Long): Long = + when (val response = accessAPI.getAccountBalanceAtBlockHeight(address, height)) { + is FlowAccessApi.AccessApiCallResponse.Success -> response.data + is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable) + } +} diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt new file mode 100644 index 00000000..923f07b6 --- /dev/null +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getAccountBalance/GetAccountBalanceAccessAPIConnectorTest.kt @@ -0,0 +1,73 @@ +package org.onflow.examples.kotlin.getAccountBalance + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.onflow.flow.common.test.FlowEmulatorProjectTest +import org.onflow.flow.common.test.FlowServiceAccountCredentials +import org.onflow.flow.common.test.FlowTestClient +import org.onflow.flow.common.test.TestAccount +import org.onflow.flow.sdk.FlowAccessApi + +@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json") +internal class GetAccountBalanceAccessAPIConnectorTest { + @FlowServiceAccountCredentials + lateinit var serviceAccount: TestAccount + + @FlowTestClient + lateinit var accessAPI: FlowAccessApi + + private lateinit var balanceAPIConnector: GetAccountBalanceAccessAPIConnector + + @BeforeEach + fun setup() { + balanceAPIConnector = GetAccountBalanceAccessAPIConnector(accessAPI) + } + + @Test + fun `Can fetch account balance at the latest block`() { + val address = serviceAccount.flowAddress + val balance = balanceAPIConnector.getBalanceAtLatestBlock(address) + + Assertions.assertNotNull(balance, "Balance should not be null") + Assertions.assertTrue(balance >= 0, "Balance should be non-negative") + } + + @Test + fun `Can fetch account balance at a specific block height`() { + val address = serviceAccount.flowAddress + val latestBlock = accessAPI.getLatestBlock(true) // Fetch the latest sealed block + + when (latestBlock) { + is FlowAccessApi.AccessApiCallResponse.Success -> { + val balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, latestBlock.data.height) + + Assertions.assertNotNull(balanceAtHeight, "Balance at specific block height should not be null") + Assertions.assertTrue(balanceAtHeight >= 0, "Balance at specific block height should be non-negative") + } + is FlowAccessApi.AccessApiCallResponse.Error -> Assertions.fail("Failed to retrieve the latest block: ${latestBlock.message}") + } + } + + @Test + fun `Balances at the latest block and specific block height should match`() { + val address = serviceAccount.flowAddress + + // Fetch balance at latest block + val balanceAtLatest = balanceAPIConnector.getBalanceAtLatestBlock(address) + + // Fetch latest block height + val latestBlock = accessAPI.getLatestBlock(true) + when (latestBlock) { + is FlowAccessApi.AccessApiCallResponse.Success -> { + val blockHeight = latestBlock.data.height + + // Fetch balance at the same block height + val balanceAtHeight = balanceAPIConnector.getBalanceAtBlockHeight(address, blockHeight) + + Assertions.assertEquals(balanceAtLatest, balanceAtHeight, "Balance at latest block and specific block height should match") + } + is FlowAccessApi.AccessApiCallResponse.Error -> Assertions.fail("Failed to retrieve the latest block: ${latestBlock.message}") + } + } +} diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt index 9471bc6b..ab5a504b 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt @@ -141,6 +141,75 @@ class TransactionIntegrationTest { assertThat(blockHeader.height).isEqualTo(latestBlock.height) } + @Test + fun `Can get account balance at latest block`() { + val address = serviceAccount.flowAddress + + val balanceResponse = try { + handleResult( + accessAPI.getAccountBalanceAtLatestBlock(address), + "Failed to get account balance at latest block" + ) + } catch (e: Exception) { + fail("Failed to retrieve account balance at latest block: ${e.message}") + } + + assertThat(balanceResponse).isNotNull + + val account = try { + handleResult( + accessAPI.getAccountAtLatestBlock(address), + "Failed to get account at latest block" + ) + } catch (e: Exception) { + fail("Failed to retrieve account at latest block: ${e.message}") + } + + val normalizedBalance = balanceResponse / 100_000_000L + + assertThat(normalizedBalance).isEqualTo(account.balance.toBigInteger().longValueExact()) + } + + @Test + fun `Can get account balance at block height`() { + val address = serviceAccount.flowAddress + + val latestBlock = try { + handleResult( + accessAPI.getLatestBlock(true), + "Failed to get latest block" + ) + } catch (e: Exception) { + fail("Failed to retrieve latest block: ${e.message}") + } + + val height = latestBlock.height + + val balanceResponse = try { + handleResult( + accessAPI.getAccountBalanceAtBlockHeight(address, height), + "Failed to get account balance at block height" + ) + } catch (e: Exception) { + fail("Failed to retrieve account balance at block height: ${e.message}") + } + + assertThat(balanceResponse).isNotNull + + val account = try { + handleResult( + accessAPI.getAccountByBlockHeight(address, height), + "Failed to get account by block height" + ) + } catch (e: Exception) { + fail("Failed to retrieve account by block height: ${e.message}") + } + + val normalizedBalance = balanceResponse / 100_000_000L + + assertThat(normalizedBalance).isEqualTo(account.balance.toBigInteger().longValueExact()) + } + @Test fun `Can get latest block`() { val latestBlock = try { diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index 78be56d3..edf22143 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -14,6 +14,10 @@ interface AsyncFlowAccessApi { fun getLatestBlock(sealed: Boolean = true): CompletableFuture> + fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture> + + fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture> + fun getBlockById(id: FlowId): CompletableFuture> fun getBlockByHeight(height: Long): CompletableFuture> diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index f18239c3..200cda25 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -26,6 +26,10 @@ interface FlowAccessApi { fun getLatestBlock(sealed: Boolean = true): AccessApiCallResponse + fun getAccountBalanceAtLatestBlock(address: FlowAddress): AccessApiCallResponse + + fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): AccessApiCallResponse + fun getBlockById(id: FlowId): AccessApiCallResponse fun getBlockByHeight(height: Long): AccessApiCallResponse diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index f5fd81e1..a2366fe2 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -152,6 +152,57 @@ class AsyncFlowAccessApiImpl( } } + override fun getAccountBalanceAtLatestBlock(address: FlowAddress): CompletableFuture> { + return try { + completableFuture( + try { + api.getAccountBalanceAtLatestBlock( + Access.GetAccountBalanceAtLatestBlockRequest + .newBuilder() + .setAddress(address.byteStringValue) + .build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.balance) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e)) + } + } + + override fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): CompletableFuture> { + return try { + completableFuture( + try { + api.getAccountBalanceAtBlockHeight( + Access.GetAccountBalanceAtBlockHeightRequest + .newBuilder() + .setAddress(address.byteStringValue) + .setBlockHeight(height) + .build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.balance) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e)) + } + } + override fun getBlockById(id: FlowId): CompletableFuture> { return try { completableFuture( diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 751cb94e..256ecbc7 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -96,6 +96,33 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest block", e) } + override fun getAccountBalanceAtLatestBlock(address: FlowAddress): FlowAccessApi.AccessApiCallResponse = + try { + val ret = api.getAccountBalanceAtLatestBlock( + Access.GetAccountBalanceAtLatestBlockRequest + .newBuilder() + .setAddress(address.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.balance) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", e) + } + + override fun getAccountBalanceAtBlockHeight(address: FlowAddress, height: Long): FlowAccessApi.AccessApiCallResponse = + try { + val ret = api.getAccountBalanceAtBlockHeight( + Access.GetAccountBalanceAtBlockHeightRequest + .newBuilder() + .setAddress(address.byteStringValue) + .setBlockHeight(height) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.balance) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", e) + } + override fun getBlockById(id: FlowId): FlowAccessApi.AccessApiCallResponse = try { val ret = api.getBlockByID( diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt index 5e6a0b67..981b4067 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -1201,27 +1201,80 @@ data class FlowCollectionGuarantee( } data class FlowBlockSeal( - val id: FlowId, + val blockId: FlowId, val executionReceiptId: FlowId, val executionReceiptSignatures: List, - val resultApprovalSignatures: List + val resultApprovalSignatures: List, + val finalState: ByteArray, + val resultId: FlowId, + val aggregatedApprovalSigs: List, ) : Serializable { companion object { @JvmStatic fun of(value: BlockSealOuterClass.BlockSeal) = FlowBlockSeal( - id = FlowId.of(value.blockId.toByteArray()), + blockId = FlowId.of(value.blockId.toByteArray()), executionReceiptId = FlowId.of(value.executionReceiptId.toByteArray()), executionReceiptSignatures = value.executionReceiptSignaturesList.map { FlowSignature(it.toByteArray()) }, - resultApprovalSignatures = value.executionReceiptSignaturesList.map { FlowSignature(it.toByteArray()) } + resultApprovalSignatures = value.resultApprovalSignaturesList.map { FlowSignature(it.toByteArray()) }, + finalState = value.finalState.toByteArray(), + resultId = FlowId.of(value.resultId.toByteArray()), + aggregatedApprovalSigs = value.aggregatedApprovalSigsList.map { FlowAggregatedSignature.of(it) }, ) } @JvmOverloads fun builder(builder: BlockSealOuterClass.BlockSeal.Builder = BlockSealOuterClass.BlockSeal.newBuilder()): BlockSealOuterClass.BlockSeal.Builder = builder - .setBlockId(id.byteStringValue) + .setBlockId(blockId.byteStringValue) .setExecutionReceiptId(executionReceiptId.byteStringValue) .addAllExecutionReceiptSignatures(executionReceiptSignatures.map { it.byteStringValue }) .addAllResultApprovalSignatures(resultApprovalSignatures.map { it.byteStringValue }) + .setFinalState(UnsafeByteOperations.unsafeWrap(finalState)) + .setResultId(resultId.byteStringValue) + .addAllAggregatedApprovalSigs(aggregatedApprovalSigs.map { it.builder().build() }) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowBlockSeal) return false + + if (blockId != other.blockId) return false + if (executionReceiptId != other.executionReceiptId) return false + if (executionReceiptSignatures != other.executionReceiptSignatures) return false + if (resultApprovalSignatures != other.resultApprovalSignatures) return false + if (!finalState.contentEquals(other.finalState)) return false + if (resultId != other.resultId) return false + if (aggregatedApprovalSigs != other.aggregatedApprovalSigs) return false + + return true + } + + override fun hashCode(): Int { + var result = blockId.hashCode() + result = 31 * result + executionReceiptId.hashCode() + result = 31 * result + executionReceiptSignatures.hashCode() + result = 31 * result + resultApprovalSignatures.hashCode() + result = 31 * result + finalState.contentHashCode() + result = 31 * result + resultId.hashCode() + result = 31 * result + aggregatedApprovalSigs.hashCode() + return result + } +} + +data class FlowAggregatedSignature( + val verifierSignatures: List, + val signerIds: List, +) : Serializable { + companion object { + @JvmStatic + fun of(value: BlockSealOuterClass.AggregatedSignature) = FlowAggregatedSignature( + verifierSignatures = value.verifierSignaturesList.map { FlowSignature(it.toByteArray()) }, + signerIds = value.signerIdsList.map { FlowId.of(it.toByteArray()) } + ) + } + + @JvmOverloads + fun builder(builder: BlockSealOuterClass.AggregatedSignature.Builder = BlockSealOuterClass.AggregatedSignature.newBuilder()): BlockSealOuterClass.AggregatedSignature.Builder = builder + .addAllVerifierSignatures(verifierSignatures.map { it.byteStringValue }) + .addAllSignerIds(signerIds.map { it.byteStringValue }) } data class FlowCollection( @@ -1467,15 +1520,17 @@ data class FlowNodeVersionInfo( .setProtocolVersion(protocolVersion) .setSporkRootBlockHeight(sporkRootBlockHeight) .setNodeRootBlockHeight(nodeRootBlockHeight) - .setCompatibleRange( + .apply { compatibleRange?.let { - NodeVersionInfoOuterClass.CompatibleRange - .newBuilder() - .setStartHeight(it.startHeight) - .setEndHeight(it.endHeight) - .build() + setCompatibleRange( + NodeVersionInfoOuterClass.CompatibleRange + .newBuilder() + .setStartHeight(it.startHeight) + .setEndHeight(it.endHeight) + .build() + ) } - ) + } override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 9c825ea8..1cd29158 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -88,6 +88,68 @@ class FlowAccessApiTest { assertEquals(FlowAccessApi.AccessApiCallResponse.Success(block), result) } + @Test + fun `Test getAccountBalanceAtLatestBlock success`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val expectedBalance = 1000L + val response = FlowAccessApi.AccessApiCallResponse.Success(expectedBalance) + + `when`(flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtLatestBlock(flowAddress) + } + + @Test + fun `Test getAccountBalanceAtLatestBlock failure`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val exception = RuntimeException("Test exception") + val response = FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at latest block", exception) + + `when`(flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtLatestBlock(flowAddress) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtLatestBlock(flowAddress) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight success`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val expectedBalance = 1000L + val response = FlowAccessApi.AccessApiCallResponse.Success(expectedBalance) + + `when`(flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight failure`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val exception = RuntimeException("Test exception") + val response = FlowAccessApi.AccessApiCallResponse.Error("Failed to get account balance at block height", exception) + + `when`(flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight)).thenReturn(response) + + val result = flowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + + assertEquals(response, result) + verify(flowAccessApi).getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + } + @Test fun `Test getCollectionById`() { val flowAccessApi = mock(FlowAccessApi::class.java) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index a0c98d00..24c4b0c8 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -205,6 +205,70 @@ class AsyncFlowAccessApiImplTest { assertEquals(mockBlock, result.data) } + @Test + fun `test getAccountBalanceAtLatestBlock success`() { + val flowAddress = FlowAddress("01") + val expectedBalance = 1000L + val balanceResponse = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(api.getAccountBalanceAtLatestBlock(any())).thenReturn(setupFutureMock(balanceResponse)) + + val result = asyncFlowAccessApi.getAccountBalanceAtLatestBlock(flowAddress).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(expectedBalance, result.data) + } + + @Test + fun `test getAccountBalanceAtLatestBlock failure`() { + val flowAddress = FlowAddress("01") + val exception = RuntimeException("Test exception") + + `when`(api.getAccountBalanceAtLatestBlock(any())).thenThrow(exception) + + val result = asyncFlowAccessApi.getAccountBalanceAtLatestBlock(flowAddress).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Error) + result as FlowAccessApi.AccessApiCallResponse.Error + assertEquals("Failed to get account balance at latest block", result.message) + assertEquals(exception, result.throwable) + } + + @Test + fun `test getAccountBalanceAtBlockHeight success`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val expectedBalance = 1000L + val balanceResponse = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(api.getAccountBalanceAtBlockHeight(any())).thenReturn(setupFutureMock(balanceResponse)) + + val result = asyncFlowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(expectedBalance, result.data) + } + + @Test + fun `test getAccountBalanceAtBlockHeight failure`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val exception = RuntimeException("Test exception") + + `when`(api.getAccountBalanceAtBlockHeight(any())).thenThrow(exception) + + val result = asyncFlowAccessApi.getAccountBalanceAtBlockHeight(flowAddress, blockHeight).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Error) + result as FlowAccessApi.AccessApiCallResponse.Error + assertEquals("Failed to get account balance at block height", result.message) + assertEquals(exception, result.throwable) + } + @Test fun `test getCollectionById`() { val collectionId = FlowId("01") diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 1a0afc68..743e1656 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -163,6 +163,81 @@ class FlowAccessApiImplTest { assertResultSuccess(result) { assertEquals(mockBlock, it) } } + @Test + fun `Test getAccountBalanceAtLatestBlock success`() { + val flowAddress = FlowAddress("01") + val expectedBalance = 1000L + val response = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(mockApi.getAccountBalanceAtLatestBlock(any())).thenReturn(response) + + val result = flowAccessApiImpl.getAccountBalanceAtLatestBlock(flowAddress) + assertResultSuccess(result) { assertEquals(expectedBalance, it) } + + verify(mockApi).getAccountBalanceAtLatestBlock( + Access.GetAccountBalanceAtLatestBlockRequest + .newBuilder() + .setAddress(flowAddress.byteStringValue) + .build() + ) + } + + @Test + fun `Test getAccountBalanceAtLatestBlock failure`() { + val flowAddress = FlowAddress("01") + val exception = RuntimeException("Test exception") + + `when`(mockApi.getAccountBalanceAtLatestBlock(any())).thenThrow(exception) + + val result = flowAccessApiImpl.getAccountBalanceAtLatestBlock(flowAddress) + + assertTrue(result is FlowAccessApi.AccessApiCallResponse.Error) + assertEquals("Failed to get account balance at latest block", (result as FlowAccessApi.AccessApiCallResponse.Error).message) + assertEquals(exception, result.throwable) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight success`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val expectedBalance = 1000L + val response = Access.AccountBalanceResponse + .newBuilder() + .setBalance(expectedBalance) + .build() + + `when`(mockApi.getAccountBalanceAtBlockHeight(any())).thenReturn(response) + + val result = flowAccessApiImpl.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + assertResultSuccess(result) { assertEquals(expectedBalance, it) } + + verify(mockApi).getAccountBalanceAtBlockHeight( + Access.GetAccountBalanceAtBlockHeightRequest + .newBuilder() + .setAddress(flowAddress.byteStringValue) + .setBlockHeight(blockHeight) + .build() + ) + } + + @Test + fun `Test getAccountBalanceAtBlockHeight failure`() { + val flowAddress = FlowAddress("01") + val blockHeight = 123L + val exception = RuntimeException("Test exception") + + `when`(mockApi.getAccountBalanceAtBlockHeight(any())).thenThrow(exception) + + val result = flowAccessApiImpl.getAccountBalanceAtBlockHeight(flowAddress, blockHeight) + + assertTrue(result is FlowAccessApi.AccessApiCallResponse.Error) + assertEquals("Failed to get account balance at block height", (result as FlowAccessApi.AccessApiCallResponse.Error).message) + assertEquals(exception, result.throwable) + } + @Test fun `Test getCollectionById`() { val collectionId = FlowId("01") diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt new file mode 100644 index 00000000..d9afd4e2 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAggregatedSignatureTest.kt @@ -0,0 +1,89 @@ +package org.onflow.flow.sdk.models + +import org.onflow.flow.sdk.FlowAggregatedSignature +import org.onflow.flow.sdk.FlowId +import org.onflow.flow.sdk.FlowSignature +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.onflow.protobuf.entities.BlockSealOuterClass + +class FlowAggregatedSignatureTest { + @Test + fun `test FlowAggregatedSignature equals and hashCode`() { + val signature1 = FlowSignature("signature1".toByteArray()) + val signature2 = FlowSignature("signature2".toByteArray()) + + val signerId1 = FlowId.of("signerId1".toByteArray()) + val signerId2 = FlowId.of("signerId2".toByteArray()) + + val aggregatedSignature1 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(signerId1) + ) + + val aggregatedSignature2 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(signerId1) + ) + + val aggregatedSignature3 = FlowAggregatedSignature( + verifierSignatures = listOf(signature2), + signerIds = listOf(signerId2) + ) + + // Test equality + assertEquals(aggregatedSignature1, aggregatedSignature2) + assertNotEquals(aggregatedSignature1, aggregatedSignature3) + + // Test hashCode + assertEquals(aggregatedSignature1.hashCode(), aggregatedSignature2.hashCode()) + assertNotEquals(aggregatedSignature1.hashCode(), aggregatedSignature3.hashCode()) + } + + @Test + fun `test FlowAggregatedSignature of function`() { + // Mock BlockSealOuterClass.AggregatedSignature + val aggregatedSignatureProto = Mockito.mock(BlockSealOuterClass.AggregatedSignature::class.java) + + val signatureBytes = "signature".toByteArray() + val signerIdBytes = "signerId".toByteArray() + + Mockito.`when`(aggregatedSignatureProto.verifierSignaturesList).thenReturn( + listOf( + com.google.protobuf.ByteString + .copyFrom(signatureBytes) + ) + ) + Mockito.`when`(aggregatedSignatureProto.signerIdsList).thenReturn( + listOf( + com.google.protobuf.ByteString + .copyFrom(signerIdBytes) + ) + ) + + val flowAggregatedSignature = FlowAggregatedSignature.of(aggregatedSignatureProto) + + assertEquals(1, flowAggregatedSignature.verifierSignatures.size) + assertEquals(FlowSignature(signatureBytes), flowAggregatedSignature.verifierSignatures[0]) + + assertEquals(1, flowAggregatedSignature.signerIds.size) + assertEquals(FlowId.of(signerIdBytes), flowAggregatedSignature.signerIds[0]) + } + + @Test + fun `test FlowAggregatedSignature builder function`() { + val signature1 = FlowSignature("signature1".toByteArray()) + val signerId1 = FlowId.of("signerId1".toByteArray()) + + val aggregatedSignature = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(signerId1) + ) + + val builderResult = aggregatedSignature.builder() + + assertEquals(listOf(signature1.byteStringValue), builderResult.verifierSignaturesList) + assertEquals(listOf(signerId1.byteStringValue), builderResult.signerIdsList) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt new file mode 100644 index 00000000..1de6d035 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockSealTest.kt @@ -0,0 +1,140 @@ +package org.onflow.flow.sdk.models + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.onflow.flow.sdk.FlowAggregatedSignature +import org.onflow.flow.sdk.FlowBlockSeal +import org.onflow.flow.sdk.FlowId +import org.onflow.flow.sdk.FlowSignature +import org.onflow.protobuf.entities.BlockSealOuterClass + +class FlowBlockSealTest { + @Test + fun `test FlowBlockSeal equals and hashCode`() { + val blockId = FlowId.of("blockId".toByteArray()) + val executionReceiptId = FlowId.of("executionReceiptId".toByteArray()) + val resultId = FlowId.of("resultId".toByteArray()) + val finalState = "finalState".toByteArray() + + val signature1 = FlowSignature("signature1".toByteArray()) + val signature2 = FlowSignature("signature2".toByteArray()) + + val aggregatedSignature1 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(blockId) + ) + + val seal1 = FlowBlockSeal( + blockId = blockId, + executionReceiptId = executionReceiptId, + executionReceiptSignatures = listOf(signature1), + resultApprovalSignatures = listOf(signature2), + finalState = finalState, + resultId = resultId, + aggregatedApprovalSigs = listOf(aggregatedSignature1) + ) + + val seal2 = FlowBlockSeal( + blockId = blockId, + executionReceiptId = executionReceiptId, + executionReceiptSignatures = listOf(signature1), + resultApprovalSignatures = listOf(signature2), + finalState = finalState, + resultId = resultId, + aggregatedApprovalSigs = listOf(aggregatedSignature1) + ) + + // Testing equals + assertEquals(seal1, seal2) + + // Testing hashCode + assertEquals(seal1.hashCode(), seal2.hashCode()) + } + + @Test + fun `test FlowBlockSeal of function`() { + // Mock BlockSealOuterClass.BlockSeal + val blockSealProto = Mockito.mock(BlockSealOuterClass.BlockSeal::class.java) + + val blockIdBytes = "blockId".toByteArray() + val executionReceiptIdBytes = "executionReceiptId".toByteArray() + val resultIdBytes = "resultId".toByteArray() + val finalStateBytes = "finalState".toByteArray() + + Mockito.`when`(blockSealProto.blockId).thenReturn( + com.google.protobuf.ByteString + .copyFrom(blockIdBytes) + ) + Mockito.`when`(blockSealProto.executionReceiptId).thenReturn( + com.google.protobuf.ByteString + .copyFrom(executionReceiptIdBytes) + ) + Mockito.`when`(blockSealProto.resultId).thenReturn( + com.google.protobuf.ByteString + .copyFrom(resultIdBytes) + ) + Mockito.`when`(blockSealProto.finalState).thenReturn( + com.google.protobuf.ByteString + .copyFrom(finalStateBytes) + ) + Mockito.`when`(blockSealProto.executionReceiptSignaturesList).thenReturn( + listOf( + com.google.protobuf.ByteString + .copyFrom("signature1".toByteArray()) + ) + ) + Mockito.`when`(blockSealProto.resultApprovalSignaturesList).thenReturn( + listOf( + com.google.protobuf.ByteString + .copyFrom("signature2".toByteArray()) + ) + ) + Mockito.`when`(blockSealProto.aggregatedApprovalSigsList).thenReturn( + listOf( + BlockSealOuterClass.AggregatedSignature.newBuilder().build() + ) + ) + + val flowBlockSeal = FlowBlockSeal.of(blockSealProto) + + assertEquals(FlowId.of(blockIdBytes), flowBlockSeal.blockId) + assertEquals(FlowId.of(executionReceiptIdBytes), flowBlockSeal.executionReceiptId) + assertEquals(FlowId.of(resultIdBytes), flowBlockSeal.resultId) + assertArrayEquals(finalStateBytes, flowBlockSeal.finalState) + } + + @Test + fun `test FlowBlockSeal builder function`() { + val blockId = FlowId.of("blockId".toByteArray()) + val executionReceiptId = FlowId.of("executionReceiptId".toByteArray()) + val resultId = FlowId.of("resultId".toByteArray()) + val finalState = "finalState".toByteArray() + + val signature1 = FlowSignature("signature1".toByteArray()) + val signature2 = FlowSignature("signature2".toByteArray()) + val aggregatedSignature1 = FlowAggregatedSignature( + verifierSignatures = listOf(signature1), + signerIds = listOf(blockId) + ) + + val seal = FlowBlockSeal( + blockId = blockId, + executionReceiptId = executionReceiptId, + executionReceiptSignatures = listOf(signature1), + resultApprovalSignatures = listOf(signature2), + finalState = finalState, + resultId = resultId, + aggregatedApprovalSigs = listOf(aggregatedSignature1) + ) + + val builderResult = seal.builder() + + assertEquals(blockId.byteStringValue, builderResult.blockId) + assertEquals(executionReceiptId.byteStringValue, builderResult.executionReceiptId) + assertEquals(resultId.byteStringValue, builderResult.resultId) + assertEquals(finalState.toList(), builderResult.finalState.toByteArray().toList()) + assertEquals(listOf(signature1.byteStringValue), builderResult.executionReceiptSignaturesList) + assertEquals(listOf(signature2.byteStringValue), builderResult.resultApprovalSignaturesList) + } +}