diff --git a/persistence/scripts/mysql/local_development.sql b/persistence/scripts/mysql/local_development.sql index 860c41b..52480e5 100644 --- a/persistence/scripts/mysql/local_development.sql +++ b/persistence/scripts/mysql/local_development.sql @@ -36,7 +36,7 @@ version int default 1 CREATE TABLE proofs ( id INT NOT NULL AUTO_INCREMENT, - name VARCHAR(512) NOT NULL, + theorem_name VARCHAR(512) NOT NULL, branch VARCHAR(512) NOT NULL, theorem INT NOT NULL, FOREIGN KEY fk_theorem (theorem) REFERENCES theorems (id) ON DELETE NO ACTION ON UPDATE NO ACTION, diff --git a/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/ProofController.java b/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/ProofController.java index d902eb8..373b79d 100644 --- a/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/ProofController.java +++ b/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/ProofController.java @@ -2,13 +2,18 @@ package edu.msudenver.tsp.persistence.controller; import edu.msudenver.tsp.persistence.dto.ProofDto; import edu.msudenver.tsp.persistence.repository.ProofRepository; +import edu.msudenver.tsp.utilities.PersistenceUtilities; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StopWatch; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; +import javax.validation.groups.Default; import java.util.List; import java.util.Optional; @@ -97,4 +102,140 @@ public class ProofController { LOG.info("Returning list of proofs with branch {}", branch); return new ResponseEntity<>(listOfProofs, HttpStatus.OK); } + + @GetMapping("/{theorem_name}") + public @ResponseBody + ResponseEntity> getAllProofsByTheoremName(@PathVariable("theorem_name") final String theoremName) { + LOG.info("Received request to query for proofs of the theorem {}", theoremName); + if (theoremName == null) { + LOG.error("ERROR: theorem name was null"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + LOG.debug("Querying for proofs of the theorem {}", theoremName); + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + final List listOfProofs = proofRepository.findByTheoremName(theoremName); + + stopWatch.stop(); + + LOG.debug("Received response from server: query took " + stopWatch.getTotalTimeMillis() + "ms to complete"); + LOG.info("Returning list of all proofs with size " + listOfProofs.size()); + + if (listOfProofs.isEmpty()) { + LOG.warn("No proofs were found of the theorem {}", theoremName); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + LOG.info("Returning list of proofs for the theorem {}", theoremName); + return new ResponseEntity<>(listOfProofs, HttpStatus.OK); + } + + @PostMapping("/") + @Validated({ProofDto.Insert.class, Default.class}) + public @ResponseBody ResponseEntity insertProof( + @Valid @RequestBody final ProofDto proofDto, + final BindingResult bindingResult) { + LOG.info("Received request to insert a new proof"); + if (bindingResult.hasErrors()) { + LOG.error("Binding result is unprocessable"); + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + + if (proofDto == null) { + LOG.error("Passed entity is null"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + LOG.debug("Saving new proof"); + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + final ProofDto savedProof = proofRepository.save(proofDto); + + stopWatch.stop(); + LOG.debug("Received response from server: query took {}ms to complete", stopWatch.getTotalTimeMillis()); + + LOG.info("Returning the newly created proof with id {}", savedProof.getId()); + return new ResponseEntity<>(savedProof, HttpStatus.CREATED); + } + + @PatchMapping("/{id}") + public @ResponseBody ResponseEntity updateProof( + @PathVariable("id") final Integer id, + @RequestBody final ProofDto proofDto, final BindingResult bindingResult) { + + LOG.info("Received request to update a proof"); + if (bindingResult.hasErrors()) { + LOG.error("Binding result is unprocessable"); + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + + if (proofDto == null) { + LOG.error("Passed entity is null"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + if (id == null) { + LOG.error("Proof ID must be specified"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + LOG.debug("Checking for existence of proof with id " + id); + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + final Optional existingProof = proofRepository.findById(id); + + stopWatch.stop(); + + LOG.debug("Received response from server: query took {}ms to complete", stopWatch.getTotalTimeMillis()); + + if (!existingProof.isPresent()) { + LOG.error("No proof associated with id {}", id); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + PersistenceUtilities.copyNonNullProperties(proofDto, existingProof.get()); + existingProof.get().setVersion(existingProof.get().getVersion()+ 1); + + LOG.info("Updating proof with id {}", id); + LOG.debug("Querying for proof with ID {}", id); + + stopWatch.start(); + + final ProofDto updatedProof = proofRepository.save(existingProof.get()); + + stopWatch.stop(); + + LOG.debug("Received response from server: query took {}ms to complete", stopWatch.getTotalTimeMillis()); + + return new ResponseEntity<>(updatedProof, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public @ResponseBody ResponseEntity deleteProofById(@PathVariable("id") final Integer id) { + LOG.info("Received request to delete proof with id {}", id); + if (id == null) { + LOG.error("Specified id is null"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + LOG.debug("Deleting proof with id {}", id); + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + proofRepository.deleteById(id); + + stopWatch.stop(); + + LOG.debug("Received response from server: query took {}ms to complete", stopWatch.getTotalTimeMillis()); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } } diff --git a/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/TheoremController.java b/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/TheoremController.java index 68ce608..2bebf44 100644 --- a/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/TheoremController.java +++ b/persistence/src/main/java/edu/msudenver/tsp/persistence/controller/TheoremController.java @@ -9,9 +9,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StopWatch; import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import javax.validation.groups.Default; import java.util.List; import java.util.Optional; @@ -162,6 +164,7 @@ public class TheoremController { } @PostMapping("/") + @Validated({TheoremDto.Insert.class, Default.class}) public @ResponseBody ResponseEntity insertTheorem( @Valid @RequestBody final TheoremDto theoremDto, final BindingResult bindingResult) { @@ -195,7 +198,7 @@ public class TheoremController { @PathVariable("id") final Integer id, @RequestBody final TheoremDto theoremDto, final BindingResult bindingResult) { - LOG.info("Received request to update an account"); + LOG.info("Received request to update a theorem"); if (bindingResult.hasErrors()) { LOG.error("Binding result is unprocessable"); return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); diff --git a/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/ProofDto.java b/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/ProofDto.java index c84623a..fdb6e76 100644 --- a/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/ProofDto.java +++ b/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/ProofDto.java @@ -20,7 +20,8 @@ import java.util.List; public class ProofDto extends BaseDto implements Serializable { @NotBlank(groups = Insert.class) @Size(min = 1, max = 512, message = "The name must be at least 1 character and at most 512 characters") - private String name; + @Column(name = "theorem_name") + private String theoremName; @NotBlank(groups = Insert.class) @Size(min = 1, max = 512, message = "The branch must be at least 1 character and at most 512 characters") private String branch; @@ -29,6 +30,15 @@ public class ProofDto extends BaseDto implements Serializable { @Temporal(TemporalType.DATE) @Column(name = "date_created") private Date dateCreated; @Temporal(TemporalType.DATE) @Column(name = "last_updated") private Date lastUpdated; + @JsonProperty("theorem_name") + public String getTheoremName() { + return theoremName; + } + + @JsonProperty("theorem_name") + public void setTheoremName(final String theoremName) { + this.theoremName = theoremName; + } @JsonProperty("referenced_definitions") public List getReferencedDefinitions() { diff --git a/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/TheoremDto.java b/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/TheoremDto.java index 6c48e32..db10620 100644 --- a/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/TheoremDto.java +++ b/persistence/src/main/java/edu/msudenver/tsp/persistence/dto/TheoremDto.java @@ -20,12 +20,12 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) public class TheoremDto extends BaseDto implements Serializable { - @NotBlank @Size(min = 1, max = 512, message = "theorem name must be between 1 and 512 characters") private String name; - @NotNull @Column(name = "theorem_type") private TheoremType theoremType; - @NotNull(message = "a branch of mathematics that this theorem is associated with must be specified") private String branch; + @NotBlank(groups = Insert.class) @Size(min = 1, max = 512, message = "theorem name must be between 1 and 512 characters") private String name; + @NotNull(groups = Insert.class) @Column(name = "theorem_type") private TheoremType theoremType; + @NotNull(groups = Insert.class, message = "a branch of mathematics that this theorem is associated with must be specified") private String branch; @Type(type = "json") @Column(name = "referenced_definitions", columnDefinition = "jsonb") private List referencedDefinitions; @Type(type = "json") @Column(name = "referenced_theorems", columnDefinition = "jsonb") private List referencedTheorems; - @NotNull @Column(name = "proven_status") private boolean provenStatus; + @NotNull(groups = Insert.class) @Column(name = "proven_status") private boolean provenStatus; @JsonProperty("theorem_type") public TheoremType getTheoremType() { @@ -68,4 +68,7 @@ public class TheoremDto extends BaseDto implements Serializable { } private static final long serialVersionUID = 1545568391140364425L; + + public interface Insert {} + } diff --git a/persistence/src/main/java/edu/msudenver/tsp/persistence/repository/ProofRepository.java b/persistence/src/main/java/edu/msudenver/tsp/persistence/repository/ProofRepository.java index 4f1a9bd..9aa16e9 100644 --- a/persistence/src/main/java/edu/msudenver/tsp/persistence/repository/ProofRepository.java +++ b/persistence/src/main/java/edu/msudenver/tsp/persistence/repository/ProofRepository.java @@ -9,5 +9,5 @@ public interface ProofRepository extends JpaRepository { List findByBranch(String branch); - List findByName(String name); + List findByTheoremName(String name); } diff --git a/persistence/src/test/java/edu/msudenver/tsp/persistence/controller/ProofControllerTest.java b/persistence/src/test/java/edu/msudenver/tsp/persistence/controller/ProofControllerTest.java index e6535d4..5536d06 100644 --- a/persistence/src/test/java/edu/msudenver/tsp/persistence/controller/ProofControllerTest.java +++ b/persistence/src/test/java/edu/msudenver/tsp/persistence/controller/ProofControllerTest.java @@ -26,7 +26,7 @@ public class ProofControllerTest { @Mock private BindingResult bindingResult; @Test - public void testGetAllTheorems() { + public void testGetAllProofs() { final ProofDto proofDto = createProof(); final List listOfProofs = new ArrayList<>(); listOfProofs.add(proofDto); @@ -84,6 +84,46 @@ public class ProofControllerTest { assertEquals(HttpStatus.NOT_FOUND, responseEntity.getStatusCode()); } + @Test + public void testGetAllProofsByTheoremName() { + final ProofDto proofDto = createProof(); + final List listOfProofs = new ArrayList<>(); + listOfProofs.add(proofDto); + listOfProofs.add(proofDto); + + when(proofRepository.findByTheoremName(anyString())).thenReturn(listOfProofs); + + final ResponseEntity> responseEntity = proofController.getAllProofsByTheoremName("test"); + + assertNotNull(responseEntity); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertTrue(responseEntity.hasBody()); + assertNotNull(responseEntity.getBody()); + + responseEntity.getBody().forEach(proof -> assertEquals(proofDto, proof)); + } + + @Test + public void testGetAllProfsByTheoremName_nullTheoremName() { + final ResponseEntity> responseEntity = proofController.getAllProofsByTheoremName(null); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + + @Test + public void testGetAllProofsByTheoremName_noProofsFound() { + when(proofRepository.findByTheoremName(anyString())).thenReturn(Collections.emptyList()); + + final ResponseEntity> responseEntity = proofController.getAllProofsByTheoremName("test nonexistent branch"); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.NOT_FOUND, responseEntity.getStatusCode()); + } + @Test public void testGetProofById() { final ProofDto proofDto = createProof(); @@ -121,6 +161,132 @@ public class ProofControllerTest { verify(proofRepository).findById(anyInt()); } + @Test + public void testInsertProof() { + final ProofDto proofDto = createProof(); + when(proofRepository.save(any(ProofDto.class))).thenReturn(proofDto); + + final ResponseEntity responseEntity = proofController.insertProof(proofDto, bindingResult); + + assertNotNull(responseEntity); + assertTrue(responseEntity.hasBody()); + assertNotNull(responseEntity.getBody()); + assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode()); + assertEquals(proofDto, responseEntity.getBody()); + verify(proofRepository).save(any(ProofDto.class)); + } + + @Test + public void testInsertProof_proofDtoIsNull() { + final ResponseEntity responseEntity = proofController.insertProof(null, bindingResult); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + + @Test + public void testInsertProof_bindingResultHasErrors() { + final ProofDto proofDto = createProof(); + when(bindingResult.hasErrors()).thenReturn(true); + + final ResponseEntity responseEntity = proofController.insertProof(proofDto, bindingResult); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + + @Test + public void testUpdateProof() { + final ProofDto existingProof = createProof(); + existingProof.setId(1); + existingProof.setVersion(1); + final ProofDto proofUpdate = new ProofDto(); + proofUpdate.setTheoremName("Test Update"); + final ProofDto updatedProof = existingProof; + updatedProof.setTheoremName("Test Update"); + when(proofRepository.findById(anyInt())).thenReturn(Optional.of(existingProof)); + when(proofRepository.save(any(ProofDto.class))).thenReturn(updatedProof); + + final ResponseEntity responseEntity = proofController.updateProof(1, proofUpdate, bindingResult); + + assertNotNull(responseEntity); + assertTrue(responseEntity.hasBody()); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertEquals(updatedProof, responseEntity.getBody()); + verify(proofRepository).findById(anyInt()); + verify(proofRepository).save(any(ProofDto.class)); + } + + @Test + public void testUpdateProof_bindingResultHasErrors() { + when(bindingResult.hasErrors()).thenReturn(true); + + final ResponseEntity responseEntity = proofController.updateProof(1, createProof(), bindingResult); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + + @Test + public void testUpdateProof_proofDtoIsNull() { + final ResponseEntity responseEntity = proofController.updateProof(1, null, bindingResult); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + + @Test + public void testUpdateProof_idIsNull() { + final ResponseEntity responseEntity = proofController.updateProof(null, createProof(), bindingResult); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + + @Test + public void testUpdateProof_theoremDoesNotExist() { + when(proofRepository.findById(anyInt())).thenReturn(Optional.empty()); + + final ResponseEntity responseEntity = proofController.updateProof(1, createProof(), bindingResult); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + verify(proofRepository, times(0)).save(any(ProofDto.class)); + } + + @Test + public void testDeleteProofById() { + doNothing().when(proofRepository).deleteById(anyInt()); + + final ResponseEntity responseEntity = proofController.deleteProofById(1); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.NO_CONTENT, responseEntity.getStatusCode()); + verify(proofRepository).deleteById(anyInt()); + } + + @Test + public void testDeleteProofById_idIsNull() { + final ResponseEntity responseEntity = proofController.deleteProofById(null); + + assertNotNull(responseEntity); + assertFalse(responseEntity.hasBody()); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + verifyZeroInteractions(proofRepository); + } + private ProofDto createProof() { final List referencedTheoremsList = new ArrayList<>(); referencedTheoremsList.add("test theorem 1"); @@ -131,7 +297,7 @@ public class ProofControllerTest { referencedDefinitionsList.add("test definition 2"); final ProofDto proofDto = new ProofDto(); - proofDto.setName("Test proof"); + proofDto.setTheoremName("Test proof"); proofDto.setBranch("Test branch"); proofDto.setDateCreated(new Date()); proofDto.setReferencedTheorems(referencedTheoremsList);