From e3d6f51049996161e84e732d8a7aec3281d1c8b4 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 5 Jul 2016 12:10:38 +0100 Subject: [PATCH] contracts, core, node: Port TransactionForTest tests to new DSL --- .../r3corda/contracts/CommercialPaperTests.kt | 4 +- .../kotlin/com/r3corda/contracts/IRSTests.kt | 367 ++++++++++-------- .../contracts/asset/ObligationTests.kt | 10 +- .../com/r3corda/core/testing/TestUtils.kt | 172 -------- .../core/contracts/TransactionGroupTests.kt | 14 +- .../messaging/TwoPartyTradeProtocolTests.kt | 35 +- .../node/visualiser/GroupToGraphConversion.kt | 2 +- 7 files changed, 248 insertions(+), 356 deletions(-) diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt index 8ed297d793..5073ef374e 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt @@ -76,6 +76,7 @@ class CommercialPaperTestsGeneric { output("paper") { thisTest.getPaper() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } timestamp(TEST_TX_TIME) + this.verifies() } // The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days, @@ -87,6 +88,7 @@ class CommercialPaperTestsGeneric { output("alice's paper") { "paper".output().data `owned by` ALICE_PUBKEY } command(ALICE_PUBKEY) { Cash.Commands.Move() } command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } + this.verifies() } // Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200 @@ -95,7 +97,7 @@ class CommercialPaperTestsGeneric { input("alice's paper") input("some profits") - fun TransactionDsl.outputs(aliceGetsBack: Amount>) { + fun TransactionDsl>.outputs(aliceGetsBack: Amount>) { output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY } output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt index 59faf91ffa..08b5f54411 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt @@ -360,7 +360,7 @@ class IRSTests { /** * Generates a typical transactional history for an IRS. */ - fun trade(): LedgerDsl { + fun trade(): LedgerDsl { val ld = LocalDate.of(2016, 3, 8) val bd = BigDecimal("0.0063518") @@ -370,6 +370,7 @@ class IRSTests { output("irs post agreement") { singleIRS() } command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timestamp(TEST_TX_TIME) + this.verifies() } transaction("Fix") { @@ -390,82 +391,93 @@ class IRSTests { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } timestamp(TEST_TX_TIME) + this.verifies() } } } @Test fun `ensure failure occurs when there are inbound states for an agreement command`() { - transaction { - input() { singleIRS() } - output("irs post agreement") { singleIRS() } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "There are no in states for an agreement" + ledger { + transaction { + input() { singleIRS() } + output("irs post agreement") { singleIRS() } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "There are no in states for an agreement" + } } } @Test fun `ensure failure occurs when no events in fix schedule`() { - val irs = singleIRS() - val emptySchedule = HashMap() - transaction { - output() { - irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule)) + ledger { + val irs = singleIRS() + val emptySchedule = HashMap() + transaction { + output() { + irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule)) + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "There are events in the fix schedule" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "There are events in the fix schedule" } } @Test fun `ensure failure occurs when no events in floating schedule`() { - val irs = singleIRS() - val emptySchedule = HashMap() - transaction { - output() { - irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule)) + ledger { + val irs = singleIRS() + val emptySchedule = HashMap() + transaction { + output() { + irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule)) + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "There are events in the float schedule" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "There are events in the float schedule" } } @Test fun `ensure notionals are non zero`() { - val irs = singleIRS() - transaction { - output() { - irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0))) + ledger { + val irs = singleIRS() + transaction { + output() { + irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0))) + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "All notionals must be non zero" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "All notionals must be non zero" - } - transaction { - output() { - irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0))) + transaction { + output() { + irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0))) + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "All notionals must be non zero" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "All notionals must be non zero" } } @Test fun `ensure positive rate on fixed leg`() { - val irs = singleIRS() - val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1")))) - transaction { - output() { - modifiedIRS + ledger { + val irs = singleIRS() + val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1")))) + transaction { + output() { + modifiedIRS + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The fixed leg rate must be positive" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "The fixed leg rate must be positive" } } @@ -474,173 +486,183 @@ class IRSTests { */ @Test fun `ensure same currency notionals`() { - val irs = singleIRS() - val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY")))) - transaction { - output() { - modifiedIRS + ledger { + val irs = singleIRS() + val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY")))) + transaction { + output() { + modifiedIRS + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The currency of the notionals must be the same" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "The currency of the notionals must be the same" } } @Test fun `ensure notional amounts are equal`() { - val irs = singleIRS() - val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token))) - transaction { - output() { - modifiedIRS + ledger { + val irs = singleIRS() + val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token))) + transaction { + output() { + modifiedIRS + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "All leg notionals must be the same" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "All leg notionals must be the same" } } @Test fun `ensure trade date and termination date checks are done pt1`() { - val irs = singleIRS() - val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1))) - transaction { - output() { - modifiedIRS1 + ledger { + val irs = singleIRS() + val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1))) + transaction { + output() { + modifiedIRS1 + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The effective date is before the termination date for the fixed leg" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "The effective date is before the termination date for the fixed leg" - } - val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1))) - transaction { - output() { - modifiedIRS2 + val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1))) + transaction { + output() { + modifiedIRS2 + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The effective date is before the termination date for the floating leg" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "The effective date is before the termination date for the floating leg" } } @Test fun `ensure trade date and termination date checks are done pt2`() { - val irs = singleIRS() + ledger { + val irs = singleIRS() - val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1))) - transaction { - output() { - modifiedIRS3 + val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1))) + transaction { + output() { + modifiedIRS3 + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The termination dates are aligned" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "The termination dates are aligned" - } - val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1))) - transaction { - output() { - modifiedIRS4 + val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1))) + transaction { + output() { + modifiedIRS4 + } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The effective dates are aligned" } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails requirement` "The effective dates are aligned" } } @Test fun `various fixing tests`() { + ledger { - val ld = LocalDate.of(2016, 3, 8) - val bd = BigDecimal("0.0063518") - - transaction { - output("irs post agreement") { singleIRS() } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this.accepts() - } - - val oldIRS = singleIRS(1) - val newIRS = oldIRS.copy(oldIRS.fixedLeg, - oldIRS.floatingLeg, - oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), - oldIRS.common) - - transaction { - input() { - oldIRS + val ld = LocalDate.of(2016, 3, 8) + val bd = BigDecimal("0.0063518") + transaction { + output("irs post agreement") { singleIRS() } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this.verifies() } - // Templated tweak for reference. A corrent fixing applied should be ok - tweak { - arg(ORACLE_PUBKEY) { - InterestRateSwap.Commands.Fix() - } - timestamp(TEST_TX_TIME) - arg(ORACLE_PUBKEY) { - Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) - } - output() { newIRS } - this.accepts() - } + val oldIRS = singleIRS(1) + val newIRS = oldIRS.copy(oldIRS.fixedLeg, + oldIRS.floatingLeg, + oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), + oldIRS.common) - // This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new - tweak { - arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } - timestamp(TEST_TX_TIME) - arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } - output() { oldIRS } - this`fails requirement` "There is at least one difference in the IRS floating leg payment schedules" - } + transaction { + input() { + oldIRS - // This tests tries to sneak in a change to another fixing (which may or may not be the latest one) - tweak { - arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } - timestamp(TEST_TX_TIME) - arg(ORACLE_PUBKEY) { - Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } - val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first() - val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey] - val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY"))) - - output() { - newIRS.copy( - newIRS.fixedLeg, - newIRS.floatingLeg, - newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( - Pair(firstResetKey, modifiedFirstResetValue))), - newIRS.common - ) + // Templated tweak for reference. A corrent fixing applied should be ok + tweak { + command(ORACLE_PUBKEY) { + InterestRateSwap.Commands.Fix() + } + timestamp(TEST_TX_TIME) + command(ORACLE_PUBKEY) { + Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) + } + output() { newIRS } + this.verifies() } - this`fails requirement` "There is only one change in the IRS floating leg payment schedule" - } - // This tests modifies the payment currency for the fixing - tweak { - arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } - timestamp(TEST_TX_TIME) - arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } - - val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key } - val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY"))) - - output() { - newIRS.copy( - newIRS.fixedLeg, - newIRS.floatingLeg, - newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( - Pair(latestReset.key, modifiedLatestResetValue))), - newIRS.common - ) + // This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new + tweak { + command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } + timestamp(TEST_TX_TIME) + command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } + output() { oldIRS } + this `fails with` "There is at least one difference in the IRS floating leg payment schedules" + } + + // This tests tries to sneak in a change to another fixing (which may or may not be the latest one) + tweak { + command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } + timestamp(TEST_TX_TIME) + command(ORACLE_PUBKEY) { + Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) + } + + val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first() + val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey] + val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY"))) + + output() { + newIRS.copy( + newIRS.fixedLeg, + newIRS.floatingLeg, + newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( + Pair(firstResetKey, modifiedFirstResetValue))), + newIRS.common + ) + } + this `fails with` "There is only one change in the IRS floating leg payment schedule" + } + + // This tests modifies the payment currency for the fixing + tweak { + command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } + timestamp(TEST_TX_TIME) + command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } + + val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key } + val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY"))) + + output() { + newIRS.copy( + newIRS.fixedLeg, + newIRS.floatingLeg, + newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( + Pair(latestReset.key, modifiedLatestResetValue))), + newIRS.common + ) + } + this `fails with` "The fix payment has the same currency as the notional" } - this`fails requirement` "The fix payment has the same currency as the notional" } } } @@ -652,7 +674,7 @@ class IRSTests { * result and the grouping won't work either. * In reality, the only fields that should be in common will be the next fixing date and the reference rate. */ - fun tradegroups(): LedgerDsl { + fun tradegroups(): LedgerDsl { val ld1 = LocalDate.of(2016, 3, 8) val bd1 = BigDecimal("0.0063518") @@ -670,6 +692,7 @@ class IRSTests { } command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timestamp(TEST_TX_TIME) + this.verifies() } transaction("Agreement") { @@ -683,6 +706,7 @@ class IRSTests { } command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timestamp(TEST_TX_TIME) + this.verifies() } transaction("Fix") { @@ -714,6 +738,7 @@ class IRSTests { Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1) } timestamp(TEST_TX_TIME) + this.verifies() } } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt index f09a2e55fc..6cc13e440b 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt @@ -33,7 +33,9 @@ class ObligationTests { ) val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2) - private fun obligationTestRoots(group: LedgerDsl) = group.apply { + private fun obligationTestRoots( + group: LedgerDsl + ) = group.apply { nonVerifiedTransaction { output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)) output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)) @@ -341,6 +343,7 @@ class ObligationTests { // Note we can sign with either key here command(ALICE_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } timestamp(TEST_TX_TIME) + this.verifies() } this.verifies() } @@ -356,6 +359,7 @@ class ObligationTests { output("change") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY) } command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } timestamp(TEST_TX_TIME) + this.verifies() } this.verifies() } @@ -397,6 +401,7 @@ class ObligationTests { input("Bob's $1,000,000 obligation to Alice") command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } timestamp(TEST_TX_TIME) + this.verifies() } this.verifies() } @@ -423,6 +428,7 @@ class ObligationTests { output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) } command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } timestamp(TEST_TX_TIME) + this.verifies() } this.verifies() } @@ -452,6 +458,7 @@ class ObligationTests { output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY } command(ALICE_PUBKEY) { Obligation.Commands.Settle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) } command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } + this.verifies() } this.verifies() } @@ -496,6 +503,7 @@ class ObligationTests { output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) } command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) } timestamp(TEST_TX_TIME) + this.verifies() } this.verifies() } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt b/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt index 65a4e9ae5b..bb57eec96f 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt @@ -10,16 +10,10 @@ import com.r3corda.core.node.services.IdentityService import com.r3corda.core.node.services.StorageService import com.r3corda.core.node.services.testing.MockIdentityService import com.r3corda.core.node.services.testing.MockStorageService -import com.r3corda.core.seconds -import com.r3corda.core.serialization.serialize import java.net.ServerSocket import java.security.KeyPair import java.security.PublicKey import java.time.Instant -import java.util.* -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.fail /** If an exception is thrown by the body, rethrows the root cause exception. */ inline fun rootCauseExceptions(body: () -> R): R { @@ -132,27 +126,6 @@ val MOCK_IDENTITY_SERVICE = JavaTestHelpers.MOCK_IDENTITY_SERVICE fun generateStateRef() = JavaTestHelpers.generateStateRef() -fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure) = JavaTestHelpers.transaction(body) - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes. -// -// Define a transaction like this: -// -// transaction { -// input { someExpression } -// output { someExpression } -// arg { someExpression } -// -// tweak { -// ... same thing but works with a copy of the parent, can add inputs/outputs/args just within this scope. -// } -// -// contract.accepts() -> should pass -// contract `fails requirement` "some substring of the error message" -// } -// class LabeledOutput(val label: String?, val state: TransactionState<*>) { override fun toString() = state.toString() + (if (label != null) " ($label)" else "") override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state) @@ -161,148 +134,3 @@ class LabeledOutput(val label: String?, val state: TransactionState<*>) { infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this) -abstract class AbstractTransactionForTest { - protected val attachments = ArrayList() - protected val outStates = ArrayList() - protected val commands = ArrayList() - protected val signers = LinkedHashSet() - protected val type = TransactionType.General() - - @JvmOverloads - open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) } - @JvmOverloads - open fun output(label: String? = null, s: ContractState) = output(label) { s } - - protected fun commandsToAuthenticatedObjects(): List> { - return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) } - } - - fun attachment(attachmentID: SecureHash) { - attachments.add(attachmentID) - } - - fun arg(vararg keys: PublicKey, c: () -> CommandData) { - val keysList = listOf(*keys) - addCommand(Command(c(), keysList)) - } - fun arg(key: PublicKey, c: CommandData) = arg(key) { c } - - fun timestamp(time: Instant) { - val data = TimestampCommand(time, 30.seconds) - timestamp(data) - } - - fun timestamp(data: TimestampCommand) { - addCommand(Command(data, DUMMY_NOTARY.owningKey)) - } - - fun addCommand(cmd: Command) { - signers.addAll(cmd.signers) - commands.add(cmd) - } - - // Forbid patterns like: transaction { ... transaction { ... } } - @Deprecated("Cannot nest transactions, use tweak", level = DeprecationLevel.ERROR) - fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure) { - } -} - -/** If you jumped here from a compiler error make sure the last line of your test tests for a transaction accept or fail - * This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests - * will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of - * the triggered diagnostic - */ -sealed class LastLineShouldTestForAcceptOrFailure { - internal object Token: LastLineShouldTestForAcceptOrFailure() -} - -// Corresponds to the args to Contract.verify -// Note on defaults: try to avoid Kotlin defaults as they don't work from Java. Instead define overloads -open class TransactionForTest : AbstractTransactionForTest() { - private val inStates = arrayListOf>() - - fun input(s: () -> ContractState) { - signers.add(DUMMY_NOTARY.owningKey) - inStates.add(TransactionState(s(), DUMMY_NOTARY)) - } - fun input(s: ContractState) = input { s } - - protected fun runCommandsAndVerify() { - val cmds = commandsToAuthenticatedObjects() - val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), signers.toList(), type) - tx.verify() - } - - fun accepts(): LastLineShouldTestForAcceptOrFailure { - runCommandsAndVerify() - return LastLineShouldTestForAcceptOrFailure.Token - } - - @JvmOverloads - fun rejects(withMessage: String? = null): LastLineShouldTestForAcceptOrFailure { - val r = try { - runCommandsAndVerify() - false - } catch (e: Exception) { - val m = e.message - if (m == null) - fail("Threw exception without a message") - else - if (withMessage != null && !m.toLowerCase().contains(withMessage.toLowerCase())) throw AssertionError("Error was actually: $m", e) - true - } - if (!r) throw AssertionError("Expected exception but didn't get one") - return LastLineShouldTestForAcceptOrFailure.Token - } - - /** - * Used to confirm that the test, when (implicitly) run against the .verify() method, fails with the text of the message - */ - infix fun `fails requirement`(msg: String): LastLineShouldTestForAcceptOrFailure = rejects(msg) - fun failsRequirement(msg: String) = this.`fails requirement`(msg) - - // Use this to create transactions where the output of this transaction is automatically used as an input of - // the next. - fun chain(vararg outputLabels: String, body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): TransactionForTest { - val states = outStates.mapNotNull { - val l = it.label - if (l != null && outputLabels.contains(l)) - it.state - else - null - } - val tx = TransactionForTest() - tx.inStates.addAll(states) - tx.body() - return tx - } - - // Allow customisation of partial transactions. - fun tweak(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure { - val tx = TransactionForTest() - tx.inStates.addAll(inStates) - tx.outStates.addAll(outStates) - tx.commands.addAll(commands) - - tx.signers.addAll(tx.inStates.map { it.notary.owningKey }) - tx.signers.addAll(commands.flatMap { it.signers }) - return tx.body() - } - - override fun toString(): String { - return """transaction { - inputs: $inStates - outputs: $outStates - commands $commands - }""" - } - - override fun equals(other: Any?) = this === other || (other is TransactionForTest && inStates == other.inStates && outStates == other.outStates && commands == other.commands) - - override fun hashCode(): Int { - var result = inStates.hashCode() - result += 31 * result + outStates.hashCode() - result += 31 * result + commands.hashCode() - return result - } -} diff --git a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt index fdefad885b..e8548db65d 100644 --- a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt @@ -56,15 +56,17 @@ class TransactionGroupTests { input("£1000") output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY } command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + this.verifies() } transaction { input("alice's £1000") command(ALICE_PUBKEY) { TestCash.Commands.Move() } command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } + this.verifies() } - verifies() + this.verifies() } } @@ -74,6 +76,7 @@ class TransactionGroupTests { val t = transaction { output("cash") { A_THOUSAND_POUNDS } command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } + this.verifies() } val conflict1 = transaction { @@ -82,6 +85,7 @@ class TransactionGroupTests { output { HALF } output { HALF } command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + this.verifies() } verifies() @@ -93,6 +97,7 @@ class TransactionGroupTests { output { HALF } output { HALF } command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + this.verifies() } assertNotEquals(conflict1, conflict2) @@ -112,11 +117,13 @@ class TransactionGroupTests { transaction { output("cash") { A_THOUSAND_POUNDS } command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } + this.verifies() } transaction { input("cash") output { A_THOUSAND_POUNDS `owned by` BOB_PUBKEY } + this.verifies() } } @@ -126,6 +133,7 @@ class TransactionGroupTests { assertFailsWith(TransactionResolutionException::class) { input(input.ref) } + this.verifies() } } } @@ -143,6 +151,7 @@ class TransactionGroupTests { input("£1000") output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) } command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + this.verifies() } assertFailsWith(TransactionConflictException::class) { @@ -157,18 +166,21 @@ class TransactionGroupTests { transaction { output("£1000") { A_THOUSAND_POUNDS } command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } + this.verifies() } transaction { input("£1000") output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY } command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + this.verifies() } transaction { input("alice's £1000") command(ALICE_PUBKEY) { TestCash.Commands.Move() } command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } + this.verifies() } }.interpreter.wireTransactions.let { signAll(it) } diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt index bdc365d599..e703d9306b 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt @@ -17,6 +17,7 @@ import com.r3corda.core.node.services.ServiceType import com.r3corda.core.node.services.TransactionStorage import com.r3corda.core.node.services.Wallet import com.r3corda.core.random63BitValue +import com.r3corda.core.contracts.Attachment import com.r3corda.core.seconds import com.r3corda.core.testing.* import com.r3corda.core.utilities.BriefLogFormatter @@ -249,10 +250,11 @@ class TwoPartyTradeProtocolTests { @Test fun `check dependencies of sale asset are resolved`() { - ledger { - val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY) - val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_KEY) + val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY) + val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_KEY) + + ledger(storageService = aliceNode.storage) { // Insert a prospectus type attachment into the commercial paper transaction. val stream = ByteArrayOutputStream() @@ -261,7 +263,7 @@ class TwoPartyTradeProtocolTests { it.write("Our commercial paper is top notch stuff".toByteArray()) it.closeEntry() } - val attachmentID = aliceNode.storage.attachments.importAttachment(ByteArrayInputStream(stream.toByteArray())) + val attachmentID = attachment(ByteArrayInputStream(stream.toByteArray())) val issuer = MEGA_CORP.ref(1) val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public, issuer).second @@ -365,8 +367,11 @@ class TwoPartyTradeProtocolTests { } } - private fun LedgerDsl>.runWithError(bobError: Boolean, aliceError: Boolean, - expectedMessageSubstring: String) { + private fun LedgerDsl.runWithError( + bobError: Boolean, + aliceError: Boolean, + expectedMessageSubstring: String + ) { val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY) val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) @@ -427,7 +432,7 @@ class TwoPartyTradeProtocolTests { return signed.associateBy { it.id } } - private fun LedgerDsl>.fillUpForBuyer( + private fun LedgerDsl.fillUpForBuyer( withError: Boolean, owner: PublicKey = BOB_PUBKEY, issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair> { @@ -441,6 +446,11 @@ class TwoPartyTradeProtocolTests { if (!withError) command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } timestamp(TEST_TX_TIME) + if (withError) { + this.fails() + } else { + this.verifies() + } } // Bob gets some cash onto the ledger from BoE @@ -448,6 +458,7 @@ class TwoPartyTradeProtocolTests { input("elbonian money 1") output("bob cash 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } + this.verifies() } val bc2 = transaction { @@ -455,13 +466,14 @@ class TwoPartyTradeProtocolTests { output("bob cash 2") { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } output { 700.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } // Change output. command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } + this.verifies() } val wallet = Wallet(listOf("bob cash 1".outputStateAndRef(), "bob cash 2".outputStateAndRef())) return Pair(wallet, listOf(eb1, bc1, bc2)) } - private fun LedgerDsl>.fillUpForSeller( + private fun LedgerDsl.fillUpForSeller( withError: Boolean, owner: PublicKey, amount: Amount>, @@ -476,6 +488,11 @@ class TwoPartyTradeProtocolTests { command(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) } if (attachmentID != null) attachment(attachmentID) + if (withError) { + this.fails() + } else { + this.verifies() + } } val wallet = Wallet(listOf("alice's paper".outputStateAndRef())) diff --git a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt index ae7ca111b2..1285522a62 100644 --- a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt +++ b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt @@ -9,7 +9,7 @@ import org.graphstream.graph.Node import org.graphstream.graph.implementations.SingleGraph import kotlin.reflect.memberProperties -class GraphVisualiser(val dsl: LedgerDsl) { +class GraphVisualiser(val dsl: LedgerDsl) { companion object { val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText() }