diff --git a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index eb5bd94..4b6ce90 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -49,4 +49,10 @@ interface AsyncFlowAccessApi { fun getNetworkParameters(): CompletableFuture> fun getLatestProtocolStateSnapshot(): CompletableFuture> + + fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> + + fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> + + fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> } diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 7dab538..6ffb564 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -53,4 +53,10 @@ interface FlowAccessApi { fun getNetworkParameters(): AccessApiCallResponse fun getLatestProtocolStateSnapshot(): AccessApiCallResponse + + fun getTransactionsByBlockId(id: FlowId): AccessApiCallResponse> + + fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> + + fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index 0daadc4..405e3ac 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -513,6 +513,74 @@ class AsyncFlowAccessApiImpl( CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e)) } } + + override fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> { + return try { + completableFuture( + try { + api.getTransactionsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder().setBlockId(id.byteStringValue).build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.transactionsList.map { FlowTransaction.of(it) }) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e)) + } + } + + override fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> { + return try { + completableFuture( + try { + api.getTransactionResultsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder().setBlockId(id.byteStringValue).build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.transactionResultsList.map { FlowTransactionResult.of(it) }) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e)) + } + } + + override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { + return try { + completableFuture( + try { + api.getExecutionResultByID(Access.GetExecutionResultByIDRequest.newBuilder().setId(id.byteStringValue).build()) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", ex) + } else { + if (response.hasExecutionResult()) { + FlowAccessApi.AccessApiCallResponse.Success(FlowExecutionResult.of(response)) + } else { + FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") + } + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e)) + } + } } fun completableFuture(future: ListenableFuture): CompletableFuture { diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 2fbf415..9363142 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -335,4 +335,47 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e) } } + + override fun getTransactionsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { + return try { + val ret = api.getTransactionsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder() + .setBlockId(id.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.transactionsList.map { FlowTransaction.of(it) }) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e) + } + } + + override fun getTransactionResultsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { + return try { + val ret = api.getTransactionResultsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder() + .setBlockId(id.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.transactionResultsList.map { FlowTransactionResult.of(it) }) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e) + } + } + + override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { + return try { + val ret = api.getExecutionResultByID( + Access.GetExecutionResultByIDRequest.newBuilder() + .setId(id.byteStringValue) + .build() + ) + if (ret.hasExecutionResult()) { + FlowAccessApi.AccessApiCallResponse.Success(FlowExecutionResult.of(ret)) + } else { + FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") + } + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e) + } + } } diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index 502f8a4..e135dc1 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -675,6 +675,110 @@ data class FlowBlock( } } +data class FlowChunk( + val collectionIndex: Int, + val startState: ByteArray, + val eventCollection: ByteArray, + val blockId: FlowId, + val totalComputationUsed: Long, + val numberOfTransactions: Int, + val index: Long, + val endState: ByteArray, + val executionDataId: FlowId, + val stateDeltaCommitment: ByteArray, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.Chunk) = FlowChunk( + collectionIndex = grpcExecutionResult.collectionIndex, + startState = grpcExecutionResult.startState.toByteArray(), + eventCollection = grpcExecutionResult.eventCollection.toByteArray(), + blockId = FlowId.of(grpcExecutionResult.blockId.toByteArray()), + totalComputationUsed = grpcExecutionResult.totalComputationUsed, + numberOfTransactions = grpcExecutionResult.numberOfTransactions, + index = grpcExecutionResult.index, + endState = grpcExecutionResult.endState.toByteArray(), + executionDataId = FlowId.of(grpcExecutionResult.executionDataId.toByteArray()), + stateDeltaCommitment = grpcExecutionResult.stateDeltaCommitment.toByteArray() + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowChunk) return false + + if (collectionIndex != other.collectionIndex) return false + if (!startState.contentEquals(other.startState)) return false + if (!eventCollection.contentEquals(other.eventCollection)) return false + if (blockId != other.blockId) return false + if (totalComputationUsed != other.totalComputationUsed) return false + if (numberOfTransactions != other.numberOfTransactions) return false + if (index != other.index) return false + if (!endState.contentEquals(other.endState)) return false + if (executionDataId != other.executionDataId) return false + if (!stateDeltaCommitment.contentEquals(other.stateDeltaCommitment)) return false + + return true + } + + override fun hashCode(): Int { + var result = collectionIndex + result = 31 * result + startState.contentHashCode() + result = 31 * result + eventCollection.contentHashCode() + result = 31 * result + blockId.hashCode() + result = 31 * result + totalComputationUsed.hashCode() + result = 31 * result + numberOfTransactions + result = 31 * result + index.hashCode() + result = 31 * result + endState.contentHashCode() + result = 31 * result + executionDataId.hashCode() + result = 31 * result + stateDeltaCommitment.contentHashCode() + return result + } +} + +data class FlowServiceEvent( + val type: String, + val payload: ByteArray, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.ServiceEvent) = FlowServiceEvent( + type = grpcExecutionResult.type, + payload = grpcExecutionResult.payload.toByteArray(), + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowServiceEvent) return false + + if (type != other.type) return false + if (!payload.contentEquals(other.payload)) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + payload.contentHashCode() + return result + } +} + +data class FlowExecutionResult( + val blockId: FlowId, + val previousResultId: FlowId, + val chunks: List, + val serviceEvents: List, +) : Serializable { + companion object { + fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = FlowExecutionResult( + blockId = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), + previousResultId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()), + chunks = grpcExecutionResult.executionResult.chunksList.map { FlowChunk.of(it) }, + serviceEvents = grpcExecutionResult.executionResult.serviceEventsList.map { FlowServiceEvent.of(it) }, + ) + } +} + data class FlowCollectionGuarantee( val id: FlowId, val signatures: List diff --git a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 463ef6c..4e37df7 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -243,4 +243,84 @@ class FlowAccessApiTest { assertEquals(FlowAccessApi.AccessApiCallResponse.Success(snapshot), result) } + + @Test + fun `Test getTransactionsByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + `when`(flowAccessApi.getTransactionsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + } + + @Test + fun `Test getTransactionsByBlockId with multiple results`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + + val transaction1 = FlowTransaction(FlowScript("script1"), emptyList(), FlowId.of("01".toByteArray()), 123L, FlowTransactionProposalKey(FlowAddress("02"), 1, 123L), FlowAddress("02"), emptyList()) + + val transaction2 = FlowTransaction(FlowScript("script2"), emptyList(), FlowId.of("02".toByteArray()), 456L, FlowTransactionProposalKey(FlowAddress("03"), 2, 456L), FlowAddress("03"), emptyList()) + + val transactions = listOf(transaction1, transaction2) + + `when`(flowAccessApi.getTransactionsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + + assertEquals(2, transactions.size) + assertEquals(transaction1, transactions[0]) + assertEquals(transaction2, transactions[1]) + } + + @Test + fun `Test getTransactionResultsByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + `when`(flowAccessApi.getTransactionResultsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactionResults)) + + val result = flowAccessApi.getTransactionResultsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactionResults), result) + } + + @Test + fun `Test getTransactionResultsByBlockId with multiple results`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + + val transactionResult1 = FlowTransactionResult(FlowTransactionStatus.SEALED, 1, "message1", emptyList()) + + val transactionResult2 = FlowTransactionResult(FlowTransactionStatus.SEALED, 2, "message2", emptyList()) + + val transactions = listOf(transactionResult1, transactionResult2) + + `when`(flowAccessApi.getTransactionResultsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionResultsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + + assertEquals(2, FlowAccessApi.AccessApiCallResponse.Success(transactions).data.size) + assertEquals(transactionResult1, FlowAccessApi.AccessApiCallResponse.Success(transactions).data[0]) + assertEquals(transactionResult2, FlowAccessApi.AccessApiCallResponse.Success(transactions).data[1]) + } + + @Test + fun `Test getExecutionResultByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val executionResult = FlowExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) + `when`(flowAccessApi.getExecutionResultByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(executionResult)) + + val result = flowAccessApi.getExecutionResultByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(executionResult), result) + } } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 154e7a5..5645677 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -5,20 +5,30 @@ import com.google.common.util.concurrent.SettableFuture import com.google.protobuf.ByteString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.onflow.flow.sdk.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc +import org.onflow.protobuf.entities.ExecutionResultOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.math.BigDecimal import java.time.LocalDateTime +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException class AsyncFlowAccessApiImplTest { private val api = mock(AccessAPIGrpc.AccessAPIFutureStub::class.java) private val asyncFlowAccessApi = AsyncFlowAccessApiImpl(api) + companion object { + val BLOCK_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1) + val PARENT_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2) + } + private fun setupFutureMock(response: T): ListenableFuture { val future: ListenableFuture = SettableFuture.create() (future as SettableFuture).set(response) @@ -305,4 +315,131 @@ class AsyncFlowAccessApiImplTest { result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(mockFlowSnapshot, result.data) } + + @Test + fun `test getTransactionsByBlockId`() { + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + `when`(api.getTransactionsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(transactions, result.data) + } + + @Test + fun `test getTransactionsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transaction1 = FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance()) + val transaction2 = FlowTransaction.of(TransactionOuterClass.Transaction.newBuilder().setReferenceBlockId(ByteString.copyFromUtf8("02")).build()) + val transactions = listOf(transaction1, transaction2) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + `when`(api.getTransactionsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(2, result.data.size) + assertEquals(transaction1, result.data[0]) + assertEquals(transaction2, result.data[1]) + } + + @Test + fun `test getTransactionResultsByBlockId`() { + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + `when`(api.getTransactionResultsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionResultsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(transactionResults, result.data) + } + + @Test + fun `test getTransactionResultsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) + val transactionResults = listOf(transactionResult1, transactionResult2) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + `when`(api.getTransactionResultsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionResultsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(2, result.data.size) + assertEquals(transactionResult1, result.data[0]) + assertEquals(transactionResult2, result.data[1]) + } + + @Test + fun `test getExecutionResultByBlockId`() { + val blockId = FlowId("01") + + val chunks = listOf(FlowChunk(collectionIndex = 1, startState = ByteArray(0), eventCollection = ByteArray(0), blockId = FlowId("01"), totalComputationUsed = 1000L, numberOfTransactions = 10, index = 1L, endState = ByteArray(0), executionDataId = FlowId("02"), stateDeltaCommitment = ByteArray(0))) + + val serviceEvents = listOf(FlowServiceEvent(type = "ServiceEventType", payload = ByteArray(0))) + + val executionResult = FlowExecutionResult(blockId = FlowId("01"), previousResultId = FlowId("02"), chunks = chunks, serviceEvents = serviceEvents) + + val grpcChunks = chunks.map { + ExecutionResultOuterClass.Chunk.newBuilder() + .setCollectionIndex(it.collectionIndex) + .setStartState(ByteString.copyFrom(it.startState)) + .setEventCollection(ByteString.copyFrom(it.eventCollection)) + .setBlockId(ByteString.copyFrom(it.blockId.bytes)) + .setTotalComputationUsed(it.totalComputationUsed) + .setNumberOfTransactions(it.numberOfTransactions) + .setIndex(it.index) + .setEndState(ByteString.copyFrom(it.endState)) + .setExecutionDataId(ByteString.copyFrom(it.executionDataId.bytes)) + .setStateDeltaCommitment(ByteString.copyFrom(it.stateDeltaCommitment)) + .build() + } + + val grpcServiceEvents = serviceEvents.map { + ExecutionResultOuterClass.ServiceEvent.newBuilder() + .setType(it.type) + .setPayload(ByteString.copyFrom(it.payload)) + .build() + } + + val response = Access.ExecutionResultByIDResponse.newBuilder() + .setExecutionResult( + ExecutionResultOuterClass.ExecutionResult.newBuilder() + .setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)) + .setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)) + .addAllChunks(grpcChunks) + .addAllServiceEvents(grpcServiceEvents) + .build() + ).build() + + `when`(api.getExecutionResultByID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getExecutionResultByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(executionResult, result.data) + } + + @Test + fun `test getTransactionsByBlockId timeout exception`() { + val blockId = FlowId("01") + val future: ListenableFuture = SettableFuture.create() + `when`(api.getTransactionsByBlockID(any())).thenReturn(future) + + val executor = Executors.newSingleThreadExecutor() + executor.submit { + assertThrows { + asyncFlowAccessApi.getTransactionsByBlockId(blockId).get(1, TimeUnit.SECONDS) + } + } + + executor.shutdown() + executor.awaitTermination(2, TimeUnit.SECONDS) + } } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 04f21fb..1a2b2ef 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -10,6 +10,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc +import org.onflow.protobuf.entities.ExecutionResultOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.io.ByteArrayOutputStream import java.io.PrintStream @@ -310,6 +311,85 @@ class FlowAccessApiImplTest { assertResultSuccess(result) { assertEquals(mockFlowSnapshot, it) } } + @Test + fun `Test getTransactionsByBlockId`() { + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionsByBlockId(blockId) + assertResultSuccess(result) { assertEquals(transactions, it) } + } + + @Test + fun `Test getTransactionsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transaction1 = FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance()) + val transaction2 = FlowTransaction.of(TransactionOuterClass.Transaction.newBuilder().setReferenceBlockId(ByteString.copyFromUtf8("02")).build()) + val transactions = listOf(transaction1, transaction2) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionsByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(2, it.size) + assertEquals(transaction1, it[0]) + assertEquals(transaction2, it[1]) + } + } + + @Test + fun `Test getTransactionResultsByBlockId`() { + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionResultsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionResultsByBlockId(blockId) + assertResultSuccess(result) { assertEquals(transactionResults, it) } + } + + @Test + fun `Test getTransactionResultsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) + val transactionResults = listOf(transactionResult1, transactionResult2) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionResultsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionResultsByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(2, it.size) + assertEquals(transactionResult1, it[0]) + assertEquals(transactionResult2, it[1]) + } + } + + @Test + fun `Test getExecutionResultByBlockId`() { + val blockId = FlowId("01") + val grpcExecutionResult = ExecutionResultOuterClass.ExecutionResult.newBuilder() + .setBlockId(ByteString.copyFromUtf8("01")) + .setPreviousResultId(ByteString.copyFromUtf8("02")) + .addChunks(ExecutionResultOuterClass.Chunk.newBuilder().build()) + .addServiceEvents(ExecutionResultOuterClass.ServiceEvent.newBuilder().build()) + .build() + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(grpcExecutionResult).build() + + `when`(mockApi.getExecutionResultByID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getExecutionResultByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(FlowExecutionResult.of(response), it) + } + } + private fun assertResultSuccess(result: FlowAccessApi.AccessApiCallResponse, assertions: (T) -> Unit) { when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> assertions(result.data)