diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 041ee3ca1e..c84fd90c0f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,8 @@ Unreleased * Updating postgres dependency to 42.2.5 +* Test ``CordaService`` s can be installed on mock nodes using ``UnstartedMockNode.installCordaService``. + .. _changelog_v4.0: Version 4.0 diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index a32f880b4c..b9e7171224 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -573,7 +573,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val loadedServices = cordappLoader.cordapps.flatMap { it.services } loadedServices.forEach { try { - installCordaService(flowStarter, it) + installCordaService(it) } catch (e: NoSuchMethodException) { log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + ServiceHub::class.java.name) @@ -637,7 +637,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance) } - private fun installCordaService(flowStarter: FlowStarter, serviceClass: Class) { + fun installCordaService(serviceClass: Class): T { serviceClass.requireAnnotation() val service = try { @@ -659,6 +659,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, service.tokenize() log.info("Installed ${serviceClass.name} Corda service") + + return service } private fun registerCordappFlows() { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt index eb23b72146..6177173298 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt @@ -11,6 +11,8 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub +import net.corda.core.node.services.CordaService +import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.getOrThrow import net.corda.node.services.config.NodeConfiguration import net.corda.testing.common.internal.testNetworkParameters @@ -137,6 +139,15 @@ class UnstartedMockNode private constructor(private val node: InternalMockNetwor /** An identifier for the node. By default this is allocated sequentially in a [MockNetwork] **/ val id get() : Int = node.id + /** + * Install a custom test-only [CordaService]. + * + * NOTE: There is no need to call this method if the service class is defined in the CorDapp and the [TestCordapp] API is used. + * + * @return the instance of the service object. + */ + fun installCordaService(serviceClass: Class): T = node.installCordaService(serviceClass) + /** * Start the node * @@ -180,6 +191,8 @@ class StartedMockNode private constructor(private val node: TestStartedNode) { /** * Manually register an initiating-responder flow pair based on the [FlowLogic] annotations. * + * NOTE: There is no need to call this method if the flow pair is defined in the CorDapp and the [TestCordapp] API is used. + * * @param initiatedFlowClass [FlowLogic] class which is annotated with [InitiatedBy]. * @return An [Observable] which emits responder flows each time one is executed. */ diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTest.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTest.kt index 1e841a2acc..81bfdca532 100644 --- a/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTest.kt +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTest.kt @@ -1,5 +1,16 @@ package net.corda.testing.node +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.Party +import net.corda.core.node.AppServiceHub +import net.corda.core.node.services.CordaService +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap import net.corda.testing.core.* import org.assertj.core.api.Assertions.* import org.junit.After @@ -44,4 +55,34 @@ class MockNetworkTest { val ex = assertFailsWith { unstarted.started } assertThat(ex).hasMessage("Node ID=$NODE_ID is not running") } + + @Test + fun installCordaService() { + val unstarted = mockNetwork.createUnstartedNode() + assertThat(unstarted.installCordaService(TestService::class.java)).isNotNull() + val started = unstarted.start() + started.registerInitiatedFlow(TestResponder::class.java) + val future = started.startFlow(TestInitiator(started.info.singleIdentity())) + mockNetwork.runNetwork() + assertThat(future.getOrThrow()).isEqualTo(TestService::class.java.name) + } + + @CordaService + class TestService(services: AppServiceHub) : SingletonSerializeAsToken() + + @InitiatingFlow + class TestInitiator(private val party: Party) : FlowLogic() { + @Suspendable + override fun call(): String { + return initiateFlow(party).receive().unwrap { it } + } + } + + @InitiatedBy(TestInitiator::class) + class TestResponder(private val otherSide: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + otherSide.send(serviceHub.cordaService(TestService::class.java).javaClass.name) + } + } } \ No newline at end of file