From 8703925df41fcf670ab0532d89bc8e20ecc59757 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Mon, 21 Aug 2023 16:25:25 +0100 Subject: [PATCH] feat(Transaction): implement API for getting all Transactions --- .../transaction/TransactionController.java | 6 ++ .../transaction/TransactionRepository.java | 17 ++++ .../transaction/TransactionService.java | 10 +- .../model/ApprovalTransaction.java | 8 +- .../MinimalApprovalTransactionSerializer.java | 19 ++++ ...lSensorChaincodeTransactionSerializer.java | 21 +++++ .../model/SensorChaincodeTransaction.java | 5 + .../transaction/model/Transaction.java | 2 + .../TransactionControllerTest.java | 44 +++++++++ .../TransactionRepositoryTest.java | 92 +++++++++++++++++++ .../transaction/TransactionServiceTest.java | 42 +++++++++ .../model/ApprovalTransactionTest.java | 9 +- 12 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepository.java create mode 100644 src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalApprovalTransactionSerializer.java create mode 100644 src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalSensorChaincodeTransactionSerializer.java create mode 100644 src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepositoryTest.java diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionController.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionController.java index f44505d..ae31ee8 100644 --- a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionController.java +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import uk.ac.ic.doc.blocc.dashboard.transaction.model.ApprovedTempReading; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.Transaction; @RestController @RequestMapping(path = "/api/v1/transaction") @@ -24,4 +25,9 @@ public List getApprovedTransactions(@RequestParam int conta return transactionService.getApprovedTempReadings(containerNum); } + @GetMapping("/all") + public List getTransactions(@RequestParam int containerNum) { + return transactionService.getTransactions(containerNum); + } + } diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepository.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepository.java new file mode 100644 index 0000000..c049881 --- /dev/null +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepository.java @@ -0,0 +1,17 @@ +package uk.ac.ic.doc.blocc.dashboard.transaction; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.ApprovalTransaction; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.CompositeKey; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.Transaction; + +@Repository +public interface TransactionRepository + extends JpaRepository { + + @Query("SELECT tx FROM Transaction tx WHERE tx.key.containerNum = ?1") + List findAllByContainerNum(int containerNum); +} diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionService.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionService.java index 9d159c6..df8d42a 100644 --- a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionService.java +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionService.java @@ -12,12 +12,14 @@ import uk.ac.ic.doc.blocc.dashboard.transaction.model.SensorChaincodeTransaction; import uk.ac.ic.doc.blocc.dashboard.transaction.model.CompositeKey; import uk.ac.ic.doc.blocc.dashboard.fabric.model.TemperatureHumidityReading; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.Transaction; @Service public class TransactionService { private final SensorChaincodeTransactionRepository sensorChaincodeTransactionRepository; private final ApprovalTransactionRepository approvalTransactionRepository; + private final TransactionRepository transactionRepository; private static final Logger logger = LoggerFactory.getLogger(TransactionService.class); @@ -25,9 +27,11 @@ public class TransactionService { @Autowired public TransactionService( SensorChaincodeTransactionRepository sensorChaincodeTransactionRepository, - ApprovalTransactionRepository approvalTransactionRepository) { + ApprovalTransactionRepository approvalTransactionRepository, + TransactionRepository transactionRepository) { this.sensorChaincodeTransactionRepository = sensorChaincodeTransactionRepository; this.approvalTransactionRepository = approvalTransactionRepository; + this.transactionRepository = transactionRepository; } public List getApprovedTempReadings(int containerNum) { @@ -89,4 +93,8 @@ public void addApprovalTransaction(String txId, int containerNum, String approvi approvalTransactionRepository.save(approvalTransaction); } + + public List getTransactions(int containerNum) { + return transactionRepository.findAllByContainerNum(containerNum); + } } diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransaction.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransaction.java index 551783e..fb26376 100644 --- a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransaction.java +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransaction.java @@ -1,5 +1,7 @@ package uk.ac.ic.doc.blocc.dashboard.transaction.model; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import jakarta.persistence.Entity; import jakarta.persistence.ManyToOne; @@ -7,6 +9,8 @@ public class ApprovalTransaction extends Transaction { @ManyToOne + @JsonSerialize(using = MinimalSensorChaincodeTransactionSerializer.class) + @JsonProperty("approvedTransaction") private SensorChaincodeTransaction sensorChaincodeTransaction; public ApprovalTransaction(String txId, int containerNum, String creator, long timestamp, @@ -19,10 +23,6 @@ protected ApprovalTransaction() { } - public SensorChaincodeTransaction getApprovedTransaction() { - return sensorChaincodeTransaction; - } - @Override public String toString() { return "Approval " + super.toString(); diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalApprovalTransactionSerializer.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalApprovalTransactionSerializer.java new file mode 100644 index 0000000..f1c4ade --- /dev/null +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalApprovalTransactionSerializer.java @@ -0,0 +1,19 @@ +package uk.ac.ic.doc.blocc.dashboard.transaction.model; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +public class MinimalApprovalTransactionSerializer extends JsonSerializer { + + @Override + public void serialize(ApprovalTransaction value, JsonGenerator gen, + SerializerProvider serializers) throws IOException { + gen.writeStartObject(); + gen.writeStringField("txId", value.getTxId()); + gen.writeStringField("creator", value.getCreator()); + gen.writeNumberField("createdTimestamp", value.getCreatedTimestamp()); + gen.writeEndObject(); + } +} diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalSensorChaincodeTransactionSerializer.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalSensorChaincodeTransactionSerializer.java new file mode 100644 index 0000000..5e69ede --- /dev/null +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/MinimalSensorChaincodeTransactionSerializer.java @@ -0,0 +1,21 @@ +package uk.ac.ic.doc.blocc.dashboard.transaction.model; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +public class MinimalSensorChaincodeTransactionSerializer extends + JsonSerializer { + + @Override + public void serialize(SensorChaincodeTransaction transaction, JsonGenerator gen, + SerializerProvider serializers) throws IOException { + gen.writeStartObject(); + gen.writeStringField("txId", transaction.getTxId()); + gen.writeStringField("creator", transaction.getCreator()); + gen.writeNumberField("createdTimestamp", transaction.getCreatedTimestamp()); + gen.writeObjectField("reading", transaction.getReading()); + gen.writeEndObject(); + } +} diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/SensorChaincodeTransaction.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/SensorChaincodeTransaction.java index ebb34f9..57f371d 100644 --- a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/SensorChaincodeTransaction.java +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/SensorChaincodeTransaction.java @@ -1,5 +1,7 @@ package uk.ac.ic.doc.blocc.dashboard.transaction.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import jakarta.persistence.Entity; import jakarta.persistence.OneToMany; import java.util.ArrayList; @@ -10,6 +12,7 @@ public class SensorChaincodeTransaction extends Transaction { @OneToMany(mappedBy = "sensorChaincodeTransaction") + @JsonSerialize(contentUsing = MinimalApprovalTransactionSerializer.class) private List approvals = new ArrayList<>(); private TemperatureHumidityReading reading; @@ -37,7 +40,9 @@ public String toString() { return "Sensor Chaincode " + super.toString(); } + @JsonIgnore public int getApprovalCount() { return approvals.size(); } + } diff --git a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/Transaction.java b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/Transaction.java index c1de86c..aaa265d 100644 --- a/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/Transaction.java +++ b/src/main/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/Transaction.java @@ -1,5 +1,6 @@ package uk.ac.ic.doc.blocc.dashboard.transaction.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.Inheritance; @@ -15,6 +16,7 @@ public CompositeKey getKey() { } @EmbeddedId + @JsonIgnore protected CompositeKey key; protected String creator; protected long createdTimestamp; diff --git a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionControllerTest.java b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionControllerTest.java index e8d4f88..205cdee 100644 --- a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionControllerTest.java +++ b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionControllerTest.java @@ -13,7 +13,11 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import uk.ac.ic.doc.blocc.dashboard.fabric.model.TemperatureHumidityReading; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.ApprovalTransaction; import uk.ac.ic.doc.blocc.dashboard.transaction.model.ApprovedTempReading; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.SensorChaincodeTransaction; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.Transaction; @WebMvcTest(TransactionController.class) class TransactionControllerTest { @@ -53,4 +57,44 @@ public void getEmptyListIfApprovedTransactionsNotExist() throws Exception { .andExpect(MockMvcResultMatchers.jsonPath("$.size()").value(0)); } + @Test + public void getAllExistingTransactionsForSpecifiedContainer() throws Exception { + int containerNum = 1; + + // Given + SensorChaincodeTransaction tx1 = new SensorChaincodeTransaction( + "tx123", + 1, + "Container5MSP", + 100L, + new TemperatureHumidityReading(25, 0.3F, 100L)); + + SensorChaincodeTransaction tx2 = new SensorChaincodeTransaction( + "tx456", + 1, + "Container5MSP", + 103L, + new TemperatureHumidityReading(30, 0.2F, 103L)); + + SensorChaincodeTransaction tx3 = new SensorChaincodeTransaction( + "tx1299", + 1, + "Container5MSP", + 100L, + new TemperatureHumidityReading(22, 0.3F, 100L)); + + ApprovalTransaction approval1 = new ApprovalTransaction( + "app1", 1, "Container6MSP", 101L, tx1); + ApprovalTransaction approval2 = new ApprovalTransaction( + "app2", 1, "Container7MSP", 102L, tx1); + + List transactions = List.of(tx1, tx2, tx3, approval1, approval2); + + when(transactionService.getTransactions(containerNum)).thenReturn(transactions); + + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/transaction/all") + .param("containerNum", String.valueOf(containerNum))) + .andExpect(status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.size()").value(5)); + } } \ No newline at end of file diff --git a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepositoryTest.java b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepositoryTest.java new file mode 100644 index 0000000..f9d5ae0 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionRepositoryTest.java @@ -0,0 +1,92 @@ +package uk.ac.ic.doc.blocc.dashboard.transaction; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.PostgreSQLContainer; +import uk.ac.ic.doc.blocc.dashboard.fabric.model.TemperatureHumidityReading; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.ApprovalTransaction; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.SensorChaincodeTransaction; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.Transaction; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class TransactionRepositoryTest { + + static final PostgreSQLContainer postgres = + new PostgreSQLContainer<>("postgres:latest"); + + static { + postgres.start(); + } + + @DynamicPropertySource + static void properties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", postgres::getJdbcUrl); + registry.add("spring.datasource.username", postgres::getUsername); + registry.add("spring.datasource.password", postgres::getPassword); + } + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private TransactionRepository repository; + + @Test + void findAllByContainerNum() { + // Given + SensorChaincodeTransaction tx1 = new SensorChaincodeTransaction( + "tx123", + 1, + "Container5MSP", + 100L, + new TemperatureHumidityReading(25, 0.3F, 100L)); + + SensorChaincodeTransaction tx2 = new SensorChaincodeTransaction( + "tx456", + 3, + "Container5MSP", + 103L, + new TemperatureHumidityReading(30, 0.2F, 103L)); + + SensorChaincodeTransaction tx3 = new SensorChaincodeTransaction( + "tx1299", + 1, + "Container5MSP", + 100L, + new TemperatureHumidityReading(22, 0.3F, 100L)); + + ApprovalTransaction approval1 = new ApprovalTransaction( + "app1", 1, "Container6MSP", 101L, tx1); + ApprovalTransaction approval2 = new ApprovalTransaction( + "app2", 1, "Container7MSP", 102L, tx1); + ApprovalTransaction approval3 = new ApprovalTransaction( + "app3", 3, "Container9MSP", 102L, tx2); + ApprovalTransaction approval4 = new ApprovalTransaction( + "app4", 1, "Container7MSP", 102L, tx3); + + entityManager.persist(tx1); + entityManager.persist(tx2); + entityManager.persist(tx3); + entityManager.persist(approval1); + entityManager.persist(approval2); + entityManager.persist(approval3); + entityManager.persist(approval4); + entityManager.flush(); + + // When + List found = repository.findAllByContainerNum(1); + + // Then + assertThat(found).hasSize(5).contains(tx1, tx3, approval1, approval2, approval4); + assertThat(found).doesNotContain(tx2, approval3); + } +} \ No newline at end of file diff --git a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionServiceTest.java b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionServiceTest.java index ebf4a41..d29202e 100644 --- a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionServiceTest.java +++ b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/TransactionServiceTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -20,6 +21,7 @@ import uk.ac.ic.doc.blocc.dashboard.transaction.model.ApprovedTempReading; import uk.ac.ic.doc.blocc.dashboard.transaction.model.CompositeKey; import uk.ac.ic.doc.blocc.dashboard.transaction.model.SensorChaincodeTransaction; +import uk.ac.ic.doc.blocc.dashboard.transaction.model.Transaction; public class TransactionServiceTest { @@ -31,6 +33,8 @@ public class TransactionServiceTest { @Mock private ApprovalTransactionRepository approvalTransactionRepository; + @Mock + private TransactionRepository transactionRepository; @BeforeEach public void setUp() { @@ -185,5 +189,43 @@ public void throwsExceptionWhenSameApprovalTransactionApprovesSensorTransactionA exception.getMessage()); } + @Test + public void getsTransactions() { + int containerNum = 1; + + SensorChaincodeTransaction tx1 = new SensorChaincodeTransaction( + "tx123", + 1, "creator", 100L, + new TemperatureHumidityReading(25, 0.3F, 100L)); + SensorChaincodeTransaction tx2 = new SensorChaincodeTransaction( + "tx1299", + 1, "creator", 100L, + new TemperatureHumidityReading(22, 0.3F, 100L)); + ApprovalTransaction approval1 = new ApprovalTransaction( + "tx222", + 1, "approvingMSP", 101L, tx1); + ApprovalTransaction approval2 = new ApprovalTransaction( + "tx223", + 1, "approvingMSP", 101L, tx2); + + List mockData = Arrays.asList( + tx1, + tx2, + approval1, + approval2 + ); + + when(transactionRepository.findAllByContainerNum(containerNum)).thenReturn(mockData); + + List result = transactionService.getTransactions(containerNum); + + assertEquals(4, result.size()); + + // Assertions + assertTrue(result.contains(tx1)); + assertTrue(result.contains(tx2)); + assertTrue(result.contains(approval1)); + assertTrue(result.contains(approval2)); + } } \ No newline at end of file diff --git a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransactionTest.java b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransactionTest.java index 030d19a..02c5e49 100644 --- a/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransactionTest.java +++ b/src/test/java/uk/ac/ic/doc/blocc/dashboard/transaction/model/ApprovalTransactionTest.java @@ -10,7 +10,6 @@ public class ApprovalTransactionTest { private ApprovalTransaction approvalTransaction; - private SensorChaincodeTransaction mockSensorTransaction; private final String txId = "tx12345"; private final int containerNum = 1; private final String creator = "creatorName"; @@ -18,17 +17,11 @@ public class ApprovalTransactionTest { @BeforeEach public void setUp() { - mockSensorTransaction = mock(SensorChaincodeTransaction.class); + SensorChaincodeTransaction mockSensorTransaction = mock(SensorChaincodeTransaction.class); approvalTransaction = new ApprovalTransaction(txId, containerNum, creator, createdTimestamp, mockSensorTransaction); } - @Test - public void getsApprovedTransaction() { - SensorChaincodeTransaction retrievedTransaction = approvalTransaction.getApprovedTransaction(); - assertEquals(mockSensorTransaction, retrievedTransaction); - } - @Test public void convertsToString() { String expectedString =