diff --git a/docs/source/api-testing.rst b/docs/source/api-testing.rst new file mode 100644 index 0000000000..4281d32347 --- /dev/null +++ b/docs/source/api-testing.rst @@ -0,0 +1,467 @@ +.. highlight:: kotlin +.. raw:: html + + + + +API: Testing +============ + +.. contents:: + +Flow testing +------------ + +MockNetwork +^^^^^^^^^^^ + +Flow testing can be fully automated using a ``MockNetwork`` composed of ``StartedMockNode`` nodes. Each +``StartedMockNode`` behaves like a regular Corda node, but its services are either in-memory or mocked out. + +A ``MockNetwork`` is created as follows: + +.. container:: codeset + + .. sourcecode:: kotlin + + class FlowTests { + private lateinit var mockNet: MockNetwork + + @Before + fun setup() { + network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package")) + } + } + + + .. sourcecode:: java + + public class IOUFlowTests { + private MockNetwork network; + + @Before + public void setup() { + network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package")); + } + } + +The ``MockNetwork`` requires at a minimum a list of packages. Each package is packaged into a CorDapp JAR and installed +as a CorDapp on each ``StartedMockNode``. + +Configuring the ``MockNetwork`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``MockNetwork`` is configured automatically. You can tweak its configuration using a ``MockNetworkParameters`` +object, or by using named paramters in Kotlin: + +.. container:: codeset + + .. sourcecode:: kotlin + + val network = MockNetwork( + cordappPackages = listOf("my.cordapp.package", "my.other.cordapp.package"), + // If true then each node will be run in its own thread. This can result in race conditions in your + // code if not carefully written, but is more realistic and may help if you have flows in your app that + // do long blocking operations. + threadPerNode = false, + // The notaries to use on the mock network. By default you get one mock notary and that is usually + // sufficient. + notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), + // If true then messages will not be routed from sender to receiver until you use the + // [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can + // examine the state of the mock network before and after a message is sent, without races and without + // the receiving node immediately sending a response. + networkSendManuallyPumped = false, + // How traffic is allocated in the case where multiple nodes share a single identity, which happens for + // notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing + // notary implementations. + servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random()) + + val network2 = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"), MockNetworkParameters( + // If true then each node will be run in its own thread. This can result in race conditions in your + // code if not carefully written, but is more realistic and may help if you have flows in your app that + // do long blocking operations. + threadPerNode = false, + // The notaries to use on the mock network. By default you get one mock notary and that is usually + // sufficient. + notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), + // If true then messages will not be routed from sender to receiver until you use the + // [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can + // examine the state of the mock network before and after a message is sent, without races and without + // the receiving node immediately sending a response. + networkSendManuallyPumped = false, + // How traffic is allocated in the case where multiple nodes share a single identity, which happens for + // notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing + // notary implementations. + servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random()) + ) + + .. sourcecode:: java + + MockNetwork network = MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"), + new MockNetworkParameters() + // If true then each node will be run in its own thread. This can result in race conditions in + // your code if not carefully written, but is more realistic and may help if you have flows in + // your app that do long blocking operations. + .setThreadPerNode(false) + // The notaries to use on the mock network. By default you get one mock notary and that is + // usually sufficient. + .setNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(DUMMY_NOTARY_NAME))) + // If true then messages will not be routed from sender to receiver until you use the + // [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code + // that can examine the state of the mock network before and after a message is sent, without + // races and without the receiving node immediately sending a response. + .setNetworkSendManuallyPumped(false) + // How traffic is allocated in the case where multiple nodes share a single identity, which + // happens for notaries in a cluster. You don't normally ever need to change this: it is mostly + // useful for testing notary implementations. + .setServicePeerAllocationStrategy(new InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())); + +Adding nodes to the network +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Nodes are created on the ``MockNetwork`` using: + +.. container:: codeset + + .. sourcecode:: kotlin + + class FlowTests { + private lateinit var mockNet: MockNetwork + lateinit var nodeA: StartedMockNode + lateinit var nodeB: StartedMockNode + + @Before + fun setup() { + network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package")) + nodeA = network.createPartyNode() + // We can optionally give the node a name. + nodeB = network.createPartyNode(CordaX500Name("Bank B", "London", "GB")) + } + } + + + .. sourcecode:: java + + public class IOUFlowTests { + private MockNetwork network; + private StartedMockNode a; + private StartedMockNode b; + + @Before + public void setup() { + network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package")); + nodeA = network.createPartyNode(null); + // We can optionally give the node a name. + nodeB = network.createPartyNode(new CordaX500Name("Bank B", "London", "GB")); + } + } + +Registering a node's initiated flows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Regular Corda nodes automatically register any response flows defined in their installed CorDapps. When using a +``MockNetwork``, each ``StartedMockNode`` must manually register any responder flows it wishes to use. + +Responder flows are registered as follows: + +.. container:: codeset + + .. sourcecode:: kotlin + + nodeA.registerInitiatedFlow(ExampleFlow.Acceptor::class.java) + + .. sourcecode:: java + + nodeA.registerInitiatedFlow(ExampleFlow.Acceptor.class); + +Running the network +^^^^^^^^^^^^^^^^^^^ + +Regular Corda nodes automatically process received messages. When using a ``MockNetwork`` with +``networkSendManuallyPumped`` set to ``false``, you must manually initiate the processing of received messages. + +You manually process received messages as follows: + +* ``StartedMockNode.pumpReceive`` to process a single message from the node's queue + +* ``MockNetwork.runNetwork`` to process all the messages in every node's queue. This may generate additional messages + that must in turn be processed + + * ``network.runNetwork(-1)`` (the default in Kotlin) will exchange messages until there are no further messages to + process + +Running flows +^^^^^^^^^^^^^ + +A ``StartedMockNode`` starts a flow using the ``StartedNodeServices.startFlow`` method. This method returns a future +representing the output of running the flow. + +.. container:: codeset + + .. sourcecode:: kotlin + + val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty)) + + .. sourcecode:: java + + CordaFuture future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty)); + +The network must then be manually run before retrieving the future's value: + +.. container:: codeset + + .. sourcecode:: kotlin + + val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty)) + // Assuming network.networkSendManuallyPumped == false. + network.runNetwork() + val signedTransaction = future.get(); + + .. sourcecode:: java + + CordaFuture future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty)); + // Assuming network.networkSendManuallyPumped == false. + network.runNetwork(); + SignedTransaction signedTransaction = future.get(); + +Accessing ``StartedMockNode`` internals +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Creating a node database transaction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whenever you query a node's database (e.g. to extract information from the node's vault), you must wrap the query in +a database transaction, as follows: + +.. container:: codeset + + .. sourcecode:: kotlin + + nodeA.database.transaction { + // Perform query here. + } + + .. sourcecode:: java + + node.getDatabase().transaction(tx -> { + // Perform query here. + } + +Querying a node's vault +~~~~~~~~~~~~~~~~~~~~~~~ + +Recorded states can be retrieved from the vault of a ``StartedMockNode`` using: + +.. container:: codeset + + .. sourcecode:: kotlin + + nodeA.database.transaction { + val myStates = nodeA.services.vaultService.queryBy().states + } + + .. sourcecode:: java + + node.getDatabase().transaction(tx -> { + List myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates(); + } + +This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes. + + +Examining a node's transaction storage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Recorded transactions can be retrieved from the transaction storage of a ``StartedMockNode`` using: + +.. container:: codeset + + .. sourcecode:: kotlin + + val transaction = nodeA.services.validatedTransactions.getTransaction(transaction.id) + + .. sourcecode:: java + + SignedTransaction transaction = nodeA.getServices().getValidatedTransactions().getTransaction(transaction.getId()) + +This allows you to check whether a given transaction has (or has not) been stored, and whether it has the correct +attributes. + +This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes. + +Further examples +^^^^^^^^^^^^^^^^ + +* See the flow testing tutorial :doc:`here ` +* Further examples are available in the Example CorDapp in + `Java `_ and + `Kotlin `_ + +Contract testing +---------------- + +The Corda test framework includes the ability to create a test ledger by calling the ``ledger`` function +on an implementation of the ``ServiceHub`` interface. + +MockServices +^^^^^^^^^^^^ + +A mock implementation of ``ServiceHub`` is provided in ``MockServices``. This is a minimal ``ServiceHub`` that +suffices to test contract logic. It has the ability to insert states into the vault, query the vault, and +construct and check transactions. + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 11 + :end-before: DOCEND 11 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 11 + :end-before: DOCEND 11 + :dedent: 4 + + +Alternatively, there is a helper constructor which just accepts a list of ``TestIdentity``. The first identity provided is +the identity of the node whose ``ServiceHub`` is being mocked, and any subsequent identities are identities that the node +knows about. Only the calling package is scanned for cordapps and a test ``IdentityService`` is created +for you, using all the given identities. + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 12 + :end-before: DOCEND 12 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 12 + :end-before: DOCEND 12 + :dedent: 4 + + +Writing tests using a test ledger +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``ServiceHub.ledger`` extension function allows you to create a test ledger. Within the ledger wrapper you can create +transactions using the ``transaction`` function. Within a transaction you can define the ``input`` and +``output`` states for the transaction, alongside any commands that are being executed, the ``timeWindow`` in which the +transaction has been executed, and any ``attachments``, as shown in this example test: + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 13 + :end-before: DOCEND 13 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 13 + :end-before: DOCEND 13 + :dedent: 4 + +Once all the transaction components have been specified, you can run ``verifies()`` to check that the given transaction is valid. + +Checking for failure states +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to test for failures, you can use the ``failsWith`` method, or in Kotlin the ``fails with`` helper method, which +assert that the transaction fails with a specific error. If you just want to assert that the transaction has failed without +verifying the message, there is also a ``fails`` method. + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 4 + :end-before: DOCEND 4 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 4 + :end-before: DOCEND 4 + :dedent: 4 + +.. note:: + + The transaction DSL forces the last line of the test to be either a ``verifies`` or ``fails with`` statement. + +Testing multiple scenarios at once +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Within a single transaction block, you can assert several times that the transaction constructed so far either passes or +fails verification. For example, you could test that a contract fails to verify because it has no output states, and then +add the relevant output state and check that the contract verifies successfully, as in the following example: + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 5 + :end-before: DOCEND 5 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 5 + :end-before: DOCEND 5 + :dedent: 4 + +You can also use the ``tweak`` function to create a locally scoped transaction that you can make changes to +and then return to the original, unmodified transaction. As in the following example: + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 7 + :end-before: DOCEND 7 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 7 + :end-before: DOCEND 7 + :dedent: 4 + + +Chaining transactions +~~~~~~~~~~~~~~~~~~~~~ + +The following example shows that within a ``ledger``, you can create more than one ``transaction`` in order to test chains +of transactions. In addition to ``transaction``, ``unverifiedTransaction`` can be used, as in the example below, to create +transactions on the ledger without verifying them, for pre-populating the ledger with existing data. When chaining transactions, +it is important to note that even though a ``transaction`` ``verifies`` successfully, the overall ledger may not be valid. This can +be verified separately by placing a ``verifies`` or ``fails`` statement within the ``ledger`` block. + +.. container:: codeset + + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + :language: kotlin + :start-after: DOCSTART 9 + :end-before: DOCEND 9 + :dedent: 4 + + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + :language: java + :start-after: DOCSTART 9 + :end-before: DOCEND 9 + :dedent: 4 + + +Further examples +^^^^^^^^^^^^^^^^ + +* See the flow testing tutorial :doc:`here ` +* Further examples are available in the Example CorDapp in + `Java `_ and + `Kotlin `_ diff --git a/docs/source/conf.py b/docs/source/conf.py index b34fa914f9..ebf92265fb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'R3 Corda' -copyright = u'2017, R3 Limited' +copyright = u'2018, R3 Limited' author = u'R3 DLG' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index aa55bfefcf..f0f803a227 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -17,6 +17,7 @@ The following are the core APIs that are used in the development of CorDapps: api-service-hub api-rpc api-core-types + api-testing Before reading this page, you should be familiar with the :doc:`key concepts of Corda `. diff --git a/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java index a18e869631..ff5e91b4ed 100644 --- a/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java +++ b/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java @@ -21,31 +21,49 @@ import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID; import static net.corda.testing.core.TestConstants.*; +import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static net.corda.testing.node.NodeTestUtils.ledger; import static net.corda.testing.node.NodeTestUtils.transaction; public class CommercialPaperTest { - private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L); - private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic(); - private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L); - private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); + private static final TestIdentity alice = new TestIdentity(ALICE_NAME, 70L); + private static final TestIdentity bigCorp = new TestIdentity(new CordaX500Name("BigCorp", "New York", "GB")); + private static final TestIdentity bob = new TestIdentity(BOB_NAME, 80L); + private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); private final byte[] defaultRef = {123}; - private MockServices ledgerServices; - @Before - public void setUp() { - // When creating the MockServices, you need to specify the packages that will contain the contracts you will use in this test - // For this test its' Cash and CommercialPaper, bot in the 'net.corda.finance.contracts' package - // In case you don't specify the 'cordappPackages' argument, the MockServices will be initialised with the Contracts found in the package of the current test - ledgerServices = new MockServices(singletonList("net.corda.finance.contracts"), MEGA_CORP); - } + // DOCSTART 11 + private final MockServices ledgerServices = new MockServices( + // A list of packages to scan for cordapps + singletonList("net.corda.finance.contracts"), + // The identity represented by this set of mock services. Defaults to a test identity. + // You can also use the alternative parameter initialIdentityName which accepts a + // [CordaX500Name] + megaCorp, + // An implementation of [IdentityService], which contains a list of all identities known + // to the node. Use [makeTestIdentityService] which returns an implementation of + // [InMemoryIdentityService] with the given identities + makeTestIdentityService(megaCorp.getIdentity()) + ); + // DOCEND 11 + + @SuppressWarnings("unused") + // DOCSTART 12 + private final MockServices simpleLedgerServices = new MockServices( + // This is the identity of the node + megaCorp, + // Other identities the test node knows about + bigCorp, + alice + ); + // DOCEND 12 // DOCSTART 1 private ICommercialPaperState getPaper() { return new JavaCommercialPaper.State( - MEGA_CORP.ref(defaultRef), - MEGA_CORP.getParty(), - issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)), + megaCorp.ref(defaultRef), + megaCorp.getParty(), + issuedBy(DOLLARS(1000), megaCorp.ref(defaultRef)), TEST_TX_TIME.plus(7, ChronoUnit.DAYS) ); } @@ -75,7 +93,7 @@ public class CommercialPaperTest { ledger(ledgerServices, l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.attachments(JCP_PROGRAM_ID); return tx.verifies(); }); @@ -91,7 +109,7 @@ public class CommercialPaperTest { ledger(ledgerServices, l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.attachments(JCP_PROGRAM_ID); return tx.failsWith("the state is propagated"); }); @@ -102,15 +120,15 @@ public class CommercialPaperTest { // DOCSTART 5 @Test - public void simpleCPMoveSuccess() { + public void simpleCPMoveSuccessAndFailure() { ICommercialPaperState inState = getPaper(); ledger(ledgerServices, l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.attachments(JCP_PROGRAM_ID); tx.failsWith("the state is propagated"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty())); + tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty())); return tx.verifies(); }); return Unit.INSTANCE; @@ -118,6 +136,24 @@ public class CommercialPaperTest { } // DOCEND 5 + // DOCSTART 13 + @Test + public void simpleCPMoveSuccess() { + ICommercialPaperState inState = getPaper(); + ledger(ledgerServices, l -> { + l.transaction(tx -> { + tx.input(JCP_PROGRAM_ID, inState); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.attachments(JCP_PROGRAM_ID); + tx.timeWindow(TEST_TX_TIME); + tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty())); + return tx.verifies(); + }); + return Unit.INSTANCE; + }); + } + // DOCEND 13 + // DOCSTART 6 @Test public void simpleIssuanceWithTweak() { @@ -126,11 +162,11 @@ public class CommercialPaperTest { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.attachments(JCP_PROGRAM_ID); tx.tweak(tw -> { - tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue()); + tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tw.timeWindow(TEST_TX_TIME); return tw.failsWith("output states are issued by a command signer"); }); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); }); @@ -146,11 +182,11 @@ public class CommercialPaperTest { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.attachments(JCP_PROGRAM_ID); tx.tweak(tw -> { - tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue()); + tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tw.timeWindow(TEST_TX_TIME); return tw.failsWith("output states are issued by a command signer"); }); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); }); @@ -160,11 +196,11 @@ public class CommercialPaperTest { // DOCSTART 8 @Test public void chainCommercialPaper() { - PartyAndReference issuer = MEGA_CORP.ref(defaultRef); + PartyAndReference issuer = megaCorp.ref(defaultRef); ledger(ledgerServices, l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", - new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); + new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty())); tx.attachments(Cash.PROGRAM_ID); return Unit.INSTANCE; }); @@ -172,7 +208,7 @@ public class CommercialPaperTest { // Some CP is issued onto the ledger by MegaCorp. l.transaction("Issuance", tx -> { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.attachments(JCP_PROGRAM_ID); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); @@ -181,11 +217,11 @@ public class CommercialPaperTest { l.transaction("Trade", tx -> { tx.input("paper"); tx.input("alice's $900"); - tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); + tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty())); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); - tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty())); + tx.command(alice.getPublicKey(), new Cash.Commands.Move()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); return Unit.INSTANCE; @@ -196,11 +232,11 @@ public class CommercialPaperTest { // DOCSTART 9 @Test public void chainCommercialPaperDoubleSpend() { - PartyAndReference issuer = MEGA_CORP.ref(defaultRef); + PartyAndReference issuer = megaCorp.ref(defaultRef); ledger(ledgerServices, l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", - new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); + new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty())); tx.attachments(Cash.PROGRAM_ID); return Unit.INSTANCE; }); @@ -208,7 +244,7 @@ public class CommercialPaperTest { // Some CP is issued onto the ledger by MegaCorp. l.transaction("Issuance", tx -> { tx.output(Cash.PROGRAM_ID, "paper", getPaper()); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.attachments(JCP_PROGRAM_ID); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); @@ -217,11 +253,11 @@ public class CommercialPaperTest { l.transaction("Trade", tx -> { tx.input("paper"); tx.input("alice's $900"); - tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); + tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty())); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); - tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty())); + tx.command(alice.getPublicKey(), new Cash.Commands.Move()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); @@ -229,8 +265,8 @@ public class CommercialPaperTest { tx.input("paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); // We moved a paper to other pubkey. - tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty())); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty())); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); l.fails(); @@ -242,11 +278,11 @@ public class CommercialPaperTest { // DOCSTART 10 @Test public void chainCommercialPaperTweak() { - PartyAndReference issuer = MEGA_CORP.ref(defaultRef); + PartyAndReference issuer = megaCorp.ref(defaultRef); ledger(ledgerServices, l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", - new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); + new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty())); tx.attachments(Cash.PROGRAM_ID); return Unit.INSTANCE; }); @@ -254,7 +290,7 @@ public class CommercialPaperTest { // Some CP is issued onto the ledger by MegaCorp. l.transaction("Issuance", tx -> { tx.output(Cash.PROGRAM_ID, "paper", getPaper()); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.attachments(JCP_PROGRAM_ID); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); @@ -263,11 +299,11 @@ public class CommercialPaperTest { l.transaction("Trade", tx -> { tx.input("paper"); tx.input("alice's $900"); - tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); + tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty())); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); - tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty())); + tx.command(alice.getPublicKey(), new Cash.Commands.Move()); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); @@ -276,8 +312,8 @@ public class CommercialPaperTest { tx.input("paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); // We moved a paper to another pubkey. - tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty())); - tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty())); + tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); lw.fails(); diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt index a8cd842ac2..e6539a910b 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -1,11 +1,7 @@ package net.corda.docs.tutorial.testdsl -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.TransactionVerificationException -import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name -import net.corda.core.node.services.IdentityService import net.corda.core.utilities.days import net.corda.finance.DOLLARS import net.corda.finance.`issued by` @@ -15,9 +11,9 @@ import net.corda.finance.contracts.ICommercialPaperState import net.corda.finance.contracts.asset.CASH import net.corda.finance.contracts.asset.Cash import net.corda.testing.core.* -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.ledger +import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.transaction import org.junit.Rule import org.junit.Test @@ -25,32 +21,46 @@ import org.junit.Test class CommercialPaperTest { private companion object { val alice = TestIdentity(ALICE_NAME, 70) - val BIG_CORP_PUBKEY = generateKeyPair().public - val BOB = TestIdentity(BOB_NAME, 80).party - val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party + val bob = TestIdentity(BOB_NAME, 80) + val bigCorp = TestIdentity((CordaX500Name("BigCorp", "New York", "GB"))) + val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) - val ALICE get() = alice.party - val ALICE_PUBKEY get() = alice.publicKey - val MEGA_CORP get() = megaCorp.party - val MEGA_CORP_PUBKEY get() = megaCorp.publicKey } @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - // When creating the MockServices, you need to specify the packages that will contain the contracts you will use in this test - // In case you don't specify the 'cordappPackages' argument, the MockServices will be initialised with the Contracts found in the package of the current test - private val ledgerServices = MockServices(listOf("net.corda.finance.contracts"), MEGA_CORP.name, rigorousMock().also { - doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY) - doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) - }) + // DOCSTART 11 + private val ledgerServices = MockServices( + // A list of packages to scan for cordapps + cordappPackages = listOf("net.corda.finance.contracts"), + // The identity represented by this set of mock services. Defaults to a test identity. + // You can also use the alternative parameter initialIdentityName which accepts a + // [CordaX500Name] + initialIdentity = megaCorp, + // An implementation of IdentityService, which contains a list of all identities known + // to the node. Use [makeTestIdentityService] which returns an implementation of + // [InMemoryIdentityService] with the given identities + identityService = makeTestIdentityService(megaCorp.identity) + ) + // DOCEND 11 + + @Suppress("unused") + // DOCSTART 12 + private val simpleLedgerServices = MockServices( + // This is the identity of the node + megaCorp, + // Other identities the test node knows about + bigCorp, + alice + ) + // DOCEND 12 // DOCSTART 1 fun getPaper(): ICommercialPaperState = CommercialPaper.State( - issuance = MEGA_CORP.ref(123), - owner = MEGA_CORP, - faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), + issuance = megaCorp.party.ref(123), + owner = megaCorp.party, + faceValue = 1000.DOLLARS `issued by` megaCorp.party.ref(123), maturityDate = TEST_TX_TIME + 7.days ) // DOCEND 1 @@ -60,7 +70,7 @@ class CommercialPaperTest { @Test(expected = IllegalStateException::class) fun simpleCP() { val inState = getPaper() - ledgerServices.ledger(DUMMY_NOTARY) { + ledgerServices.ledger(dummyNotary.party) { transaction { attachments(CP_PROGRAM_ID) input(CP_PROGRAM_ID, inState) @@ -75,10 +85,10 @@ class CommercialPaperTest { @Test(expected = TransactionVerificationException.ContractRejection::class) fun simpleCPMove() { val inState = getPaper() - ledgerServices.ledger(DUMMY_NOTARY) { + ledgerServices.ledger(dummyNotary.party) { transaction { input(CP_PROGRAM_ID, inState) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) attachments(CP_PROGRAM_ID) verifies() } @@ -90,10 +100,10 @@ class CommercialPaperTest { @Test fun simpleCPMoveFails() { val inState = getPaper() - ledgerServices.ledger(DUMMY_NOTARY) { + ledgerServices.ledger(dummyNotary.party) { transaction { input(CP_PROGRAM_ID, inState) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) attachments(CP_PROGRAM_ID) `fails with`("the state is propagated") } @@ -103,35 +113,52 @@ class CommercialPaperTest { // DOCSTART 5 @Test - fun simpleCPMoveSuccess() { + fun simpleCPMoveFailureAndSuccess() { val inState = getPaper() - ledgerServices.ledger(DUMMY_NOTARY) { + ledgerServices.ledger(dummyNotary.party) { transaction { input(CP_PROGRAM_ID, inState) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) attachments(CP_PROGRAM_ID) `fails with`("the state is propagated") - output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE)) + output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party)) verifies() } } } // DOCEND 5 + // DOCSTART 13 + @Test + fun simpleCPMoveSuccess() { + val inState = getPaper() + ledgerServices.ledger(dummyNotary.party) { + transaction { + input(CP_PROGRAM_ID, inState) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + attachments(CP_PROGRAM_ID) + timeWindow(TEST_TX_TIME) + output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party)) + verifies() + } + } + } + // DOCEND 13 + // DOCSTART 6 @Test fun `simple issuance with tweak`() { - ledgerServices.ledger(DUMMY_NOTARY) { + ledgerServices.ledger(dummyNotary.party) { transaction { output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. attachments(CP_PROGRAM_ID) tweak { // The wrong pubkey. - command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(bigCorp.publicKey, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) `fails with`("output states are issued by a command signer") } - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) verifies() } @@ -142,16 +169,16 @@ class CommercialPaperTest { // DOCSTART 7 @Test fun `simple issuance with tweak and top level transaction`() { - ledgerServices.transaction(DUMMY_NOTARY) { + ledgerServices.transaction(dummyNotary.party) { output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. attachments(CP_PROGRAM_ID) tweak { // The wrong pubkey. - command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(bigCorp.publicKey, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) `fails with`("output states are issued by a command signer") } - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) verifies() } @@ -161,17 +188,17 @@ class CommercialPaperTest { // DOCSTART 8 @Test fun `chain commercial paper`() { - val issuer = MEGA_CORP.ref(123) - ledgerServices.ledger(DUMMY_NOTARY) { + val issuer = megaCorp.party.ref(123) + ledgerServices.ledger(dummyNotary.party) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) - output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party) } // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { output(CP_PROGRAM_ID, "paper", getPaper()) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) attachments(CP_PROGRAM_ID) timeWindow(TEST_TX_TIME) verifies() @@ -181,10 +208,10 @@ class CommercialPaperTest { transaction("Trade") { input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) - output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(ALICE)) - command(ALICE_PUBKEY, Cash.Commands.Move()) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party) + output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(alice.party)) + command(alice.publicKey, Cash.Commands.Move()) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) verifies() } } @@ -194,17 +221,17 @@ class CommercialPaperTest { // DOCSTART 9 @Test fun `chain commercial paper double spend`() { - val issuer = MEGA_CORP.ref(123) - ledgerServices.ledger(DUMMY_NOTARY) { + val issuer = megaCorp.party.ref(123) + ledgerServices.ledger(dummyNotary.party) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) - output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party) } // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { output(CP_PROGRAM_ID, "paper", getPaper()) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) attachments(CP_PROGRAM_ID) timeWindow(TEST_TX_TIME) verifies() @@ -213,18 +240,18 @@ class CommercialPaperTest { transaction("Trade") { input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) - output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(ALICE)) - command(ALICE_PUBKEY, Cash.Commands.Move()) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party) + output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(alice.party)) + command(alice.publicKey, Cash.Commands.Move()) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) verifies() } transaction { input("paper") // We moved a paper to another pubkey. - output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(BOB)) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(bob.party)) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) verifies() } @@ -236,17 +263,17 @@ class CommercialPaperTest { // DOCSTART 10 @Test fun `chain commercial tweak`() { - val issuer = MEGA_CORP.ref(123) - ledgerServices.ledger(DUMMY_NOTARY) { + val issuer = megaCorp.party.ref(123) + ledgerServices.ledger(dummyNotary.party) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) - output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party) } // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { output(CP_PROGRAM_ID, "paper", getPaper()) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) + command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) attachments(CP_PROGRAM_ID) timeWindow(TEST_TX_TIME) verifies() @@ -255,10 +282,10 @@ class CommercialPaperTest { transaction("Trade") { input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) - output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(ALICE)) - command(ALICE_PUBKEY, Cash.Commands.Move()) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party) + output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(alice.party)) + command(alice.publicKey, Cash.Commands.Move()) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) verifies() } @@ -266,8 +293,8 @@ class CommercialPaperTest { transaction { input("paper") // We moved a paper to another pubkey. - output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(BOB)) - command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) + output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(bob.party)) + command(megaCorp.publicKey, CommercialPaper.Commands.Move()) verifies() } fails()