From 48b121d145eba62c97eec98800a1b8c2d7b632b9 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 31 Mar 2017 11:21:36 +0100 Subject: [PATCH] Add Cash*Flow tests Add tests for CashIssueFlow, CashPaymentFlow and CashExitFlow. While these were mostly covered by other tests already, CashExistFlow was not, and any bugs would be harder to identify because they are mixed in with other functionality (i.e. vault tests) --- .../kotlin/net/corda/contracts/asset/Cash.kt | 1 + .../clause/AbstractConserveAmount.kt | 1 + .../net/corda/flows/CashExitFlowTests.kt | 72 ++++++++++++++++ .../net/corda/flows/CashIssueFlowTests.kt | 65 ++++++++++++++ .../net/corda/flows/CashPaymentFlowTests.kt | 86 +++++++++++++++++++ .../node/services/vault/NodeVaultService.kt | 2 + 6 files changed, 227 insertions(+) create mode 100644 finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt create mode 100644 finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt create mode 100644 finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt index 5485a7ab45..cea84f6cc0 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt @@ -154,6 +154,7 @@ class Cash : OnLedgerAsset() { fun generateIssue(tx: TransactionBuilder, amount: Amount>, owner: CompositeKey, notary: Party) { check(tx.inputStates().isEmpty()) check(tx.outputStates().map { it.data }.sumCashOrNull() == null) + require(amount.quantity > 0) val at = amount.token.issuer tx.addOutputState(TransactionState(State(amount, owner), notary)) tx.addCommand(generateIssueCommand(), at.party.owningKey) diff --git a/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt b/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt index 67f0099766..6ff4d283f3 100644 --- a/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt +++ b/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt @@ -29,6 +29,7 @@ abstract class AbstractConserveAmount, C : CommandData, T : @Throws(InsufficientBalanceException::class) private fun gatherCoins(acceptableCoins: Collection>, amount: Amount): Pair>, Amount> { + require(amount.quantity > 0) { "Cannot gather zero coins" } val gathered = arrayListOf>() var gatheredAmount = Amount(0, amount.token) for (c in acceptableCoins) { diff --git a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt new file mode 100644 index 0000000000..7baac1e431 --- /dev/null +++ b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt @@ -0,0 +1,72 @@ +package net.corda.flows + +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.DOLLARS +import net.corda.core.contracts.`issued by` +import net.corda.core.crypto.Party +import net.corda.core.getOrThrow +import net.corda.core.serialization.OpaqueBytes +import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNetwork.MockNode +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CashExitFlowTests { + private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + private val initialBalance = 2000.DOLLARS + private val ref = OpaqueBytes.of(0x01) + private lateinit var bankOfCordaNode: MockNode + private lateinit var bankOfCorda: Party + private lateinit var notaryNode: MockNode + private lateinit var notary: Party + + @Before + fun start() { + val nodes = net.createTwoNodes() + notaryNode = nodes.first + bankOfCordaNode = nodes.second + notary = notaryNode.info.notaryIdentity + bankOfCorda = bankOfCordaNode.info.legalIdentity + + net.runNetwork() + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, + bankOfCorda, + notary)).resultFuture + net.runNetwork() + future.getOrThrow() + } + + @After + fun cleanUp() { + net.stopNodes() + } + + @Test + fun `exit some cash`() { + val exitAmount = 500.DOLLARS + val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, + ref)).resultFuture + net.runNetwork() + val exitTx = future.getOrThrow().tx + val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) + assertEquals(1, exitTx.inputs.size) + assertEquals(1, exitTx.outputs.size) + val output = exitTx.outputs.map { it.data }.filterIsInstance().single() + assertEquals(expected, output.amount) + } + + @Test + fun `exit zero cash`() { + val expected = 0.DOLLARS + val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, + ref)).resultFuture + net.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } +} diff --git a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt new file mode 100644 index 0000000000..f98ecfc1d3 --- /dev/null +++ b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt @@ -0,0 +1,65 @@ +package net.corda.flows + +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.DOLLARS +import net.corda.core.contracts.`issued by` +import net.corda.core.crypto.Party +import net.corda.core.getOrThrow +import net.corda.core.serialization.OpaqueBytes +import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNetwork.MockNode +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CashIssueFlowTests { + private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + private lateinit var bankOfCordaNode: MockNode + private lateinit var bankOfCorda: Party + private lateinit var notaryNode: MockNode + private lateinit var notary: Party + + @Before + fun start() { + val nodes = net.createTwoNodes() + notaryNode = nodes.first + bankOfCordaNode = nodes.second + notary = notaryNode.info.notaryIdentity + bankOfCorda = bankOfCordaNode.info.legalIdentity + + net.runNetwork() + } + + @After + fun cleanUp() { + net.stopNodes() + } + + @Test + fun `issue some cash`() { + val expected = 500.DOLLARS + val ref = OpaqueBytes.of(0x01) + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, + bankOfCorda, + notary)).resultFuture + net.runNetwork() + val issueTx = future.getOrThrow() + val output = issueTx.tx.outputs.single().data as Cash.State + assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount) + } + + @Test + fun `issue zero cash`() { + val expected = 0.DOLLARS + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, OpaqueBytes.of(0x01), + bankOfCorda, + notary)).resultFuture + net.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } +} diff --git a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt new file mode 100644 index 0000000000..de4b15415c --- /dev/null +++ b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt @@ -0,0 +1,86 @@ +package net.corda.flows + +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.DOLLARS +import net.corda.core.contracts.`issued by` +import net.corda.core.crypto.Party +import net.corda.core.getOrThrow +import net.corda.core.serialization.OpaqueBytes +import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNetwork.MockNode +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.util.concurrent.ExecutionException +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CashPaymentFlowTests { + private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + private val initialBalance = 2000.DOLLARS + private val ref = OpaqueBytes.of(0x01) + private lateinit var bankOfCordaNode: MockNode + private lateinit var bankOfCorda: Party + private lateinit var notaryNode: MockNode + private lateinit var notary: Party + + @Before + fun start() { + val nodes = net.createTwoNodes() + notaryNode = nodes.first + bankOfCordaNode = nodes.second + notary = notaryNode.info.notaryIdentity + bankOfCorda = bankOfCordaNode.info.legalIdentity + + net.runNetwork() + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, + bankOfCorda, + notary)).resultFuture + net.runNetwork() + future.getOrThrow() + } + + @After + fun cleanUp() { + net.stopNodes() + } + + @Test + fun `pay some cash`() { + val payTo = notaryNode.info.legalIdentity + val expected = 500.DOLLARS + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, + payTo)).resultFuture + net.runNetwork() + val paymentTx = future.getOrThrow() + val states = paymentTx.tx.outputs.map { it.data }.filterIsInstance() + val ourState = states.single { it.owner != payTo.owningKey } + val paymentState = states.single { it.owner == payTo.owningKey } + assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), paymentState.amount) + } + + @Test + fun `pay more than we have`() { + val payTo = notaryNode.info.legalIdentity + val expected = 4000.DOLLARS + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, + payTo)).resultFuture + net.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } + + @Test + fun `pay zero cash`() { + val payTo = notaryNode.info.legalIdentity + val expected = 0.DOLLARS + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, + payTo)).resultFuture + net.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 74163bd403..ae36165e32 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -528,9 +528,11 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P * @param amount the amount to gather states up to. * @throws InsufficientBalanceException if there isn't enough value in the states to cover the requested amount. */ + // TODO: Merge this with the function in [AbstractConserveAmount] @Throws(InsufficientBalanceException::class) private fun gatherCoins(acceptableCoins: Collection>, amount: Amount): Pair>, Amount> { + require(amount.quantity > 0) { "Cannot gather zero coins" } val gathered = arrayListOf>() var gatheredAmount = Amount(0, amount.token) for (c in acceptableCoins) {