mirror of
https://github.com/corda/corda.git
synced 2025-03-11 06:54:04 +00:00
[CORDA-1035] Flow test API docs
This commit is contained in:
parent
c94b606cf0
commit
904f706e10
467
docs/source/api-testing.rst
Normal file
467
docs/source/api-testing.rst
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
.. highlight:: kotlin
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||||
|
|
||||||
|
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<SignedTransaction> 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<SignedTransaction> 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<MyStateType>().states
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
node.getDatabase().transaction(tx -> {
|
||||||
|
List<MyStateType> 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 <flow-testing>`
|
||||||
|
* Further examples are available in the Example CorDapp in
|
||||||
|
`Java <https://github.com/corda/cordapp-example/blob/release-V3/java-source/src/test/java/com/example/flow/IOUFlowTests.java>`_ and
|
||||||
|
`Kotlin <https://github.com/corda/cordapp-example/blob/release-V3/kotlin-source/src/test/kotlin/com/example/flow/IOUFlowTests.kt>`_
|
||||||
|
|
||||||
|
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 <tutorial-test-dsl>`
|
||||||
|
* Further examples are available in the Example CorDapp in
|
||||||
|
`Java <https://github.com/corda/cordapp-example/blob/release-V3/java-source/src/test/java/com/example/flow/IOUFlowTests.java>`_ and
|
||||||
|
`Kotlin <https://github.com/corda/cordapp-example/blob/release-V3/kotlin-source/src/test/kotlin/com/example/flow/IOUFlowTests.kt>`_
|
@ -46,7 +46,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'R3 Corda'
|
project = u'R3 Corda'
|
||||||
copyright = u'2017, R3 Limited'
|
copyright = u'2018, R3 Limited'
|
||||||
author = u'R3 DLG'
|
author = u'R3 DLG'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
@ -17,6 +17,7 @@ The following are the core APIs that are used in the development of CorDapps:
|
|||||||
api-service-hub
|
api-service-hub
|
||||||
api-rpc
|
api-rpc
|
||||||
api-core-types
|
api-core-types
|
||||||
|
api-testing
|
||||||
|
|
||||||
Before reading this page, you should be familiar with the :doc:`key concepts of Corda <key-concepts>`.
|
Before reading this page, you should be familiar with the :doc:`key concepts of Corda <key-concepts>`.
|
||||||
|
|
||||||
|
@ -27,19 +27,44 @@ import static net.corda.testing.core.TestConstants.BOB_NAME;
|
|||||||
import static net.corda.testing.core.TestConstants.TEST_TX_TIME;
|
import static net.corda.testing.core.TestConstants.TEST_TX_TIME;
|
||||||
|
|
||||||
public class CommercialPaperTest {
|
public class CommercialPaperTest {
|
||||||
private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L);
|
private static final TestIdentity alice = new TestIdentity(ALICE_NAME, 70L);
|
||||||
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic();
|
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 bob = new TestIdentity(BOB_NAME, 80L);
|
||||||
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
||||||
private final byte[] defaultRef = {123};
|
private final byte[] defaultRef = {123};
|
||||||
private final MockServices ledgerServices = new MockServices(MEGA_CORP);
|
|
||||||
|
// DOCSTART 11
|
||||||
|
private final MockServices ledgerServices = new MockServices(
|
||||||
|
// A list of packages to scan for cordapps
|
||||||
|
emptyList(),
|
||||||
|
// 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
|
// DOCSTART 1
|
||||||
private ICommercialPaperState getPaper() {
|
private ICommercialPaperState getPaper() {
|
||||||
return new JavaCommercialPaper.State(
|
return new JavaCommercialPaper.State(
|
||||||
MEGA_CORP.ref(defaultRef),
|
megaCorp.ref(defaultRef),
|
||||||
MEGA_CORP.getParty(),
|
megaCorp.getParty(),
|
||||||
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)),
|
issuedBy(DOLLARS(1000), megaCorp.ref(defaultRef)),
|
||||||
TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
|
TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -69,7 +94,7 @@ public class CommercialPaperTest {
|
|||||||
ledger(ledgerServices, l -> {
|
ledger(ledgerServices, l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
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.attachments(JCP_PROGRAM_ID);
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
@ -85,7 +110,7 @@ public class CommercialPaperTest {
|
|||||||
ledger(ledgerServices, l -> {
|
ledger(ledgerServices, l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
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.attachments(JCP_PROGRAM_ID);
|
||||||
return tx.failsWith("the state is propagated");
|
return tx.failsWith("the state is propagated");
|
||||||
});
|
});
|
||||||
@ -96,15 +121,15 @@ public class CommercialPaperTest {
|
|||||||
|
|
||||||
// DOCSTART 5
|
// DOCSTART 5
|
||||||
@Test
|
@Test
|
||||||
public void simpleCPMoveSuccess() {
|
public void simpleCPMoveSuccessAndFailure() {
|
||||||
ICommercialPaperState inState = getPaper();
|
ICommercialPaperState inState = getPaper();
|
||||||
ledger(ledgerServices, l -> {
|
ledger(ledgerServices, l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
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.attachments(JCP_PROGRAM_ID);
|
||||||
tx.failsWith("the state is propagated");
|
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 tx.verifies();
|
||||||
});
|
});
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
@ -112,6 +137,24 @@ public class CommercialPaperTest {
|
|||||||
}
|
}
|
||||||
// DOCEND 5
|
// 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
|
// DOCSTART 6
|
||||||
@Test
|
@Test
|
||||||
public void simpleIssuanceWithTweak() {
|
public void simpleIssuanceWithTweak() {
|
||||||
@ -120,11 +163,11 @@ public class CommercialPaperTest {
|
|||||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||||
tx.attachments(JCP_PROGRAM_ID);
|
tx.attachments(JCP_PROGRAM_ID);
|
||||||
tx.tweak(tw -> {
|
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);
|
tw.timeWindow(TEST_TX_TIME);
|
||||||
return tw.failsWith("output states are issued by a command signer");
|
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);
|
tx.timeWindow(TEST_TX_TIME);
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
@ -140,11 +183,11 @@ public class CommercialPaperTest {
|
|||||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||||
tx.attachments(JCP_PROGRAM_ID);
|
tx.attachments(JCP_PROGRAM_ID);
|
||||||
tx.tweak(tw -> {
|
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);
|
tw.timeWindow(TEST_TX_TIME);
|
||||||
return tw.failsWith("output states are issued by a command signer");
|
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);
|
tx.timeWindow(TEST_TX_TIME);
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
@ -154,11 +197,11 @@ public class CommercialPaperTest {
|
|||||||
// DOCSTART 8
|
// DOCSTART 8
|
||||||
@Test
|
@Test
|
||||||
public void chainCommercialPaper() {
|
public void chainCommercialPaper() {
|
||||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
PartyAndReference issuer = megaCorp.ref(defaultRef);
|
||||||
ledger(ledgerServices, l -> {
|
ledger(ledgerServices, l -> {
|
||||||
l.unverifiedTransaction(tx -> {
|
l.unverifiedTransaction(tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
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);
|
tx.attachments(Cash.PROGRAM_ID);
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
@ -166,7 +209,7 @@ public class CommercialPaperTest {
|
|||||||
// Some CP is issued onto the ledger by MegaCorp.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
l.transaction("Issuance", tx -> {
|
l.transaction("Issuance", tx -> {
|
||||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
|
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.attachments(JCP_PROGRAM_ID);
|
||||||
tx.timeWindow(TEST_TX_TIME);
|
tx.timeWindow(TEST_TX_TIME);
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
@ -175,11 +218,11 @@ public class CommercialPaperTest {
|
|||||||
l.transaction("Trade", tx -> {
|
l.transaction("Trade", tx -> {
|
||||||
tx.input("paper");
|
tx.input("paper");
|
||||||
tx.input("alice's $900");
|
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");
|
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
|
||||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
|
||||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
@ -190,11 +233,11 @@ public class CommercialPaperTest {
|
|||||||
// DOCSTART 9
|
// DOCSTART 9
|
||||||
@Test
|
@Test
|
||||||
public void chainCommercialPaperDoubleSpend() {
|
public void chainCommercialPaperDoubleSpend() {
|
||||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
PartyAndReference issuer = megaCorp.ref(defaultRef);
|
||||||
ledger(ledgerServices, l -> {
|
ledger(ledgerServices, l -> {
|
||||||
l.unverifiedTransaction(tx -> {
|
l.unverifiedTransaction(tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
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);
|
tx.attachments(Cash.PROGRAM_ID);
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
@ -202,7 +245,7 @@ public class CommercialPaperTest {
|
|||||||
// Some CP is issued onto the ledger by MegaCorp.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
l.transaction("Issuance", tx -> {
|
l.transaction("Issuance", tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
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.attachments(JCP_PROGRAM_ID);
|
||||||
tx.timeWindow(TEST_TX_TIME);
|
tx.timeWindow(TEST_TX_TIME);
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
@ -211,11 +254,11 @@ public class CommercialPaperTest {
|
|||||||
l.transaction("Trade", tx -> {
|
l.transaction("Trade", tx -> {
|
||||||
tx.input("paper");
|
tx.input("paper");
|
||||||
tx.input("alice's $900");
|
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");
|
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
|
||||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
|
||||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -223,8 +266,8 @@ public class CommercialPaperTest {
|
|||||||
tx.input("paper");
|
tx.input("paper");
|
||||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||||
// We moved a paper to other pubkey.
|
// We moved a paper to other pubkey.
|
||||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
|
||||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
l.fails();
|
l.fails();
|
||||||
@ -236,11 +279,11 @@ public class CommercialPaperTest {
|
|||||||
// DOCSTART 10
|
// DOCSTART 10
|
||||||
@Test
|
@Test
|
||||||
public void chainCommercialPaperTweak() {
|
public void chainCommercialPaperTweak() {
|
||||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
PartyAndReference issuer = megaCorp.ref(defaultRef);
|
||||||
ledger(ledgerServices, l -> {
|
ledger(ledgerServices, l -> {
|
||||||
l.unverifiedTransaction(tx -> {
|
l.unverifiedTransaction(tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
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);
|
tx.attachments(Cash.PROGRAM_ID);
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
@ -248,7 +291,7 @@ public class CommercialPaperTest {
|
|||||||
// Some CP is issued onto the ledger by MegaCorp.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
l.transaction("Issuance", tx -> {
|
l.transaction("Issuance", tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
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.attachments(JCP_PROGRAM_ID);
|
||||||
tx.timeWindow(TEST_TX_TIME);
|
tx.timeWindow(TEST_TX_TIME);
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
@ -257,11 +300,11 @@ public class CommercialPaperTest {
|
|||||||
l.transaction("Trade", tx -> {
|
l.transaction("Trade", tx -> {
|
||||||
tx.input("paper");
|
tx.input("paper");
|
||||||
tx.input("alice's $900");
|
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");
|
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
|
||||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
|
||||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -270,8 +313,8 @@ public class CommercialPaperTest {
|
|||||||
tx.input("paper");
|
tx.input("paper");
|
||||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||||
// We moved a paper to another pubkey.
|
// We moved a paper to another pubkey.
|
||||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
|
||||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||||
return tx.verifies();
|
return tx.verifies();
|
||||||
});
|
});
|
||||||
lw.fails();
|
lw.fails();
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package net.corda.docs.tutorial.testdsl
|
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.contracts.TransactionVerificationException
|
||||||
import net.corda.core.crypto.generateKeyPair
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.node.services.IdentityService
|
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
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.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import net.corda.testing.node.ledger
|
import net.corda.testing.node.ledger
|
||||||
|
import net.corda.testing.node.makeTestIdentityService
|
||||||
import net.corda.testing.node.transaction
|
import net.corda.testing.node.transaction
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -25,30 +21,46 @@ import org.junit.Test
|
|||||||
class CommercialPaperTest {
|
class CommercialPaperTest {
|
||||||
private companion object {
|
private companion object {
|
||||||
val alice = TestIdentity(ALICE_NAME, 70)
|
val alice = TestIdentity(ALICE_NAME, 70)
|
||||||
val BIG_CORP_PUBKEY = generateKeyPair().public
|
val bob = TestIdentity(BOB_NAME, 80)
|
||||||
val BOB = TestIdentity(BOB_NAME, 80).party
|
val bigCorp = TestIdentity((CordaX500Name("BigCorp", "New York", "GB")))
|
||||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
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
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityService>().also {
|
// DOCSTART 11
|
||||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
private val ledgerServices = MockServices(
|
||||||
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
|
// A list of packages to scan for cordapps
|
||||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
cordappPackages = emptyList(),
|
||||||
})
|
// 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
|
// DOCSTART 1
|
||||||
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||||
issuance = MEGA_CORP.ref(123),
|
issuance = megaCorp.party.ref(123),
|
||||||
owner = MEGA_CORP,
|
owner = megaCorp.party,
|
||||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
faceValue = 1000.DOLLARS `issued by` megaCorp.party.ref(123),
|
||||||
maturityDate = TEST_TX_TIME + 7.days
|
maturityDate = TEST_TX_TIME + 7.days
|
||||||
)
|
)
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
@ -58,7 +70,7 @@ class CommercialPaperTest {
|
|||||||
@Test(expected = IllegalStateException::class)
|
@Test(expected = IllegalStateException::class)
|
||||||
fun simpleCP() {
|
fun simpleCP() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
transaction {
|
transaction {
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
@ -73,10 +85,10 @@ class CommercialPaperTest {
|
|||||||
@Test(expected = TransactionVerificationException.ContractRejection::class)
|
@Test(expected = TransactionVerificationException.ContractRejection::class)
|
||||||
fun simpleCPMove() {
|
fun simpleCPMove() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
transaction {
|
transaction {
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
@ -88,10 +100,10 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun simpleCPMoveFails() {
|
fun simpleCPMoveFails() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
transaction {
|
transaction {
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
`fails with`("the state is propagated")
|
`fails with`("the state is propagated")
|
||||||
}
|
}
|
||||||
@ -101,35 +113,52 @@ class CommercialPaperTest {
|
|||||||
|
|
||||||
// DOCSTART 5
|
// DOCSTART 5
|
||||||
@Test
|
@Test
|
||||||
fun simpleCPMoveSuccess() {
|
fun simpleCPMoveFailureAndSuccess() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
transaction {
|
transaction {
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
`fails with`("the state is propagated")
|
`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()
|
verifies()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DOCEND 5
|
// 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
|
// DOCSTART 6
|
||||||
@Test
|
@Test
|
||||||
fun `simple issuance with tweak`() {
|
fun `simple issuance with tweak`() {
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
transaction {
|
transaction {
|
||||||
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
tweak {
|
tweak {
|
||||||
// The wrong pubkey.
|
// The wrong pubkey.
|
||||||
command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue())
|
command(bigCorp.publicKey, CommercialPaper.Commands.Issue())
|
||||||
timeWindow(TEST_TX_TIME)
|
timeWindow(TEST_TX_TIME)
|
||||||
`fails with`("output states are issued by a command signer")
|
`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)
|
timeWindow(TEST_TX_TIME)
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
@ -140,16 +169,16 @@ class CommercialPaperTest {
|
|||||||
// DOCSTART 7
|
// DOCSTART 7
|
||||||
@Test
|
@Test
|
||||||
fun `simple issuance with tweak and top level transaction`() {
|
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.
|
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
tweak {
|
tweak {
|
||||||
// The wrong pubkey.
|
// The wrong pubkey.
|
||||||
command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue())
|
command(bigCorp.publicKey, CommercialPaper.Commands.Issue())
|
||||||
timeWindow(TEST_TX_TIME)
|
timeWindow(TEST_TX_TIME)
|
||||||
`fails with`("output states are issued by a command signer")
|
`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)
|
timeWindow(TEST_TX_TIME)
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
@ -159,17 +188,17 @@ class CommercialPaperTest {
|
|||||||
// DOCSTART 8
|
// DOCSTART 8
|
||||||
@Test
|
@Test
|
||||||
fun `chain commercial paper`() {
|
fun `chain commercial paper`() {
|
||||||
val issuer = MEGA_CORP.ref(123)
|
val issuer = megaCorp.party.ref(123)
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
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.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
output(CP_PROGRAM_ID, "paper", getPaper())
|
output(CP_PROGRAM_ID, "paper", getPaper())
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
timeWindow(TEST_TX_TIME)
|
timeWindow(TEST_TX_TIME)
|
||||||
verifies()
|
verifies()
|
||||||
@ -179,10 +208,10 @@ class CommercialPaperTest {
|
|||||||
transaction("Trade") {
|
transaction("Trade") {
|
||||||
input("paper")
|
input("paper")
|
||||||
input("alice's $900")
|
input("alice's $900")
|
||||||
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP)
|
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
|
||||||
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE))
|
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
|
||||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
command(alice.publicKey, Cash.Commands.Move())
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,17 +221,17 @@ class CommercialPaperTest {
|
|||||||
// DOCSTART 9
|
// DOCSTART 9
|
||||||
@Test
|
@Test
|
||||||
fun `chain commercial paper double spend`() {
|
fun `chain commercial paper double spend`() {
|
||||||
val issuer = MEGA_CORP.ref(123)
|
val issuer = megaCorp.party.ref(123)
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
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.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
output(CP_PROGRAM_ID, "paper", getPaper())
|
output(CP_PROGRAM_ID, "paper", getPaper())
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
timeWindow(TEST_TX_TIME)
|
timeWindow(TEST_TX_TIME)
|
||||||
verifies()
|
verifies()
|
||||||
@ -211,18 +240,18 @@ class CommercialPaperTest {
|
|||||||
transaction("Trade") {
|
transaction("Trade") {
|
||||||
input("paper")
|
input("paper")
|
||||||
input("alice's $900")
|
input("alice's $900")
|
||||||
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP)
|
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
|
||||||
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE))
|
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
|
||||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
command(alice.publicKey, Cash.Commands.Move())
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
input("paper")
|
input("paper")
|
||||||
// We moved a paper to another pubkey.
|
// We moved a paper to another pubkey.
|
||||||
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(BOB))
|
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(bob.party))
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,17 +263,17 @@ class CommercialPaperTest {
|
|||||||
// DOCSTART 10
|
// DOCSTART 10
|
||||||
@Test
|
@Test
|
||||||
fun `chain commercial tweak`() {
|
fun `chain commercial tweak`() {
|
||||||
val issuer = MEGA_CORP.ref(123)
|
val issuer = megaCorp.party.ref(123)
|
||||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
ledgerServices.ledger(dummyNotary.party) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
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.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
output(CP_PROGRAM_ID, "paper", getPaper())
|
output(CP_PROGRAM_ID, "paper", getPaper())
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
timeWindow(TEST_TX_TIME)
|
timeWindow(TEST_TX_TIME)
|
||||||
verifies()
|
verifies()
|
||||||
@ -253,10 +282,10 @@ class CommercialPaperTest {
|
|||||||
transaction("Trade") {
|
transaction("Trade") {
|
||||||
input("paper")
|
input("paper")
|
||||||
input("alice's $900")
|
input("alice's $900")
|
||||||
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP)
|
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
|
||||||
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE))
|
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
|
||||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
command(alice.publicKey, Cash.Commands.Move())
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,8 +293,8 @@ class CommercialPaperTest {
|
|||||||
transaction {
|
transaction {
|
||||||
input("paper")
|
input("paper")
|
||||||
// We moved a paper to another pubkey.
|
// We moved a paper to another pubkey.
|
||||||
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(BOB))
|
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(bob.party))
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
fails()
|
fails()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user