diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java index 176c6f2..57a2eb0 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java @@ -14,17 +14,23 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; import djmil.cordacheckers.cordaclient.dao.Color; +import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.cordaclient.dao.VirtualNode; import djmil.cordacheckers.cordaclient.dao.VirtualNodeList; import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; -import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; -import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalAction; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes; @Service public class CordaClient { @@ -59,7 +65,7 @@ public class CordaClient { * @param holdingIdentity * @return GameProposals list in JSON form */ - public String listGameProposals(HoldingIdentity holdingIdentity) { + public List gameProposalList(HoldingIdentity holdingIdentity) { final RequestBody requestBody = new RequestBody( "list-" + UUID.randomUUID(), @@ -67,21 +73,27 @@ public class CordaClient { new Empty() ); - final String gameProposalsJsonString = cordaFlowExecute( + final GameProposalListRes listFlowResult = cordaFlowExecute( holdingIdentity, - requestBody + requestBody, + GameProposalListRes.class ); - return gameProposalsJsonString; + if (listFlowResult.failureStatus() != null) { + System.out.println("GameProposalCreateFlow failed: " + listFlowResult.failureStatus()); + throw new RuntimeException("GameProsal: CreateFlow execution has failed"); + } + + return listFlowResult.successStatus(); } - public String createGameProposal( + public UUID gameProposalCreate( HoldingIdentity sender, HoldingIdentity receiver, Color receiverColor, String message - ) { - final CreateGameProposal createGameProposal = new CreateGameProposal( + ) throws JsonMappingException, JsonProcessingException { + final GameProposalCreateReq createGameProposal = new GameProposalCreateReq( receiver.x500Name(), receiverColor, message @@ -93,21 +105,28 @@ public class CordaClient { createGameProposal ); - final String createdGameProposalUuid = cordaFlowExecute( + final GameProposalCreateRes createResult = cordaFlowExecute( sender, - requestBody + requestBody, + GameProposalCreateRes.class ); - return createdGameProposalUuid; + if (createResult.failureStatus() != null) { + System.out.println("GameProposalCreateFlow failed: " + createResult.failureStatus()); + throw new RuntimeException("GameProsal: CreateFlow execution has failed"); + } + + return createResult.successStatus(); } - public String rejectGameProposal( + public String gameProposalAction( HoldingIdentity self, - String gameProposalUuid + UUID gameProposalUuid, + GameProposalActionReq.Action action ) { - final GameProposalAction rejectGameProposal = new GameProposalAction( - gameProposalUuid, - GameProposalAction.Action.REJECT + final GameProposalActionReq rejectGameProposal = new GameProposalActionReq( + gameProposalUuid.toString(), + action ); final RequestBody requestBody = new RequestBody( @@ -116,15 +135,21 @@ public class CordaClient { rejectGameProposal ); - final String createdGameProposalUuid = cordaFlowExecute( + final GameProposalActionRes actionResult = cordaFlowExecute( self, - requestBody + requestBody, + GameProposalActionRes.class ); - return createdGameProposalUuid; + if (actionResult.failureStatus() != null) { + System.out.println("GameProposalActionFlow failed: " + actionResult.failureStatus()); + throw new RuntimeException("GameProsal: ActionFlow execution has failed"); + } + + return actionResult.successStatus(); } - private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) { + private T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class flowResultType) { try { final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody); @@ -134,15 +159,9 @@ public class CordaClient { requestBodyJson ); - final String flowExecutionResult = cordaFlowPoll(startedFlow); + final String flowResult = cordaFlowPoll(startedFlow); - // NOTE: - // At this point, real production code, probably should convert data between CordaFlow - // abstarction into ReactApp abstraction. Instead, to limit boring json shuffling, all - // family of Corda.List flows were deliberatly designed to return frontend frendly JSONs. - // At the same time, all other Corda flows, simply return plain text string with - // operation result. - return flowExecutionResult; + return this.jsonMapper.readValue(flowResult, flowResultType); } catch (Exception e) { throw new RuntimeException("Unable to perform "+requestBody.flowClassName() @@ -204,15 +223,16 @@ public class CordaClient { "CordaClient.cordaFlowPoll: empty getBody()" ); - if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) { - System.out.println("Completed "+responseBody.flowResult()); - return responseBody.flowResult(); - } else if (responseBody.flowError() != null) { - return "Flow execution error: " +responseBody.flowError(); + throw new RuntimeException("CordaClient.cordaFlowPoll: flow execution error: " + +responseBody.flowError()); + } + + if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) { + return responseBody.flowResult(); } } - return "CordaClient.cordaFlowPoll: retry limit"; + throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit"); } } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java index 0d354f5..48b6c5a 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java @@ -1,5 +1,7 @@ package djmil.cordacheckers.cordaclient.dao; +import java.util.UUID; + import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JsonDeserialize @@ -8,6 +10,6 @@ public record GameProposal( String recipient, Color recipientColor, String message, - String id) { + UUID id) { } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalAction.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java similarity index 64% rename from backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalAction.java rename to backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java index c1f3650..6dc8c0c 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalAction.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java @@ -1,6 +1,6 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; -public record GameProposalAction(String gameProposalUuid, Action action) { +public record GameProposalActionReq(String gameProposalUuid, Action action) { public enum Action { ACCEPT, REJECT, diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java new file mode 100644 index 0000000..6b1df3b --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java @@ -0,0 +1,5 @@ +package djmil.cordacheckers.cordaclient.dao.flow.arguments; + +public record GameProposalActionRes(String successStatus, String failureStatus) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/CreateGameProposal.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java similarity index 55% rename from backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/CreateGameProposal.java rename to backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java index 42796f2..d9c7da4 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/CreateGameProposal.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java @@ -2,6 +2,6 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; import djmil.cordacheckers.cordaclient.dao.Color; -public record CreateGameProposal(String opponentName, Color opponentColor, String message) { +public record GameProposalCreateReq(String opponentName, Color opponentColor, String message) { } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java new file mode 100644 index 0000000..a0775b2 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java @@ -0,0 +1,10 @@ +package djmil.cordacheckers.cordaclient.dao.flow.arguments; + +import java.util.UUID; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize +public record GameProposalCreateRes(UUID successStatus, String failureStatus) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java new file mode 100644 index 0000000..c8956f0 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java @@ -0,0 +1,9 @@ +package djmil.cordacheckers.cordaclient.dao.flow.arguments; + +import java.util.List; + +import djmil.cordacheckers.cordaclient.dao.GameProposal; + +public record GameProposalListRes(List successStatus, String failureStatus) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java b/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java index 26bb254..cea4c2f 100644 --- a/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java +++ b/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java @@ -1,6 +1,8 @@ package djmil.cordacheckers.gameproposal; import java.net.URI; +import java.util.List; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -9,9 +11,12 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; + import djmil.cordacheckers.cordaclient.CordaClient; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; -import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; import djmil.cordacheckers.cordaclient.dao.Color; import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.user.HoldingIdentityResolver; @@ -36,9 +41,9 @@ public class GameProposalController { public ResponseEntity findAllUnconsumed( @AuthenticationPrincipal User player ) { - String gpList = cordaClient.listGameProposals(player.getHoldingIdentity()); + List gpList = cordaClient.gameProposalList(player.getHoldingIdentity()); - return ResponseEntity.ok(gpList); + return ResponseEntity.ok(gpList.toString()); } // @PostMapping() @@ -50,15 +55,16 @@ public class GameProposalController { @PostMapping() public ResponseEntity createGameProposal( @AuthenticationPrincipal User sender, - @RequestBody CreateGameProposal gpRequest, + @RequestBody GameProposalCreateReq gpRequest, UriComponentsBuilder ucb - ) { + ) throws JsonMappingException, JsonProcessingException { final HoldingIdentity gpSender = sender.getHoldingIdentity(); // TODO: throw execption with custom type final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName()); final Color gpReceiverColor = gpRequest.opponentColor(); - String newGameProposalUuid = cordaClient.createGameProposal( + // TODO handle expectionns here + UUID newGameProposalUuid = cordaClient.gameProposalCreate( gpSender, gpReceiver, gpReceiverColor, diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java index 4548e9f..c04fdac 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java @@ -6,14 +6,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import djmil.cordacheckers.cordaclient.dao.Color; +import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.cordaclient.dao.VirtualNode; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action; import djmil.cordacheckers.user.HoldingIdentityResolver; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; +import java.util.UUID; import javax.naming.InvalidNameException; @@ -34,44 +39,45 @@ public class CordaClientTest { @Test void testGameProposalList() throws JsonProcessingException { - String resp = cordaClient.listGameProposals( - holdingIdentityResolver.getByUsername("alice")); + List gpList = cordaClient.gameProposalList( + holdingIdentityResolver.getByUsername("Alice")); - System.out.println("testListGameProposals "+ resp); + System.out.println("testListGameProposals\n"+ gpList); } @Test - void testGemeProposalCreate() { + void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException { final String gpSender = "alice"; final String gpReceiver = "bob"; final Color gpReceiverColor = Color.WHITE; final String gpMessage = "GameProposal create test"; - final String gpUuid = cordaClient.createGameProposal( + final UUID createdGpUuid = cordaClient.gameProposalCreate( holdingIdentityResolver.getByUsername(gpSender), holdingIdentityResolver.getByUsername(gpReceiver), gpReceiverColor, gpMessage ); - String listResSender = cordaClient.listGameProposals( + List gpListSender = cordaClient.gameProposalList( holdingIdentityResolver.getByUsername(gpSender)); - String listResReceiver = cordaClient.listGameProposals( + assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull(); + + List gpListReceiver = cordaClient.gameProposalList( holdingIdentityResolver.getByUsername(gpReceiver)); - assertThat(listResSender).contains(gpUuid); - assertThat(listResReceiver).contains(gpUuid); + assertThat(findByUuid(gpListReceiver, createdGpUuid)).isNotNull(); } @Test - void testGemeProposalReject() { + void testGemeProposalReject() throws JsonMappingException, JsonProcessingException { final String gpSender = "alice"; final String gpReceiver = "bob"; final Color gpReceiverColor = Color.WHITE; - final String gpMessage = "GameProposal create test"; + final String gpMessage = "GameProposal REJECT test"; - final String gpUuid = cordaClient.createGameProposal( + final UUID gpUuid = cordaClient.gameProposalCreate( holdingIdentityResolver.getByUsername(gpSender), holdingIdentityResolver.getByUsername(gpReceiver), gpReceiverColor, @@ -80,20 +86,37 @@ public class CordaClientTest { System.out.println("Create GP UUID "+ gpUuid); - String listResSender = cordaClient.listGameProposals( - holdingIdentityResolver.getByUsername(gpSender)); + assertThatThrownBy(() -> { + cordaClient.gameProposalAction( + holdingIdentityResolver.getByUsername(gpSender), + gpUuid, + Action.REJECT); + }); - String listResReceiver = cordaClient.listGameProposals( - holdingIdentityResolver.getByUsername(gpReceiver)); - - assertThat(listResSender).contains(gpUuid); - assertThat(listResReceiver).contains(gpUuid); - - final String rejectRes = cordaClient.rejectGameProposal( + final String rejectRes = cordaClient.gameProposalAction( holdingIdentityResolver.getByUsername(gpReceiver), - "1ed70601-c79a-486d-b907-8537f317083a" + gpUuid, + Action.REJECT ); assertThat(rejectRes).isEqualToIgnoringCase("REJECTED"); + + List gpListSender = cordaClient.gameProposalList( + holdingIdentityResolver.getByUsername(gpSender)); + + assertThat(findByUuid(gpListSender, gpUuid)).isNull(); + + List gpListReceiver = cordaClient.gameProposalList( + holdingIdentityResolver.getByUsername(gpReceiver)); + + assertThat(findByUuid(gpListReceiver, gpUuid)).isNull(); + } + + private GameProposal findByUuid(List gpList, UUID uuid) { + for (GameProposal gameProposal : gpList) { + if (gameProposal.id().compareTo(uuid) == 0) + return gameProposal; + }; + return null; } }