如何将Corda中的Acceptor流隔离以进行单元测试?

时间:2018-01-09 10:57:09

标签: corda

我试图在Corda中对一个Acceptor流进行单元测试,而不是从Initiating流中调用它。具体来说,我想发送一个无效的SignedTransaction(无效,因为它未通过输出状态协定验证),以确保Acceptor流按预期处理它并拒绝签名。

如果通过调用Initiating Flow来测试Acceptor流程,那么在调用Acceptor流之前,无效的txn将在Initiator流程侧被踢出。

1 个答案:

答案 0 :(得分:2)

目前没有公共API可以实现这一目标。我在这里提出了一个JIRA添加了一个:https://r3-cev.atlassian.net/browse/CORDA-916

但是,从Corda V2开始,您可以按如下方式隔离测试响应流:

// This is the real implementation of Initiator.
@InitiatingFlow
open class Initiator(val counterparty: Party) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        val session = initiateFlow(counterparty)
        session.send("goodString")
    }
}

// This is the response flow that we want to isolate for testing.
@InitiatedBy(Initiator::class)
class Responder(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        val string = counterpartySession.receive<String>().unwrap { contents -> contents }
        if (string != "goodString") {
            throw FlowException("String did not contain the expected message.")
        }
    }
}

class FlowTests {
    private lateinit var network: MockNetwork
    private lateinit var a: StartedNode<MockNode>
    private lateinit var b: StartedNode<MockNode>

    @Before
    fun setup() {
        setCordappPackages("com.template")
        network = MockNetwork()
        val nodes = network.createSomeNodes(2)
        a = nodes.partyNodes[0]
        b = nodes.partyNodes[1]
        nodes.partyNodes.forEach {
            it.registerInitiatedFlow(Responder::class.java)
        }
        network.runNetwork()
    }

    @After
    fun tearDown() {
        network.stopNodes()
        unsetCordappPackages()
    }

    // This is a fake implementation of Initiator to check how Responder responds to non-golden-path scenarios.
    @InitiatingFlow
    class BadInitiator(val counterparty: Party): FlowLogic<Unit>() {
        @Suspendable
        override fun call() {
            val session = initiateFlow(counterparty)
            session.send("badString")
        }
    }

    @Test
    fun `test`() {
        // This method returns the Responder flow object used by node B.
        val initiatedResponderFlowObservable = b.internals.internalRegisterFlowFactory(
                // We tell node B to respond to BadInitiator with Responder.
                initiatingFlowClass = BadInitiator::class.java,
                initiatedFlowClass = Responder::class.java,
                flowFactory = InitiatedFlowFactory.CorDapp(flowVersion = 0, appName = "", factory = { flowSession -> Responder(flowSession) }),
                // We want to observe the Responder flow object to check for errors.
                track = true)

        val initiatedResponderFlowFuture = initiatedResponderFlowObservable.toFuture()

        // We run the BadInitiator flow on node A.
        val flow = BadInitiator(b.info.chooseIdentity())
        val future = a.services.startFlow(flow)
        network.runNetwork()
        future.resultFuture.get()

        // We check that the invocation of the Responder flow object has caused an ExecutionException.
        val initiatedResponderFlow = initiatedResponderFlowFuture.get()
        val initiatedResponderFlowResultFuture = initiatedResponderFlow.stateMachine.resultFuture
        val exceptionFromFlow = assertFailsWith<ExecutionException> {
            initiatedResponderFlowResultFuture.get()
        }.cause
        assertThat(exceptionFromFlow).isInstanceOf(FlowException::class.java).hasMessage("String did not contain the expected message.")
    }
}

让我们逐步完成此代码:

  • 我们定义一个BadInitiator,它应该导致响应流抛出错误
  • 我们通过调用BadInitiator,响应流程
  • 来注册测试节点B以响应Responder
  • 测试节点A运行带有测试节点B的BadInitiator流程
  • 我们检查这是否导致测试节点B Responder流向FlowException投掷正确的消息。

这里的代码与Java相同:

第一个文件 - 流程:

public class TemplateFlow {
    // This is the real implementation of Initiator.
    @InitiatingFlow
    @StartableByRPC
    public static class Initiator extends FlowLogic<Void> {
        private Party counterparty;

        public Initiator(Party counterparty) {
            this.counterparty = counterparty;
        }

        @Suspendable
        @Override public Void call() {
            FlowSession session = initiateFlow(counterparty);
            session.send("goodString");
            return null;
        }
    }

    // This is the response flow that we want to isolate for testing.
    @InitiatedBy(Initiator.class)
    public static class Responder extends FlowLogic<Void> {
        private FlowSession counterpartySession;

        public Responder(FlowSession counterpartySession) {
            this.counterpartySession = counterpartySession;
        }

        @Suspendable
        @Override
        public Void call() throws FlowException {
            UntrustworthyData<String> packet = counterpartySession.receive(String.class);
            String string = packet.unwrap(data -> data);
            if (!string.equals("goodString")) {
                throw new FlowException("String did not contain the expected message.");
            }
            return null;
        }
    }
}

第二个文件 - 假的&#34;坏的发起人&#34;流速:

@InitiatingFlow
class BadInitiator extends FlowLogic<Void> {
    private Party counterparty;

    public BadInitiator(Party counterparty) {
        this.counterparty = counterparty;
    }

    @Suspendable
    @Override public Void call() {
        FlowSession session = initiateFlow(counterparty);
        session.send("badString");
        return null;
    }
}

第三个文件 - 流量测试:

public class FlowTests {
    private MockNetwork network;
    private StartedNode<MockNetwork.MockNode> a;
    private StartedNode<MockNetwork.MockNode> b;

    @Before
    public void setup() {
        setCordappPackages("com.template");
        network = new MockNetwork();
        MockNetwork.BasketOfNodes nodes = network.createSomeNodes(2);
        a = nodes.getPartyNodes().get(0);
        b = nodes.getPartyNodes().get(1);
        for (StartedNode<MockNetwork.MockNode> node : nodes.getPartyNodes()) {
            node.registerInitiatedFlow(Responder.class);
        }
        network.runNetwork();
    }

    @After
    public void tearDown() {
        network.stopNodes();
        unsetCordappPackages();
    }

    @Rule
    public final ExpectedException exception = ExpectedException.none();

    // TODO: Move this into being a lambda.
    private static Responder factory(Object flowSession) {
        return new Responder((FlowSession) flowSession);
    }

    private InitiatedFlowFactory.CorDapp<Responder> flowFactory = new InitiatedFlowFactory.CorDapp<>(0, "", FlowTests::factory);

    @Test
    public void test() throws Exception {
        // This method returns the Responder flow object used by node B.
        Observable initiatedResponderFlowObservable = b.getInternals().internalRegisterFlowFactory(
                // We tell node B to respond to BadInitiator with Responder.
                // We want to observe the Responder flow object to check for errors.
                BadInitiator.class, flowFactory, Responder.class, true);

        CordaFuture<Responder> initiatedResponderFlowFuture = Utils.toFuture(initiatedResponderFlowObservable);

        // We run the BadInitiator flow on node A.
        BadInitiator flow = new BadInitiator(b.getInfo().getLegalIdentities().get(0));
        CordaFuture<Void> future = a.getServices().startFlow(flow).getResultFuture();
        network.runNetwork();
        future.get();

        // We check that the invocation of the Responder flow object has caused an ExecutionException.
        Responder initiatedResponderFlow = initiatedResponderFlowFuture.get();
        CordaFuture initiatedResponderFlowResultFuture = initiatedResponderFlow.getStateMachine().getResultFuture();
        exception.expectCause(instanceOf(FlowException.class));
        exception.expectMessage("String did not contain the expected message.");
        initiatedResponderFlowResultFuture.get();
    }
}