Skip to content

Commit

Permalink
finalized contract testing for both java and kotlin
Browse files Browse the repository at this point in the history
  • Loading branch information
parisyup committed May 20, 2024
1 parent 69f4ac8 commit 24ac614
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@
import net.corda.v5.membership.NotaryInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.PublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

import static java.util.stream.Collectors.toList;

public class TestContractFlow implements ClientStartableFlow {
Expand Down Expand Up @@ -66,11 +64,12 @@ public String call(ClientRequestBody requestBody) {
if (otherMember == null) {
throw new CordaRuntimeException("MemberLookup can't find otherMember specified in flow arguments.");
}

// Obtain the Notary name and public key.
NotaryInfo notary = notaryLookup.getNotaryServices().stream().findFirst().get();

UUID iouStateId = null;

// Create a well formed transaction with an output State which can be referenced
// as an input StateRef in the tests
try {
IOUState iouState = new IOUState(
10,
Expand Down Expand Up @@ -116,6 +115,13 @@ public String call(ClientRequestBody requestBody) {
// Issue needs only one output
results.put("Issue needs only one output", testIssueNeedsOnlyOneOutput(myInfo, otherMember, notary));

// Issue needs only one output
results.put("Settle needs only one input", testSettleNeedsOnlyOneInput(myInfo, otherMember, notary));

// Transfer needs only one output
results.put("Transfer needs only one input", testTransferNeedsOnlyOneInput(myInfo, otherMember, notary));


return results.toString();

} catch (Exception e) {
Expand All @@ -127,6 +133,7 @@ public String call(ClientRequestBody requestBody) {
@Suspendable
private String testMultipleCommandsNotPermitted(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
//create sample output state for the test
IOUState iouState = new IOUState(
10,
myInfo.getName(),
Expand All @@ -136,6 +143,7 @@ private String testMultipleCommandsNotPermitted(MemberInfo myInfo, MemberInfo ot
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

//build sample transaction to test
var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
Expand All @@ -145,15 +153,21 @@ private String testMultipleCommandsNotPermitted(MemberInfo myInfo, MemberInfo ot
.addCommand(new IOUContract.Transfer())
.addSignatories(iouState.getParticipants());

//sign the test to check if it fails
var signedTransaction = txBuilder.toSignedTransaction();

//no error found. Contract failed.
return "Fail";

//catching the error to see if the contract passed
} catch (Exception e) {
//check if gotten the expected error
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("Require a single command")) {
//expected error found so the contract passes the test
return "Pass";
} else {
//different error thrown. Contract failed
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
Expand Down Expand Up @@ -294,5 +308,84 @@ private String testIssueNeedsOnlyOneOutput(MemberInfo myInfo, MemberInfo otherMe
return "Contract failed but with a different Exception: " + e.getMessage();
}
}

}

@Suspendable
private String testSettleNeedsOnlyOneInput(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouOutputState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouOutputState)
.addCommand(new IOUContract.Transfer())
.addSignatories(iouOutputState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("one input")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}

@Suspendable
private String testTransferNeedsOnlyOneInput(MemberInfo myInfo, MemberInfo otherMember, NotaryInfo notary) {
try {
IOUState iouOutputState = new IOUState(
10,
myInfo.getName(),
otherMember.getName(),
3,
UUID.randomUUID(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);

var txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouOutputState)
.addCommand(new IOUContract.Transfer())
.addSignatories(iouOutputState.getParticipants());

var signedTransaction = txBuilder.toSignedTransaction();

return "Fail";

} catch (Exception e) {
String exceptionMessage = e.getMessage() != null ? e.getMessage() : "No exception message";
if (exceptionMessage.contains("one input")) {
return "Pass";
} else {
return "Contract failed but with a different Exception: " + e.getMessage();
}
}
}
}


/*
RequestBody for triggering the flow via http-rpc:
{
"clientRequestId": "dummy-1",
"flowClassName": "com.r3.developers.samples.obligation.workflows.TestContractFlow",
"requestBody": {
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
}
}
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ class IOUContract : Contract {
// Rules applied only to transactions with the Settle Command.
is Settle -> {
"When command is Update there should be one and only one output state." using (transaction.outputContractStates.size == 1)
"When command is Update there should be one and only one input state." using (transaction.inputContractStates.size == 1)
}
// Rules applied only to transactions with the Transfer Command.
is Transfer -> {
"When command is Update there should be one and only one output state." using (transaction.outputContractStates.size == 1)
"When command is Update there should be one and only one input state." using (transaction.inputContractStates.size == 1)
}
else -> {
throw CordaRuntimeException("Command not allowed.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class TestContractFlow: ClientStartableFlow {
.setNotary(notary.name)
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addInputState(inputStateRef)
.addCommand(IOUContract.Issue())
.addCommand(IOUContract.Transfer())
.addSignatories(iouState.participants)
Expand All @@ -137,7 +138,7 @@ class TestContractFlow: ClientStartableFlow {
"Contract failed but with a different Exception: ${e.message}"
}
}
// Multiple Commands not permitted
// Only Two Participants
results["Only Two Participants"] = try {
val iouState = IOUState(
amount = 10,
Expand Down Expand Up @@ -170,7 +171,7 @@ class TestContractFlow: ClientStartableFlow {
}


// Multiple Commands not permitted
// Settle needs only one output
results["Settle needs only one output"] = try {
val iouState = IOUState(
amount = 10,
Expand All @@ -186,6 +187,7 @@ class TestContractFlow: ClientStartableFlow {
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addOutputState(iouState)
.addInputState(inputStateRef)
.addCommand(IOUContract.Settle())
.addSignatories(iouState.participants)

Expand All @@ -204,7 +206,7 @@ class TestContractFlow: ClientStartableFlow {
}


// Multiple Commands not permitted
// Transfer needs only one output
results["Transfer needs only one output"] = try {
val iouState = IOUState(
amount = 10,
Expand All @@ -220,6 +222,7 @@ class TestContractFlow: ClientStartableFlow {
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addOutputState(iouState)
.addInputState(inputStateRef)
.addCommand(IOUContract.Transfer())
.addSignatories(iouState.participants)

Expand All @@ -238,7 +241,7 @@ class TestContractFlow: ClientStartableFlow {
}


// Multiple Commands not permitted
// Issue needs only one output
results["Issue needs only one output"] = try {
val iouState = IOUState(
amount = 10,
Expand Down Expand Up @@ -271,6 +274,70 @@ class TestContractFlow: ClientStartableFlow {
}
}

// Transfer requires one input
results["Transfer requires one input"] = try {
val iouState = IOUState(
amount = 10,
paid = 3,
lender = myInfo.name,
borrower = otherMember.name,
linearId = UUID.randomUUID(),
participants = listOf(myInfo.ledgerKeys.first(), otherMember.ledgerKeys.first())
)

val txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.name)
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addCommand(IOUContract.Transfer())
.addSignatories(iouState.participants)

@Suppress("DEPRECATION", "UNUSED_VARIABLE")
val signedTransaction = txBuilder.toSignedTransaction()

"Fail"

} catch (e:Exception) {
val exceptionMessage = e.message ?: "No exception message"
if (exceptionMessage.contains("one input state")) {
"Pass" }
else {
"Contract failed but with a different Exception: ${e.message}"
}
}

// Settle requires one input
results["Settle requires one input"] = try {
val iouState = IOUState(
amount = 10,
paid = 3,
lender = myInfo.name,
borrower = otherMember.name,
linearId = UUID.randomUUID(),
participants = listOf(myInfo.ledgerKeys.first(), otherMember.ledgerKeys.first())
)

val txBuilder = ledgerService.createTransactionBuilder()
.setNotary(notary.name)
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(iouState)
.addCommand(IOUContract.Settle())
.addSignatories(iouState.participants)

@Suppress("DEPRECATION", "UNUSED_VARIABLE")
val signedTransaction = txBuilder.toSignedTransaction()

"Fail"

} catch (e:Exception) {
val exceptionMessage = e.message ?: "No exception message"
if (exceptionMessage.contains("one input state")) {
"Pass" }
else {
"Contract failed but with a different Exception: ${e.message}"
}
}



return results.toString()
Expand Down

0 comments on commit 24ac614

Please sign in to comment.