我试图在Corda中对一个Acceptor流进行单元测试,而不是从Initiating流中调用它。具体来说,我想发送一个无效的SignedTransaction(无效,因为它未通过输出状态协定验证),以确保Acceptor流按预期处理它并拒绝签名。
如果通过调用Initiating Flow来测试Acceptor流程,那么在调用Acceptor流之前,无效的txn将在Initiator流程侧被踢出。
答案 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
,响应流程Responder
BadInitiator
流程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();
}
}