From f34ee622c91f5d08c6f5ff758952c8fd7f920c00 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 29 Jan 2025 12:56:40 -0300 Subject: [PATCH] hotfix: enhance analytics endpoints with pagination support and update email templates --- pom.xml | 39 ++- .../analytics/AnalyticsController.java | 52 ++-- .../analytics/AnalyticsHelperController.java | 277 +++++++++--------- .../BiochemistryAnalyticsController.java | 8 +- .../CoagulationAnalyticsController.java | 26 +- .../HematologyAnalyticsController.java | 11 +- .../repositories/UserRepository.java | 36 ++- .../analytics/AbstractAnalyticService.java | 23 +- .../analytics/AnalyticHelperService.java | 155 +++++----- .../BiochemistryAnalyticService.java | 70 ++--- .../analytics/CoagulationAnalyticService.java | 70 ++--- .../analytics/HematologyAnalyticService.java | 71 +++-- .../analytics/IAnalyticHelperService.java | 52 ++-- .../services/email/EmailService.java | 28 +- .../services/users/UserService.java | 7 +- .../components/ControlRulesValidators.java | 39 ++- .../utils/constants/EmailTemplate.java | 10 + .../resources/application-local.properties | 2 +- src/main/resources/application.properties | 7 +- .../BiochemistryAnalyticControllerTest.java | 225 +++++++------- .../CoagulationAnalyticControllerTest.java | 199 +++++++------ .../HematologyAnalyticControllerTest.java | 200 ++++++------- .../controllers/UsersControllerTest.java | 66 +++-- .../services/AnalyticHelperServiceTests.java | 16 +- .../services/UserServiceTest.java | 195 ++++++------ 25 files changed, 978 insertions(+), 906 deletions(-) diff --git a/pom.xml b/pom.xml index 9338de8..cd5efb1 100644 --- a/pom.xml +++ b/pom.xml @@ -45,9 +45,10 @@ spring-boot-starter-hateoas - org.flywaydb - flyway-core - 11.2.0 + org.mariadb.jdbc + mariadb-java-client + 3.5.1 + runtime org.flywaydb @@ -62,11 +63,6 @@ org.springframework.boot spring-boot-starter-validation - - org.mariadb.jdbc - mariadb-java-client - 3.5.1 - org.projectlombok lombok @@ -142,4 +138,31 @@ + + + + + + + + + com.google.errorprone + error_prone_annotations + 2.36.0 + + + + + org.checkerframework + checker-qual + 3.37.0 + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsController.java b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsController.java index 01c9299..2f812c2 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsController.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsController.java @@ -28,38 +28,38 @@ @RestController() public abstract class AnalyticsController extends AnalyticsHelperController { - public AnalyticsController(AnalyticHelperService analyticHelperService) { - super(analyticHelperService); - } + protected AnalyticsController(AnalyticHelperService analyticHelperService) { + super(analyticHelperService); + } - @GetMapping() - public abstract ResponseEntity>> getAllAnalytics( - @PageableDefault(sort = "date", - direction = Sort.Direction.DESC) @ParameterObject Pageable pageable); + @GetMapping() + public abstract ResponseEntity>> getAllAnalytics( + @PageableDefault(sort = "date", + direction = Sort.Direction.DESC) @ParameterObject Pageable pageable); - @GetMapping("/date-range") - public abstract ResponseEntity> getAnalyticsDateBetween( - @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate, @PageableDefault(sort = "date", - direction = Sort.Direction.DESC) @ParameterObject Pageable pageable); + @GetMapping("/date-range") + public abstract ResponseEntity> getAnalyticsDateBetween( + @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, @PageableDefault(sort = "date", + direction = Sort.Direction.DESC) @ParameterObject Pageable pageable); - @GetMapping("/level-date-range") - public abstract ResponseEntity> getAllAnalyticsByLevelDateRange( - @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable); + @GetMapping("/level-date-range") + public abstract ResponseEntity> getAllAnalyticsByLevelDateRange( + @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable); - @GetMapping("/name-and-level-date-range") - public abstract ResponseEntity> getAllAnalyticsByNameAndLevelDateRange( - @RequestParam String name, @RequestParam String level, - @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate); + @GetMapping("/name-and-level-date-range") + public abstract ResponseEntity> getAllAnalyticsByNameAndLevelDateRange( + @RequestParam String name, @RequestParam String level, + @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable); - @GetMapping("/mean-standard-deviation") - public abstract ResponseEntity getMeanAndStandardDeviation( - @RequestParam String name, @RequestParam String level, - @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate); + @GetMapping("/mean-standard-deviation") + public abstract ResponseEntity getMeanAndStandardDeviation( + @RequestParam String name, @RequestParam String level, + @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable); } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsHelperController.java b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsHelperController.java index b3d3afe..c469597 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsHelperController.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/AnalyticsHelperController.java @@ -25,145 +25,140 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; public class AnalyticsHelperController { - private final AnalyticHelperService analyticHelperService; - - public AnalyticsHelperController(AnalyticHelperService analyticHelperService) { - this.analyticHelperService = analyticHelperService; - } - - @GetMapping("/{id}") - public ResponseEntity getAnalyticsById(@PathVariable Long id) { - return ResponseEntity.ok(analyticHelperService.findOneById(id)); - } - - @DeleteMapping("/{id}") - @Transactional - public ResponseEntity deleteAnalyticsResultById(@PathVariable Long id) { - analyticHelperService.deleteAnalyticsById(id); - return ResponseEntity.noContent().build(); - } - - @PostMapping - @Transactional - public ResponseEntity> postAnalytics( - @Valid @RequestBody List<@Valid AnalyticsDTO> values) { - analyticHelperService.saveNewAnalyticsRecords(values); - return ResponseEntity.status(201).build(); - } - - @PatchMapping() - public ResponseEntity updateAnalyticsMean( - @Valid @RequestBody UpdateAnalyticsMeanDTO updateAnalyticsMeanDTO) { - analyticHelperService.updateAnalyticsMeanByNameAndLevelAndLevelLot( - updateAnalyticsMeanDTO.name(), updateAnalyticsMeanDTO.level(), - updateAnalyticsMeanDTO.levelLot(), updateAnalyticsMeanDTO.mean()); - return ResponseEntity.noContent().build(); - } - - @GetMapping("/grouped-by-level") - public ResponseEntity> getGroupedByLevel(@RequestParam String name, - @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { - List groupedData = - analyticHelperService.findAnalyticsWithGroupedResults(name, startDate, endDate); - return ResponseEntity.ok(groupedData); - } - - @GetMapping("/grouped-by-level/mean-deviation") - public ResponseEntity> getMeanAndDeviationGrouped( - @RequestParam String name, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { - List groupedData = analyticHelperService - .calculateGroupedMeanAndStandardDeviation(name, startDate, endDate); - return ResponseEntity.ok(groupedData); - } - - public ResponseEntity>> getAllAnalyticsWithLinks( - List names, Pageable pageable) { - Page resultsList = - analyticHelperService.findAnalyticsPagedByNameIn(names, pageable); - - var entityModels = resultsList.getContent().stream() - .map(record -> createEntityModel(record, pageable)).collect(Collectors.toList()); - - var result = addPaginationLinks(CollectionModel.of(entityModels), resultsList, pageable); - return ResponseEntity.ok(result); - } - - public ResponseEntity>> getAnalyticsByDateBetweenWithLinks( - List names, LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { - - Page analyticsRecordPaged = analyticHelperService - .findAnalyticsByNameInAndDateBetweenWithLinks(names, startDate, endDate, pageable); - - if (analyticsRecordPaged == null) { - return ResponseEntity.noContent().build(); - } - - var entityModels = analyticsRecordPaged.getContent().stream() - .map(record -> createEntityModel(record, pageable)).collect(Collectors.toList()); - - var collectionModel = CollectionModel.of(entityModels); - var result = addPaginationLinks(collectionModel, analyticsRecordPaged, pageable); - - return ResponseEntity.ok(result); - } - - EntityModel createEntityModel(AnalyticsDTO record, Pageable pageable) { - return EntityModel.of(record, Link.of(ServletUriComponentsBuilder.fromCurrentContextPath() - .path("/backend-api") - .path(linkTo(methodOn(getClass()).getAnalyticsById(record.id())).toUri().getPath()) - .toUriString()).withSelfRel()); - } - - private CollectionModel> addPaginationLinks( - CollectionModel> collectionModel, - Page page, Pageable pageable) { - - UriComponentsBuilder uriBuilder = - ServletUriComponentsBuilder.fromCurrentRequest().replacePath("/backend-api" - + - ServletUriComponentsBuilder.fromCurrentRequest().build().getPath()); - - // Clear any existing collection-level links to prevent duplication - // collectionModel.removeLinks(); - - // Link for the first page - collectionModel.add(Link.of(uriBuilder.replaceQueryParam("page", 0) - .replaceQueryParam("size", pageable.getPageSize()).toUriString() - .replace("%2520", "%20")).withRel("first")); - - // Link for the previous page if it exists - if (page.hasPrevious()) { - collectionModel - .add(Link.of(uriBuilder.replaceQueryParam("page", pageable.getPageNumber() - 1) - .replaceQueryParam("size", pageable.getPageSize()).toUriString() - .replace("%2520", "%20")).withRel("prev")); - } - - // Link for the next page if it exists - if (page.hasNext()) { - collectionModel - .add(Link.of(uriBuilder.replaceQueryParam("page", pageable.getPageNumber() + 1) - .replaceQueryParam("size", pageable.getPageSize()).toUriString() - .replace("%2520", "%20")).withRel("next")); - } - - // Link for the last page - collectionModel.add(Link.of(uriBuilder.replaceQueryParam("page", page.getTotalPages() - 1) - .replaceQueryParam("size", pageable.getPageSize()).toUriString() - .replace("%2520", "%20")).withRel("last")); - - // Add metadata about the current page - collectionModel.add(Link.of(uriBuilder.replaceQueryParam("page", pageable.getPageNumber()) - .replaceQueryParam("size", pageable.getPageSize()).toUriString() - .replace("%2520", "%20")).withRel("current-page")); - - // collectionModel.add(Link.of(String.valueOf(page.getTotalPages())).withSelfRel()); - collectionModel.add(Link.of(String.valueOf(page.getTotalPages()), "totalPages")); - collectionModel.add(Link.of(String.valueOf(page.getNumber()), "currentPage")); - - - return collectionModel; - } + private final AnalyticHelperService analyticHelperService; + + public AnalyticsHelperController(AnalyticHelperService analyticHelperService) { + this.analyticHelperService = analyticHelperService; + } + + @GetMapping("/{id}") + public ResponseEntity getAnalyticsById(@PathVariable Long id) { + return ResponseEntity.ok(analyticHelperService.findOneById(id)); + } + + @DeleteMapping("/{id}") + @Transactional + public ResponseEntity deleteAnalyticsResultById(@PathVariable Long id) { + analyticHelperService.deleteAnalyticsById(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping + @Transactional + public ResponseEntity> postAnalytics( + @Valid @RequestBody List<@Valid AnalyticsDTO> values) { + analyticHelperService.saveNewAnalyticsRecords(values); + return ResponseEntity.status(201).build(); + } + + @PatchMapping() + public ResponseEntity updateAnalyticsMean( + @Valid @RequestBody UpdateAnalyticsMeanDTO updateAnalyticsMeanDTO) { + analyticHelperService.updateAnalyticsMeanByNameAndLevelAndLevelLot( + updateAnalyticsMeanDTO.name(), updateAnalyticsMeanDTO.level(), + updateAnalyticsMeanDTO.levelLot(), updateAnalyticsMeanDTO.mean()); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/grouped-by-level") + public ResponseEntity> getGroupedByLevel( + @RequestParam String name, @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, Pageable pageable) { + List groupedData = analyticHelperService + .findAnalyticsWithGroupedResults(name, startDate, endDate, pageable); + return ResponseEntity.ok(groupedData); + } + + @GetMapping("/grouped-by-level/mean-deviation") + public ResponseEntity> getMeanAndDeviationGrouped( + @RequestParam String name, @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, Pageable pageable) { + List groupedData = analyticHelperService + .calculateGroupedMeanAndStandardDeviation(name, startDate, endDate, pageable); + return ResponseEntity.ok(groupedData); + } + + public ResponseEntity>> getAllAnalyticsWithLinks( + List names, Pageable pageable) { + Page resultsList = + analyticHelperService.findAnalyticsPagedByNameIn(names, pageable); + + var entityModels = resultsList.getContent().stream() + .map(record -> createEntityModel(record, pageable)).collect(Collectors.toList()); + + var result = addPaginationLinks(CollectionModel.of(entityModels), resultsList, pageable); + return ResponseEntity.ok(result); + } + + public ResponseEntity>> getAnalyticsByDateBetweenWithLinks( + List names, LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + + Page analyticsRecordPaged = analyticHelperService + .findAnalyticsByNameInAndDateBetweenWithLinks(names, startDate, endDate, pageable); + + if (analyticsRecordPaged == null) { + return ResponseEntity.noContent().build(); + } + + var entityModels = analyticsRecordPaged.getContent().stream() + .map(record -> createEntityModel(record, pageable)).collect(Collectors.toList()); + + var collectionModel = CollectionModel.of(entityModels); + var result = addPaginationLinks(collectionModel, analyticsRecordPaged, pageable); + + return ResponseEntity.ok(result); + } + + EntityModel createEntityModel(AnalyticsDTO record, Pageable pageable) { + return EntityModel.of(record, Link.of(ServletUriComponentsBuilder.fromCurrentContextPath() + .path("/backend-api") + .path(linkTo(methodOn(getClass()).getAnalyticsById(record.id())).toUri().getPath()) + .toUriString()).withSelfRel()); + } + + private CollectionModel> addPaginationLinks( + CollectionModel> collectionModel, Page page, + Pageable pageable) { + + UriComponentsBuilder uriBuilder = + ServletUriComponentsBuilder.fromCurrentRequest().replacePath("/backend-api" + + ServletUriComponentsBuilder.fromCurrentRequest().build().getPath()); + + // Link for the first page + collectionModel.add(Link.of(uriBuilder.replaceQueryParam("page", 0) + .replaceQueryParam("size", pageable.getPageSize()).toUriString() + .replace("%2520", "%20")).withRel("first")); + + // Link for the previous page if it exists + if (page.hasPrevious()) { + collectionModel + .add(Link.of(uriBuilder.replaceQueryParam("page", pageable.getPageNumber() - 1) + .replaceQueryParam("size", pageable.getPageSize()).toUriString() + .replace("%2520", "%20")).withRel("prev")); + } + + // Link for the next page if it exists + if (page.hasNext()) { + collectionModel + .add(Link.of(uriBuilder.replaceQueryParam("page", pageable.getPageNumber() + 1) + .replaceQueryParam("size", pageable.getPageSize()).toUriString() + .replace("%2520", "%20")).withRel("next")); + } + + // Link for the last page + collectionModel.add(Link.of(uriBuilder.replaceQueryParam("page", page.getTotalPages() - 1) + .replaceQueryParam("size", pageable.getPageSize()).toUriString() + .replace("%2520", "%20")).withRel("last")); + + // Add metadata about the current page + collectionModel.add(Link.of(uriBuilder.replaceQueryParam("page", pageable.getPageNumber()) + .replaceQueryParam("size", pageable.getPageSize()).toUriString() + .replace("%2520", "%20")).withRel("current-page")); + + collectionModel.add(Link.of(String.valueOf(page.getTotalPages()), "totalPages")); + collectionModel.add(Link.of(String.valueOf(page.getNumber()), "currentPage")); + + + return collectionModel; + } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/BiochemistryAnalyticsController.java b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/BiochemistryAnalyticsController.java index 721b021..2373a6c 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/BiochemistryAnalyticsController.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/BiochemistryAnalyticsController.java @@ -70,9 +70,9 @@ public ResponseEntity> getAllAnalyticsByLevelDateRange( public ResponseEntity> getAllAnalyticsByNameAndLevelDateRange( @RequestParam String name, @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable) { return ResponseEntity.ok(biochemistryAnalyticsService - .findAnalyticsByNameAndLevelAndDate(name, level, startDate, endDate)); + .findAnalyticsByNameAndLevelAndDate(name, level, startDate, endDate, pageable)); } @@ -81,8 +81,8 @@ public ResponseEntity> getAllAnalyticsByNameAndLevelDateRange public ResponseEntity getMeanAndStandardDeviation( @RequestParam String name, @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable) { return ResponseEntity.ok(biochemistryAnalyticsService - .calculateMeanAndStandardDeviation(name, level, startDate, endDate)); + .calculateMeanAndStandardDeviation(name, level, startDate, endDate, pageable)); } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/CoagulationAnalyticsController.java b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/CoagulationAnalyticsController.java index a6fa107..f3b8af6 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/CoagulationAnalyticsController.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/CoagulationAnalyticsController.java @@ -55,16 +55,6 @@ public ResponseEntity> getAnalyticsDateBetween( .findAnalyticsByNameInAndDateBetween(names, startDate, endDate, pageable)); } - @Override - @GetMapping("/name-and-level-date-range") - public ResponseEntity> getAllAnalyticsByNameAndLevelDateRange( - @RequestParam String name, @RequestParam String level, - @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { - return ResponseEntity.ok(coagulationAnalyticsService - .findAnalyticsByNameAndLevelAndDate(name, level, startDate, endDate)); - } - @Override @GetMapping("/level-date-range") public ResponseEntity> getAllAnalyticsByLevelDateRange( @@ -75,14 +65,24 @@ public ResponseEntity> getAllAnalyticsByLevelDateRange( } + @Override + @GetMapping("/name-and-level-date-range") + public ResponseEntity> getAllAnalyticsByNameAndLevelDateRange( + @RequestParam String name, @RequestParam String level, + @RequestParam("startDate") LocalDateTime startDate, + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable) { + return ResponseEntity.ok(coagulationAnalyticsService + .findAnalyticsByNameAndLevelAndDate(name, level, startDate, endDate, pageable)); + } + + @Override @GetMapping("/mean-standard-deviation") public ResponseEntity getMeanAndStandardDeviation( @RequestParam String name, @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable) { return ResponseEntity.ok(coagulationAnalyticsService.calculateMeanAndStandardDeviation(name, - level, startDate, endDate)); - + level, startDate, endDate, pageable)); } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/HematologyAnalyticsController.java b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/HematologyAnalyticsController.java index 60ccd78..4e8c823 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/HematologyAnalyticsController.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/controllers/analytics/HematologyAnalyticsController.java @@ -68,18 +68,21 @@ public ResponseEntity> getAllAnalyticsByLevelDateRange( public ResponseEntity> getAllAnalyticsByNameAndLevelDateRange( @RequestParam String name, @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable) { return ResponseEntity.ok(hematologyAnalyticsService.findAnalyticsByNameAndLevelAndDate(name, - level, startDate, endDate)); + level, startDate, endDate, pageable)); } + @Override @GetMapping("/mean-standard-deviation") public ResponseEntity getMeanAndStandardDeviation( @RequestParam String name, @RequestParam String level, @RequestParam("startDate") LocalDateTime startDate, - @RequestParam("endDate") LocalDateTime endDate) { + @RequestParam("endDate") LocalDateTime endDate, @ParameterObject Pageable pageable) { return ResponseEntity.ok(hematologyAnalyticsService.calculateMeanAndStandardDeviation(name, - level, startDate, endDate)); + level, startDate, endDate, pageable)); } + + } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/repositories/UserRepository.java b/src/main/java/leonardo/labutilities/qualitylabpro/repositories/UserRepository.java index f02d241..ac83e0a 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/repositories/UserRepository.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/repositories/UserRepository.java @@ -10,28 +10,32 @@ @Repository public interface UserRepository extends JpaRepository { - UserDetails getReferenceByUsername(String username); - User findByUsername(String username); - User findByEmail(String email); + UserDetails getReferenceByUsername(String username); - User findByUsernameOrEmail(String username, String email); + User findByUsername(String username); - UserDetails getReferenceByUsernameAndEmail(String userName, String Email); + User findByEmail(String email); - boolean existsByUsernameAndEmail(String userName, String Email); + User findByUsernameOrEmail(String username, String email); - @Transactional - @Modifying - @Query("UPDATE users u SET u.password = ?2 WHERE u.username = ?1") - void setPasswordWhereByUsername(String username, String newPassword); + boolean existsByUsernameOrEmail(String username, String email); - @Transactional - @Modifying - @Query("UPDATE users u SET u.password = ?2 WHERE u.email = ?1") - void setPasswordWhereByEmail(String email, String newPassword); + UserDetails getReferenceByUsernameAndEmail(String userName, String Email); + boolean existsByUsernameAndEmail(String userName, String Email); - boolean existsByUsername(String name); + @Transactional + @Modifying + @Query("UPDATE users u SET u.password = ?2 WHERE u.username = ?1") + void setPasswordWhereByUsername(String username, String newPassword); - boolean existsByEmail(String email); + @Transactional + @Modifying + @Query("UPDATE users u SET u.password = ?2 WHERE u.email = ?1") + void setPasswordWhereByEmail(String email, String newPassword); + + + boolean existsByUsername(String name); + + boolean existsByEmail(String email); } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AbstractAnalyticService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AbstractAnalyticService.java index 1ebe68b..ad357c1 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AbstractAnalyticService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AbstractAnalyticService.java @@ -11,19 +11,18 @@ public abstract class AbstractAnalyticService extends AnalyticHelperService { - public AbstractAnalyticService(AnalyticsRepository analyticsRepository, EmailService emailService, - ControlRulesValidators controlRulesValidators) { - super(analyticsRepository, emailService, controlRulesValidators); - } + public AbstractAnalyticService(AnalyticsRepository analyticsRepository, + EmailService emailService, ControlRulesValidators controlRulesValidators) { + super(analyticsRepository, emailService, controlRulesValidators); + } - @Override - public abstract List findAnalyticsByNameAndLevel(Pageable pageable, - String name, String level); + @Override + public abstract List findAnalyticsByNameAndLevel(Pageable pageable, String name, + String level); - @Override - public abstract List - findAnalyticsByNameAndLevelAndDate(String name, - String level, LocalDateTime dateStart, LocalDateTime dateEnd); + @Override + public abstract List findAnalyticsByNameAndLevelAndDate(String name, String level, + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable); - public abstract String convertLevel(String level); + public abstract String convertLevel(String level); } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AnalyticHelperService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AnalyticHelperService.java index 86425f6..ff3b1fe 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AnalyticHelperService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/AnalyticHelperService.java @@ -27,39 +27,61 @@ public abstract class AnalyticHelperService implements IAnalyticHelperService { private final AnalyticsRepository analyticsRepository; private final EmailService emailService; - private final Pageable pageable = PageRequest.of(0, 100); private final ControlRulesValidators controlRulesValidators; - public AnalyticHelperService(AnalyticsRepository analyticsRepository, - EmailService emailService, ControlRulesValidators controlRulesValidators) { + protected AnalyticHelperService(AnalyticsRepository analyticsRepository, + EmailService emailService, ControlRulesValidators controlRulesValidators) { this.analyticsRepository = analyticsRepository; this.emailService = emailService; this.controlRulesValidators = controlRulesValidators; } + // VALIDATION METHODS + private void validateResultsNotEmpty(List results, String message) { + if (results == null || results.isEmpty()) { + throw new CustomGlobalErrorHandling.ResourceNotFoundException(message); + } + } + public boolean isAnalyticsNonExistent(AnalyticsDTO values) { return !analyticsRepository.existsByDateAndLevelAndName(values.date(), values.level(), values.name()); } - private boolean isRuleBroken(AnalyticsDTO record) { - String rules = record.rules(); - return ("+3s".equals(rules) || "-3s".equals(rules) || "-2s".equals(rules) || "+2s".equals(rules)) - && !BLACK_LIST.contains(record.name()); + private boolean isRuleBroken(AnalyticsDTO analyticsDTO) { + String rules = analyticsDTO.rules(); + return ("+3s".equals(rules) || "-3s".equals(rules) || "-2s".equals(rules) + || "+2s".equals(rules)); } - public List validateAnalyticsNameExists(List results) { - if (results.isEmpty()) { - throw new CustomGlobalErrorHandling.ResourceNotFoundException("Results not found."); + public void ensureNameExists(String name) { + if (!analyticsRepository.existsByName(name.toUpperCase())) { + throw new CustomGlobalErrorHandling.ResourceNotFoundException( + "AnalyticsDTO by name not found"); } - return results; } + // STATISTICS METHODS + private MeanAndStdDeviationDTO computeStatistics(List values) { + double sum = values.stream().mapToDouble(Double::doubleValue).sum(); + int size = values.size(); + double mean = sum / size; + double variance = values.stream().mapToDouble(value -> Math.pow(value - mean, 2)).average() + .orElse(0.0); + return new MeanAndStdDeviationDTO(mean, Math.sqrt(variance)); + } + + private List extractRecordValues(List records) { + return records.stream().map(AnalyticsDTO::value).toList(); + } + + + // BUSINESS LOGIC METHODS public List findAnalyticsWithGroupedResults(String name, - LocalDateTime startDate, LocalDateTime endDate) { + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { List analytics = - findGroupedAnalyticsByLevel(name, startDate, endDate); + findGroupedAnalyticsByLevel(name, startDate, endDate, pageable); Map statsByLevel = analytics.stream().collect(Collectors.toMap(GroupedValuesByLevelDTO::level, group -> computeStatistics(extractRecordValues(group.values())))); @@ -73,35 +95,20 @@ public List findAnalyticsWithGroupedResults(String nam @Override public List findGroupedAnalyticsByLevel(String name, - LocalDateTime startDate, LocalDateTime endDate) { + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { List records = analyticsRepository .findByNameAndDateBetweenGroupByLevel(name, startDate, endDate, pageable).stream() .map(AnalyticMapper::toRecord).toList(); - validateResultsNotEmpty(records, "No analytics found for the given parameters"); + validateResultsNotEmpty(records, + "No analytics found for the given name and date between parameters"); return records.stream().collect(Collectors.groupingBy(AnalyticsDTO::level)).entrySet() - .stream().map(entry -> new GroupedValuesByLevelDTO(entry.getKey(), entry.getValue())) + .stream() + .map(entry -> new GroupedValuesByLevelDTO(entry.getKey(), entry.getValue())) .toList(); } - private MeanAndStdDeviationDTO computeStatistics(List values) { - double sum = values.stream().mapToDouble(Double::doubleValue).sum(); - int size = values.size(); - double mean = sum / size; - double variance = values.stream().mapToDouble(value -> Math.pow(value - mean, 2)).average() - .orElse(0.0); - return new MeanAndStdDeviationDTO(mean, Math.sqrt(variance)); - } - - private List extractRecordValues(List records) { - return records.stream().map(AnalyticsDTO::value).toList(); - } - private void validateResultsNotEmpty(List results, String message) { - if (results == null || results.isEmpty()) { - throw new CustomGlobalErrorHandling.ResourceNotFoundException(message); - } - } @Override public List returnMeanAndStandardDeviationForGroups( @@ -114,16 +121,18 @@ public List returnMeanAndStandardDeviationForGroups @Override public Page findAnalyticsByNameInAndDateBetweenWithLinks(List names, - LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { return analyticsRepository.findByNameInAndDateBetweenPaged(names, dateStart, dateEnd, pageable); } @Override public List findFilteredGroupedAnalytics( - List records) { - return records.stream().filter(this::isGroupedRecordValid) - .map(record -> new GroupedValuesByLevelDTO(record.level(), record.values())).toList(); + List groupedValuesByLevelDTO) { + return groupedValuesByLevelDTO.stream().filter(this::isGroupedRecordValid) + .map(groupedValue -> new GroupedValuesByLevelDTO(groupedValue.level(), + groupedValue.values())) + .toList(); } @Override @@ -133,15 +142,15 @@ public void updateAnalyticsMeanByNameAndLevelAndLevelLot(String name, String lev } @Override - public boolean isGroupedRecordValid(GroupedValuesByLevelDTO record) { - return record.values().stream() - .allMatch(genericValuesRecord -> !Objects.equals(genericValuesRecord.rules(), "+3s") - && !Objects.equals(genericValuesRecord.rules(), "-3s")); + public boolean isGroupedRecordValid(GroupedValuesByLevelDTO groupedValuesByLevelDTO) { + return groupedValuesByLevelDTO.values().stream() + .allMatch(groupedValue -> !Objects.equals(groupedValue.rules(), "+3s") + && !Objects.equals(groupedValue.rules(), "-3s")); } @Override - public boolean isRecordValid(AnalyticsDTO record) { - String rules = record.rules(); + public boolean isRecordValid(AnalyticsDTO analyticsDTO) { + String rules = analyticsDTO.rules(); return (!Objects.equals(rules, "+3s") || !Objects.equals(rules, "-3s")); } @@ -155,7 +164,7 @@ public AnalyticsDTO findOneById(Long id) { @Override public void saveNewAnalyticsRecords(List valuesOfLevelsList) { var newAnalytics = valuesOfLevelsList.stream().filter(this::isAnalyticsNonExistent) - .map(AnalyticMapper::toEntity).collect(Collectors.toList()); + .map(AnalyticMapper::toEntity).toList(); if (newAnalytics.isEmpty()) { log.warn("No new analytics records to save."); @@ -170,9 +179,13 @@ public void saveNewAnalyticsRecords(List valuesOfLevelsList) { if (notPassedList.isEmpty()) { return; } + + var filteredList = notPassedList.stream() + .filter(notPassed -> !BLACK_LIST.contains(notPassed.name())).toList(); + try { - var content = controlRulesValidators.validateRules(notPassedList); - emailService.sendFailedAnalyticsNotification(notPassedList, content); + var content = controlRulesValidators.validateRules(filteredList); + emailService.sendFailedAnalyticsNotification(filteredList, content); } catch (Exception e) { log.error("Error sending identifier notification: {}", e.getMessage()); } @@ -196,17 +209,16 @@ public List findAnalyticsByNameWithPagination(Pageable pageable, S @Override public Page findAnalyticsByNameInByLevelBaseMethod(List names, - String level, LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { - Page results = analyticsRepository - .findByNameInAndLevelAndDateBetween(names, level, startDate, endDate, pageable); + String level, LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + Page results = analyticsRepository.findByNameInAndLevelAndDateBetween(names, + level, startDate, endDate, pageable); validateResultsNotEmpty(results.getContent(), - "No analytics found for the given parameters"); + "No analytics found for the given parameters with pagination"); return results; } @Override - public List findAnalyticsByDate(LocalDateTime dateStart, - LocalDateTime dateEnd) { + public List findAnalyticsByDate(LocalDateTime dateStart, LocalDateTime dateEnd) { List results = analyticsRepository.findByDateBetween(dateStart, dateEnd) .stream().map(AnalyticMapper::toRecord).toList(); validateResultsNotEmpty(results, "No analytics found for the given date range"); @@ -215,14 +227,10 @@ public List findAnalyticsByDate(LocalDateTime dateStart, @Override public Page findAnalyticsByNameInAndDateBetween(List names, - LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { return analyticsRepository.findByNameInAndDateBetween(names, dateStart, dateEnd, pageable); } - @Override - public abstract List findAnalyticsByNameAndLevel(Pageable pageable, - String name, String level); - @Override public void deleteAnalyticsById(Long id) { if (!analyticsRepository.existsById(id)) { @@ -233,21 +241,22 @@ public void deleteAnalyticsById(Long id) { } public MeanAndStdDeviationDTO calculateMeanAndStandardDeviation(String name, String level, - LocalDateTime dateStart, LocalDateTime dateEnd) { + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { List values = - findAnalyticsByNameAndLevelAndDate(name, level, dateStart, dateEnd).stream() - .filter(this::isRecordValid).toList(); - return computeStatistics(extractRecordValues(values)); + findAnalyticsByNameAndLevelAndDate(name, level, dateStart, dateEnd, pageable) + .stream().filter(this::isRecordValid).toList(); + return computeStatistics(extractRecordValues(values)); } - public List calculateGroupedMeanAndStandardDeviation( - String name, LocalDateTime startDate, LocalDateTime endDate) { + public List calculateGroupedMeanAndStandardDeviation(String name, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { List records = analyticsRepository .findByNameAndDateBetweenGroupByLevel(name, startDate, endDate, pageable).stream() .map(AnalyticMapper::toRecord).toList(); - var values = records.stream().collect(Collectors.groupingBy(AnalyticsDTO::level)) - .entrySet().stream() - .map(entry -> new GroupedValuesByLevelDTO(entry.getKey(), entry.getValue())).toList(); + var values = records.stream().collect(Collectors.groupingBy(AnalyticsDTO::level)).entrySet() + .stream() + .map(entry -> new GroupedValuesByLevelDTO(entry.getKey(), entry.getValue())) + .toList(); return returnMeanAndStandardDeviationForGroups(values); } @@ -262,7 +271,7 @@ public Page findAnalyticsPagedByNameIn(List names, Pageabl } public List findAnalyticsByNameAndLevelWithPagination(Pageable pageable, - String name, String level) { + String name, String level) { List analyticsList = analyticsRepository.findByNameAndLevel(pageable, name.toUpperCase(), level).stream() .map(AnalyticMapper::toRecord).toList(); @@ -271,19 +280,15 @@ public List findAnalyticsByNameAndLevelWithPagination(Pageable pag } public List findAnalyticsByNameLevelAndDate(String name, String level, - LocalDateTime dateStart, LocalDateTime dateEnd) { + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { ensureNameExists(name); List results = analyticsRepository .findByNameAndLevelAndDateBetween(name, level, dateStart, dateEnd, pageable) .stream().map(AnalyticMapper::toRecord).toList(); - validateResultsNotEmpty(results, "No analytics found for the given parameters"); + validateResultsNotEmpty(results, + "No analytics found for the given name, level, dateStart, dateEnd -> parameters"); return results; } - public void ensureNameExists(String name) { - if (!analyticsRepository.existsByName(name.toUpperCase())) { - throw new CustomGlobalErrorHandling.ResourceNotFoundException( - "AnalyticsDTO by name not found"); - } - } + } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/BiochemistryAnalyticService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/BiochemistryAnalyticService.java index 2ef839d..6cc6672 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/BiochemistryAnalyticService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/BiochemistryAnalyticService.java @@ -15,39 +15,39 @@ @Service public class BiochemistryAnalyticService extends AbstractAnalyticService { - public BiochemistryAnalyticService(AnalyticsRepository analyticsRepository, EmailService emailService, - ControlRulesValidators controlRulesValidators) { - super(analyticsRepository, emailService, controlRulesValidators); - } - - public Page findAnalyticsByNameInByLevel(List names, String level, - LocalDateTime startDate, LocalDateTime endDate, - Pageable pageable) { - return this.findAnalyticsByNameInByLevelBaseMethod(names, this.convertLevel(level), - startDate, endDate, pageable); - } - - @Override - public List findAnalyticsByNameAndLevel(Pageable pageable, String name, - String level) { - this.ensureNameExists(name); - return this.findAnalyticsByNameAndLevelWithPagination(pageable, name, - this.convertLevel(level)); - } - - @Override - public List findAnalyticsByNameAndLevelAndDate(String name, String level, - LocalDateTime dateStart, LocalDateTime dateEnd) { - return findAnalyticsByNameLevelAndDate(name, convertLevel(level), dateStart, dateEnd); - } - - @Override - public String convertLevel(String inputLevel) { - return switch (inputLevel) { - case "1" -> "PCCC1"; - case "2" -> "PCCC2"; - default -> throw new CustomGlobalErrorHandling.ResourceNotFoundException( - "Level not found."); - }; - } + public BiochemistryAnalyticService(AnalyticsRepository analyticsRepository, + EmailService emailService, ControlRulesValidators controlRulesValidators) { + super(analyticsRepository, emailService, controlRulesValidators); + } + + public Page findAnalyticsByNameInByLevel(List names, String level, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + return this.findAnalyticsByNameInByLevelBaseMethod(names, this.convertLevel(level), + startDate, endDate, pageable); + } + + @Override + public List findAnalyticsByNameAndLevel(Pageable pageable, String name, + String level) { + this.ensureNameExists(name); + return this.findAnalyticsByNameAndLevelWithPagination(pageable, name, + this.convertLevel(level)); + } + + @Override + public List findAnalyticsByNameAndLevelAndDate(String name, String level, + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { + return findAnalyticsByNameLevelAndDate(name, convertLevel(level), dateStart, dateEnd, + pageable); + } + + @Override + public String convertLevel(String inputLevel) { + return switch (inputLevel) { + case "1" -> "PCCC1"; + case "2" -> "PCCC2"; + default -> throw new CustomGlobalErrorHandling.ResourceNotFoundException( + "Level not found."); + }; + } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/CoagulationAnalyticService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/CoagulationAnalyticService.java index e3f444d..003111c 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/CoagulationAnalyticService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/CoagulationAnalyticService.java @@ -15,39 +15,39 @@ @Service public class CoagulationAnalyticService extends AbstractAnalyticService { - public CoagulationAnalyticService(AnalyticsRepository analyticsRepository, EmailService emailService, ControlRulesValidators controlRulesValidators) { - super(analyticsRepository, emailService, controlRulesValidators); - } - - public Page findAnalyticsByNameInByLevel(List names, String level, - LocalDateTime startDate, LocalDateTime endDate, - Pageable pageable) { - return this.findAnalyticsByNameInByLevelBaseMethod(names, this.convertLevel(level), - startDate, endDate, pageable); - } - - @Override - public List findAnalyticsByNameAndLevel(Pageable pageable, String name, - String level) { - this.ensureNameExists(name); - return this.findAnalyticsByNameAndLevelWithPagination(pageable, name, - this.convertLevel(level)); - } - - @Override - public List findAnalyticsByNameAndLevelAndDate(String name, String level, - LocalDateTime dateStart, LocalDateTime dateEnd) { - return this.findAnalyticsByNameLevelAndDate(name.toUpperCase(), this.convertLevel(level), - dateStart, dateEnd); - } - - @Override - public String convertLevel(String inputLevel) { - return switch (inputLevel) { - case "1" -> "Normal C. Assayed"; - case "2" -> "Low Abn C. Assayed"; - default -> throw new CustomGlobalErrorHandling.ResourceNotFoundException( - "Level not found."); - }; - } + public CoagulationAnalyticService(AnalyticsRepository analyticsRepository, + EmailService emailService, ControlRulesValidators controlRulesValidators) { + super(analyticsRepository, emailService, controlRulesValidators); + } + + public Page findAnalyticsByNameInByLevel(List names, String level, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + return this.findAnalyticsByNameInByLevelBaseMethod(names, this.convertLevel(level), + startDate, endDate, pageable); + } + + @Override + public List findAnalyticsByNameAndLevel(Pageable pageable, String name, + String level) { + this.ensureNameExists(name); + return this.findAnalyticsByNameAndLevelWithPagination(pageable, name, + this.convertLevel(level)); + } + + @Override + public List findAnalyticsByNameAndLevelAndDate(String name, String level, + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { + return findAnalyticsByNameLevelAndDate(name, convertLevel(level), dateStart, dateEnd, + pageable); + } + + @Override + public String convertLevel(String inputLevel) { + return switch (inputLevel) { + case "1" -> "Normal C. Assayed"; + case "2" -> "Low Abn C. Assayed"; + default -> throw new CustomGlobalErrorHandling.ResourceNotFoundException( + "Level not found."); + }; + } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/HematologyAnalyticService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/HematologyAnalyticService.java index 1f06ada..93c9662 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/HematologyAnalyticService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/HematologyAnalyticService.java @@ -15,40 +15,39 @@ @Service public class HematologyAnalyticService extends AbstractAnalyticService { - public HematologyAnalyticService(AnalyticsRepository analyticsRepository, EmailService emailService, ControlRulesValidators controlRulesValidators) { - super(analyticsRepository, emailService, controlRulesValidators); - } - - public Page findAnalyticsByNameInByLevel(List names, String level, - LocalDateTime startDate, LocalDateTime endDate, - Pageable pageable) { - return this.findAnalyticsByNameInByLevelBaseMethod(names, this.convertLevel(level), - startDate, endDate, pageable); - } - - @Override - public List findAnalyticsByNameAndLevel(Pageable pageable, String name, - String level) { - ensureNameExists(name); - return findAnalyticsByNameAndLevelWithPagination(pageable, name, convertLevel(level)); - } - - @Override - public List findAnalyticsByNameAndLevelAndDate(String name, - String level, LocalDateTime dateStart, - LocalDateTime dateEnd) { - ensureNameExists(name); - return findAnalyticsByNameLevelAndDate(name, convertLevel(level), dateStart, dateEnd); - } - - @Override - public String convertLevel(String inputLevel) { - return switch (inputLevel) { - case "1" -> "low"; - case "2" -> "normal"; - case "3" -> "high"; - default -> throw new CustomGlobalErrorHandling.ResourceNotFoundException( - "Level not found."); - }; - } + public HematologyAnalyticService(AnalyticsRepository analyticsRepository, + EmailService emailService, ControlRulesValidators controlRulesValidators) { + super(analyticsRepository, emailService, controlRulesValidators); + } + + public Page findAnalyticsByNameInByLevel(List names, String level, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + return this.findAnalyticsByNameInByLevelBaseMethod(names, this.convertLevel(level), + startDate, endDate, pageable); + } + + @Override + public List findAnalyticsByNameAndLevel(Pageable pageable, String name, + String level) { + ensureNameExists(name); + return findAnalyticsByNameAndLevelWithPagination(pageable, name, convertLevel(level)); + } + + @Override + public List findAnalyticsByNameAndLevelAndDate(String name, String level, + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable) { + return findAnalyticsByNameLevelAndDate(name, convertLevel(level), dateStart, dateEnd, + pageable); + } + + @Override + public String convertLevel(String inputLevel) { + return switch (inputLevel) { + case "1" -> "low"; + case "2" -> "normal"; + case "3" -> "high"; + default -> throw new CustomGlobalErrorHandling.ResourceNotFoundException( + "Level not found."); + }; + } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/IAnalyticHelperService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/IAnalyticHelperService.java index 58a8cf9..96a6735 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/IAnalyticHelperService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/analytics/IAnalyticHelperService.java @@ -11,47 +11,45 @@ public interface IAnalyticHelperService { - List findGroupedAnalyticsByLevel(String name, LocalDateTime startDate, - LocalDateTime endDate); + List findGroupedAnalyticsByLevel(String name, LocalDateTime startDate, + LocalDateTime endDate, Pageable pageable); - List returnMeanAndStandardDeviationForGroups( - List records); + List returnMeanAndStandardDeviationForGroups( + List records); - Page findAnalyticsByNameInAndDateBetweenWithLinks(List names, LocalDateTime dateStart, - LocalDateTime dateEnd, Pageable pageable); + Page findAnalyticsByNameInAndDateBetweenWithLinks(List names, + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable); - List findFilteredGroupedAnalytics(List records); + List findFilteredGroupedAnalytics( + List records); - void updateAnalyticsMeanByNameAndLevelAndLevelLot(String name, String level, String levelLot, - double mean); + void updateAnalyticsMeanByNameAndLevelAndLevelLot(String name, String level, String levelLot, + double mean); - boolean isGroupedRecordValid(GroupedValuesByLevelDTO record); + boolean isGroupedRecordValid(GroupedValuesByLevelDTO groupedValuesByLevelDTO); - boolean isRecordValid(AnalyticsDTO record); + boolean isRecordValid(AnalyticsDTO analyticsDTO); - AnalyticsDTO findOneById(Long id); + AnalyticsDTO findOneById(Long id); - void saveNewAnalyticsRecords(List valuesOfLevelsList); + void saveNewAnalyticsRecords(List valuesOfLevelsList); - Page findAnalytics(Pageable pageable); + Page findAnalytics(Pageable pageable); - List findAnalyticsByNameWithPagination(Pageable pageable, String name); + List findAnalyticsByNameWithPagination(Pageable pageable, String name); - Page findAnalyticsByNameInByLevelBaseMethod(List names, String level, - LocalDateTime startDate, LocalDateTime endDate, - Pageable pageable); + Page findAnalyticsByNameInByLevelBaseMethod(List names, String level, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable); - List findAnalyticsByDate(LocalDateTime dateStart, LocalDateTime dateEnd); + List findAnalyticsByDate(LocalDateTime dateStart, LocalDateTime dateEnd); - Page findAnalyticsByNameInAndDateBetween(List names, - LocalDateTime startDate, LocalDateTime endDate, - Pageable pageable); + Page findAnalyticsByNameInAndDateBetween(List names, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable); - List findAnalyticsByNameAndLevel(Pageable pageable, String name, - String level); + List findAnalyticsByNameAndLevel(Pageable pageable, String name, String level); - List findAnalyticsByNameAndLevelAndDate(String name, String level, - LocalDateTime dateStart, LocalDateTime dateEnd); + List findAnalyticsByNameAndLevelAndDate(String name, String level, + LocalDateTime dateStart, LocalDateTime dateEnd, Pageable pageable); - void deleteAnalyticsById(Long id); + void deleteAnalyticsById(Long id); } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/email/EmailService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/email/EmailService.java index 24920ad..743564b 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/email/EmailService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/email/EmailService.java @@ -14,6 +14,7 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -26,6 +27,7 @@ @RequiredArgsConstructor @Slf4j +@EnableAsync @Service public class EmailService { private final JavaMailSender javaMailSender; @@ -50,7 +52,6 @@ private void init() { } } - @Async public void sendPlainTextEmail(EmailDTO email) { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(emailFrom); @@ -87,7 +88,8 @@ public void sendHtmlEmail(EmailDTO emailDTO) { helper.setText(buildEmailBody(emailDTO.body()), true); javaMailSender.send(mimeMessage); - log.info("HTML identifier sent successfully to {} recipients", internetAddresses.length); + log.info("HTML identifier sent successfully to {} recipients", + internetAddresses.length); } catch (MessagingException e) { log.error("Failed to send HTML identifier: {}", e.getMessage(), e); @@ -131,15 +133,17 @@ public void sendFailedAnalyticsNotification(List failedRecords, } } - public String generateAnalyticsFailedEmailBody(List notPassedList, String otherValidations) { - String formattedList = notPassedList.stream().map(record -> String.format( - "%s%s%s%s%s%s%s", - record.name(), record.level(), record.value().toString(), record.mean().toString(), - record.rules(), record.description(), - record.date().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))) + public String generateAnalyticsFailedEmailBody(List notPassedList, + String otherValidations) { + String formattedList = notPassedList.stream() + .map(analytics -> String.format(TABLE_ROW, analytics.name(), analytics.level(), + analytics.value().toString(), analytics.mean().toString(), + analytics.rules(), analytics.description(), + analytics.date().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))) .collect(Collectors.joining("\n")); return String.format(HTML_TEMPLATE, - ANALYTICS_WARNING_HEADER + String.format(TABLE_STYLE, formattedList) + LAST_ANALYTICS_PARAGRAPH + "\n" + otherValidations); + ANALYTICS_WARNING_HEADER + String.format(TABLE_STYLE, formattedList) + + LAST_ANALYTICS_PARAGRAPH + "\n" + otherValidations); } public void notifyUserLogin(String username, String email, LocalDateTime date) { @@ -162,11 +166,11 @@ public void notifyUserUpdate(String username, String email, LocalDateTime date) sendUserActionEmail("Account Update", username, email, date); } - private void sendUserActionEmail(String actionType, String username, String email, + public void sendUserActionEmail(String actionType, String username, String email, LocalDateTime date) { String subject = String.format("User %s - %s", username, actionType); String content = createUserActionEmailContent(actionType, username, email, date); - sendPlainTextEmail(new EmailDTO(email, subject, content)); + this.sendPlainTextEmail(new EmailDTO(email, subject, content)); } private String createUserActionEmailContent(String actionType, String username, String email, @@ -178,6 +182,6 @@ private String createUserActionEmailContent(String actionType, String username, } private String buildEmailBody(String email) { - return String.format("\n\n%s\n\nBest regards,\nLabGraph Team", email); + return String.format("%n%n%s%n%nBest regards,%nLabGraph Team", email); } } diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/services/users/UserService.java b/src/main/java/leonardo/labutilities/qualitylabpro/services/users/UserService.java index 0cde912..a08e26e 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/services/users/UserService.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/services/users/UserService.java @@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -68,9 +67,10 @@ public User signUp(String username, String email, String password) { var user = new User(username, BCryptEncoderComponent.encrypt(password), email, UserRoles.USER); - if (userRepository.existsByEmail(email)) { + if (userRepository.existsByUsernameOrEmail(email, email)) { throw new CustomGlobalErrorHandling.UserAlreadyExistException(); } + try { emailService.notifyUserSignup(user.getUsername(), user.getEmail(), LocalDateTime.now()); } catch (Exception e) { @@ -82,7 +82,8 @@ public User signUp(String username, String email, String password) { public TokenJwtDTO signIn(String identifier, String password) { - final var credential = userRepository.findByUsernameOrEmail(identifier, identifier).getUsername(); + final var credential = + userRepository.findByUsernameOrEmail(identifier, identifier).getUsername(); final var authToken = new UsernamePasswordAuthenticationToken(credential, password); final var auth = authenticationManager.authenticate(authToken); diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/utils/components/ControlRulesValidators.java b/src/main/java/leonardo/labutilities/qualitylabpro/utils/components/ControlRulesValidators.java index 2a5acdf..dc04001 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/utils/components/ControlRulesValidators.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/utils/components/ControlRulesValidators.java @@ -30,16 +30,16 @@ public ControlRulesValidators(AnalyticsRepository analyticsRepository) { """; - public String validateRules(List records) { + public String validateRules(List analytics) { StringBuilder errors = new StringBuilder(); errors.append("
"); // Track already reported violations Set reportedViolations = new HashSet<>(); - for (AnalyticsDTO record : records) { + for (AnalyticsDTO analytic : analytics) { // Create unique key for test/level combination - String violationKey = record.name() + "-" + record.level(); + String violationKey = analytic.name() + "-" + analytic.level(); // Skip if we already reported this violation if (reportedViolations.contains(violationKey)) { @@ -47,23 +47,31 @@ public String validateRules(List records) { } var analyticsRecords = - analyticsRepository.findLast10ByNameAndLevel(record.name(), record.level()); + analyticsRepository.findLast10ByNameAndLevel(analytic.name(), analytic.level()); var mean = analyticsRecords.getFirst().mean(); var stdDev = analyticsRecords.getFirst().sd(); var values = analyticsRecords.stream().map(AnalyticsDTO::value).toList(); + + if (rule1_3s(values, mean, stdDev)) { + errors.append(String.format(ERROR_MESSAGE_TEMPLATE, "1-3s", analytic.name(), + analytic.level(), "One observation exceeds mean ±3 SD", + "Random Error. Reject run and investigate for potential systematic errors.")); + reportedViolations.add(violationKey); + } + if (rule4_1s(values, mean, stdDev)) { - errors.append(String.format(ERROR_MESSAGE_TEMPLATE, "4-1s", record.name(), - record.level(), - "Four consecutive observations exceed 1 SD on the same side of the mean.", - "Investigate possible trends or deviations in the process.")); + errors.append(String.format(ERROR_MESSAGE_TEMPLATE, "4-1s", analytic.name(), + analytic.level(), + "Four consecutive measurements exceed ±1 SD on same side of mean", + "Systematic Error. Check for calibration drift, reagent lot changes, or environmental conditions.")); reportedViolations.add(violationKey); } if (rule10x(values, mean, stdDev)) { - errors.append(String.format(ERROR_MESSAGE_TEMPLATE, "10x", record.name(), - record.level(), "Ten consecutive results are on the same side of the mean.", - "Perform maintenance or calibration on the equipment.")); + errors.append(String.format(ERROR_MESSAGE_TEMPLATE, "10x", analytic.name(), + analytic.level(), "Ten consecutive measurements on same side of mean", + "Systematic Error. Review calibration, reagent stability, and instrument maintenance. Recalibrate if necessary.")); reportedViolations.add(violationKey); } } @@ -72,6 +80,15 @@ public String validateRules(List records) { return errors.toString(); } + public boolean rule1_3s(List values, double mean, double stdDev) { + for (double value : values) { + if (value > mean + 3 * stdDev || value < mean - 3 * stdDev) { + return true; + } + } + return false; + } + public boolean rule4_1s(List values, double mean, double stdDev) { if (values.size() < 4) { return false; diff --git a/src/main/java/leonardo/labutilities/qualitylabpro/utils/constants/EmailTemplate.java b/src/main/java/leonardo/labutilities/qualitylabpro/utils/constants/EmailTemplate.java index 5c820b3..ebb2605 100644 --- a/src/main/java/leonardo/labutilities/qualitylabpro/utils/constants/EmailTemplate.java +++ b/src/main/java/leonardo/labutilities/qualitylabpro/utils/constants/EmailTemplate.java @@ -21,6 +21,16 @@ public class EmailTemplate { """; + public static final String TABLE_ROW = """ + + %s + %s + %s + %s + %s + %s + %s + """; public static final String ANALYTICS_WARNING_HEADER = """

⚠️ Quality Control Alert: Westgard violations diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 1d56976..35c0f8c 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -46,4 +46,4 @@ email.to.send.list=${EMAIL_TO_SEND_LIST} # = SECURITY CONFIGURATION # =============================== api.security.token.secret=${API_SECURITY_TOKEN_SECRET} -api.security.issuer=${API_SECURITY_ISSUER} \ No newline at end of file +api.security.issuer=${API_SECURITY_ISSUER} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 392bcdb..31ad475 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -32,10 +32,9 @@ spring.jpa.show-sql=false spring.jpa.open-in-view=false # Database Connection Pool -spring.datasource.tomcat.validation-query=SELECT 1 -spring.datasource.tomcat.test-on-borrow=true -spring.datasource.tomcat.test-while-idle=true -spring.datasource.tomcat.validation-interval=60000 +spring.datasource.hikari.maximum-pool-size=5 +spring.datasource.hikari.minimum-idle=1 +spring.datasource.hikari.idle-timeout=300000 # =============================== # = EMAIL CONFIGURATION diff --git a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/BiochemistryAnalyticControllerTest.java b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/BiochemistryAnalyticControllerTest.java index ebf7e83..6fb671e 100644 --- a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/BiochemistryAnalyticControllerTest.java +++ b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/BiochemistryAnalyticControllerTest.java @@ -24,7 +24,8 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; - +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import static leonardo.labutilities.qualitylabpro.utils.AnalyticsHelperMocks.createSampleRecordList; @@ -38,116 +39,114 @@ @AutoConfigureJsonTesters @ActiveProfiles("test") public class BiochemistryAnalyticControllerTest { - @Autowired - private MockMvc mockMvc; - - @MockitoBean - private TokenService tokenService; - - @MockitoBean - private UserRepository userRepository; - - @MockitoBean - private BiochemistryAnalyticService biochemistryAnalyticsService; - - @Autowired - private JacksonTester> jacksonGenericValuesRecord; - - @Autowired - private JacksonTester jacksonUpdateAnalyticsMeanRecord; - - @Test - @DisplayName("It should return a list of all analytics by level") - void getAllAnalytics_by_level_return_list() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(biochemistryAnalyticsService.findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), - any(Pageable.class))) - .thenReturn(page); - - mockMvc.perform(get("/biochemistry-analytics/level-date-range") - .param("level", "PCCC1") - .param("startDate", "2025-01-01 00:00:00") - .param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(biochemistryAnalyticsService, times(1)) - .findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), any(Pageable.class)); - } - - @Test - @DisplayName("It should return HTTP code 201 when analytics records are saved") - void analytics_post_return_201() throws Exception { - List records = createSampleRecordList(); - mockMvc.perform(post("/biochemistry-analytics").contentType(MediaType.APPLICATION_JSON) - .content(jacksonGenericValuesRecord.write(records).getJson())) - .andExpect(status().isCreated()); - verify(biochemistryAnalyticsService, times(1)).saveNewAnalyticsRecords(anyList()); - } - - @Test - @DisplayName("It should return HTTP code 204 when analytics records are updated") - void analytics_put_return_204() throws Exception { - var mockDto = new UpdateAnalyticsMeanDTO("Glucose", "PCCC1", "1234", 10.5); - mockMvc.perform(patch("/biochemistry-analytics").contentType(MediaType.APPLICATION_JSON) - .content(jacksonUpdateAnalyticsMeanRecord.write(mockDto).getJson())) - .andExpect(status().isNoContent()); - verify(biochemistryAnalyticsService, times(1)) - .updateAnalyticsMeanByNameAndLevelAndLevelLot("Glucose", "PCCC1", "1234", 10.5); - } - - @Test - @DisplayName("It should return a list of all analytics with pagination") - void getAllAnalytics_return_list() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(biochemistryAnalyticsService.findAnalyticsPagedByNameIn(anyList(), any(Pageable.class))) - .thenReturn(page); - - mockMvc.perform(get("/biochemistry-analytics") - .param("page", "0") - .param("size", "10")) - .andExpect(status().isOk()); - - verify(biochemistryAnalyticsService, times(1)) - .findAnalyticsPagedByNameIn(anyList(), any(Pageable.class)); - } - - - @Test - @DisplayName("It should return analytics records for a date range") - @WithMockUser(username = "admin", roles = {"ADMIN"}) - void getAnalyticsByDateRange_return_analytics() throws Exception { - Page records = new PageImpl<>(createSampleRecordList()); - - when(biochemistryAnalyticsService.findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any())) - .thenReturn(records); - - mockMvc.perform(get("/biochemistry-analytics/date-range") - .param("startDate", "2025-01-01 00:00:00").param("endDate", - "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(biochemistryAnalyticsService, times(1)) - .findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any()); - } - - @Test - @DisplayName("It should return mean and standard deviation for a date range") - @WithMockUser(username = "admin", roles = {"ADMIN"}) - void getMeanAndStandardDeviation_return_result() throws Exception { - MeanAndStdDeviationDTO result = new MeanAndStdDeviationDTO(10.5, 2.3); - when(biochemistryAnalyticsService.calculateMeanAndStandardDeviation(any(), any(), any(), - any())).thenReturn(result); - - mockMvc.perform(get("/biochemistry-analytics/mean-standard-deviation") - .param("name", "Hemoglobin").param("level", "High") - .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(biochemistryAnalyticsService, times(1)).calculateMeanAndStandardDeviation(any(), - any(), any(), any()); - } + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private TokenService tokenService; + + @MockitoBean + private UserRepository userRepository; + + @MockitoBean + private BiochemistryAnalyticService biochemistryAnalyticsService; + + @Autowired + private JacksonTester> jacksonGenericValuesRecord; + + @Autowired + private JacksonTester jacksonUpdateAnalyticsMeanRecord; + + @Test + @DisplayName("It should return a list of all analytics by level") + void getAllAnalytics_by_level_return_list() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(biochemistryAnalyticsService.findAnalyticsByNameInByLevel(anyList(), any(), any(), + any(), any(Pageable.class))).thenReturn(page); + + mockMvc.perform(get("/biochemistry-analytics/level-date-range").param("level", "PCCC1") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) + .andExpect(status().isOk()); + + verify(biochemistryAnalyticsService, times(1)).findAnalyticsByNameInByLevel(anyList(), + any(), any(), any(), any(Pageable.class)); + } + + @Test + @DisplayName("It should return HTTP code 201 when analytics records are saved") + void analytics_post_return_201() throws Exception { + List records = createSampleRecordList(); + mockMvc.perform(post("/biochemistry-analytics").contentType(MediaType.APPLICATION_JSON) + .content(jacksonGenericValuesRecord.write(records).getJson())) + .andExpect(status().isCreated()); + verify(biochemistryAnalyticsService, times(1)).saveNewAnalyticsRecords(anyList()); + } + + @Test + @DisplayName("It should return HTTP code 204 when analytics records are updated") + void analytics_put_return_204() throws Exception { + var mockDto = new UpdateAnalyticsMeanDTO("Glucose", "PCCC1", "1234", 10.5); + mockMvc.perform(patch("/biochemistry-analytics").contentType(MediaType.APPLICATION_JSON) + .content(jacksonUpdateAnalyticsMeanRecord.write(mockDto).getJson())) + .andExpect(status().isNoContent()); + verify(biochemistryAnalyticsService, times(1)) + .updateAnalyticsMeanByNameAndLevelAndLevelLot("Glucose", "PCCC1", "1234", 10.5); + } + + @Test + @DisplayName("It should return a list of all analytics with pagination") + void getAllAnalytics_return_list() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(biochemistryAnalyticsService.findAnalyticsPagedByNameIn(anyList(), + any(Pageable.class))).thenReturn(page); + + mockMvc.perform(get("/biochemistry-analytics").param("page", "0").param("size", "10")) + .andExpect(status().isOk()); + + verify(biochemistryAnalyticsService, times(1)).findAnalyticsPagedByNameIn(anyList(), + any(Pageable.class)); + } + + + @Test + @DisplayName("It should return analytics records for a date range") + @WithMockUser(username = "admin", roles = {"ADMIN"}) + void getAnalyticsByDateRange_return_analytics() throws Exception { + Page records = new PageImpl<>(createSampleRecordList()); + + when(biochemistryAnalyticsService.findAnalyticsByNameInAndDateBetween(anyList(), any(), + any(), any())).thenReturn(records); + + mockMvc.perform(get("/biochemistry-analytics/date-range") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) + .andExpect(status().isOk()); + + verify(biochemistryAnalyticsService, times(1)) + .findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any()); + } + + @Test + @DisplayName("It should return mean and standard deviation for a date range") + void getMeanAndStandardDeviation_return_result() throws Exception { + MeanAndStdDeviationDTO result = new MeanAndStdDeviationDTO(10.5, 2.3); + LocalDateTime startDate = LocalDateTime.parse("2025-01-01 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + LocalDateTime endDate = LocalDateTime.parse("2025-01-05 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + when(biochemistryAnalyticsService.calculateMeanAndStandardDeviation(eq("Hemoglobin"), + eq("High"), eq(startDate), eq(endDate), any(Pageable.class))).thenReturn(result); + + mockMvc.perform(get("/biochemistry-analytics/mean-standard-deviation") + .param("name", "Hemoglobin").param("level", "High") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00") + .param("page", "0").param("size", "10")).andExpect(status().isOk()); + + verify(biochemistryAnalyticsService).calculateMeanAndStandardDeviation(eq("Hemoglobin"), + eq("High"), eq(startDate), eq(endDate), any(Pageable.class)); + } } diff --git a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/CoagulationAnalyticControllerTest.java b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/CoagulationAnalyticControllerTest.java index 84fb80c..3102a18 100644 --- a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/CoagulationAnalyticControllerTest.java +++ b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/CoagulationAnalyticControllerTest.java @@ -19,13 +19,18 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; - +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import static leonardo.labutilities.qualitylabpro.utils.AnalyticsHelperMocks.createSampleRecordList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -38,100 +43,100 @@ @ActiveProfiles("test") public class CoagulationAnalyticControllerTest { - @Autowired - private MockMvc mockMvc; - - @MockitoBean - private TokenService tokenService; - - @MockitoBean - private UserRepository userRepository; - - @MockitoBean - private CoagulationAnalyticService coagulationAnalyticsService; - - @Autowired - private JacksonTester> jacksonGenericValuesRecord; - - @Test - @DisplayName("It should return a list of all analytics by level") - void getAllAnalytics_by_level_return_list() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(coagulationAnalyticsService - .findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), any(Pageable.class))) - .thenReturn(page); - - mockMvc.perform(get("/coagulation-analytics/level-date-range") - .param("level", "PCCC1") - .param("startDate", "2025-01-01 00:00:00") - .param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(coagulationAnalyticsService, times(1)) - .findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), any(Pageable.class)); - } - - @Test - @DisplayName("It should return HTTP code 201 when analytics records are saved") - void analytics_post_return_201() throws Exception { - List records = createSampleRecordList(); - mockMvc.perform(post("/coagulation-analytics").contentType(MediaType.APPLICATION_JSON) - .content(jacksonGenericValuesRecord.write(records).getJson())) - .andExpect(status().isCreated()); - verify(coagulationAnalyticsService, times(1)).saveNewAnalyticsRecords(anyList()); - } - - @Test - @DisplayName("It should return a list of all analytics with pagination") - void getAllAnalytics_return_list() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(coagulationAnalyticsService.findAnalyticsPagedByNameIn(anyList(), any(Pageable.class))) - .thenReturn(page); - - mockMvc.perform(get("/coagulation-analytics") - .param("page", "0") - .param("size", "10")) - .andExpect(status().isOk()); - - verify(coagulationAnalyticsService, times(1)) - .findAnalyticsPagedByNameIn(anyList(), any(Pageable.class)); - } - - @Test - @DisplayName("It should return analytics records for a date range") - void getAnalyticsByDateRange_return_analytics() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - - when(coagulationAnalyticsService.findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any())) - .thenReturn(page); - - mockMvc.perform(get("/coagulation-analytics/date-range") - .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(coagulationAnalyticsService, times(1)) - .findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any()); - } - - @Test - @DisplayName("It should return mean and standard deviation for a date range") - void getMeanAndStandardDeviation_return_result() throws Exception { - MeanAndStdDeviationDTO result = new MeanAndStdDeviationDTO(10.5, 2.3); - when(coagulationAnalyticsService.calculateMeanAndStandardDeviation(any(), any(), any(), - any())).thenReturn(result); - - mockMvc.perform(get("/coagulation-analytics/mean-standard-deviation") - .param("name", "Hemoglobin").param("level", "High") - .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(coagulationAnalyticsService, times(1)).calculateMeanAndStandardDeviation(any(), - any(), any(), any()); - } + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private TokenService tokenService; + + @MockitoBean + private UserRepository userRepository; + + @MockitoBean + private CoagulationAnalyticService coagulationAnalyticsService; + + @Autowired + private JacksonTester> jacksonGenericValuesRecord; + + @Test + @DisplayName("It should return a list of all analytics by level") + void getAllAnalytics_by_level_return_list() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(coagulationAnalyticsService.findAnalyticsByNameInByLevel(anyList(), any(), any(), + any(), any(Pageable.class))).thenReturn(page); + + mockMvc.perform(get("/coagulation-analytics/level-date-range").param("level", "PCCC1") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) + .andExpect(status().isOk()); + + verify(coagulationAnalyticsService, times(1)).findAnalyticsByNameInByLevel(anyList(), any(), + any(), any(), any(Pageable.class)); + } + + @Test + @DisplayName("It should return HTTP code 201 when analytics records are saved") + void analytics_post_return_201() throws Exception { + List records = createSampleRecordList(); + mockMvc.perform(post("/coagulation-analytics").contentType(MediaType.APPLICATION_JSON) + .content(jacksonGenericValuesRecord.write(records).getJson())) + .andExpect(status().isCreated()); + verify(coagulationAnalyticsService, times(1)).saveNewAnalyticsRecords(anyList()); + } + + @Test + @DisplayName("It should return a list of all analytics with pagination") + void getAllAnalytics_return_list() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(coagulationAnalyticsService.findAnalyticsPagedByNameIn(anyList(), any(Pageable.class))) + .thenReturn(page); + + mockMvc.perform(get("/coagulation-analytics").param("page", "0").param("size", "10")) + .andExpect(status().isOk()); + + verify(coagulationAnalyticsService, times(1)).findAnalyticsPagedByNameIn(anyList(), + any(Pageable.class)); + } + + @Test + @DisplayName("It should return analytics records for a date range") + void getAnalyticsByDateRange_return_analytics() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + + when(coagulationAnalyticsService.findAnalyticsByNameInAndDateBetween(anyList(), any(), + any(), any())).thenReturn(page); + + mockMvc.perform(get("/coagulation-analytics/date-range") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) + .andExpect(status().isOk()); + + verify(coagulationAnalyticsService, times(1)).findAnalyticsByNameInAndDateBetween(anyList(), + any(), any(), any()); + } + + @Test + @DisplayName("It should return mean and standard deviation for a date range") + void getMeanAndStandardDeviation_return_result() throws Exception { + MeanAndStdDeviationDTO result = new MeanAndStdDeviationDTO(10.5, 2.3); + LocalDateTime startDate = LocalDateTime.parse("2025-01-01 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + LocalDateTime endDate = LocalDateTime.parse("2025-01-05 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + when(coagulationAnalyticsService.calculateMeanAndStandardDeviation(eq("Hemoglobin"), + eq("High"), eq(startDate), eq(endDate), any(Pageable.class))).thenReturn(result); + + mockMvc.perform(get("/coagulation-analytics/mean-standard-deviation") + .param("name", "Hemoglobin").param("level", "High") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00") + .param("page", "0").param("size", "10")).andExpect(status().isOk()); + + verify(coagulationAnalyticsService).calculateMeanAndStandardDeviation(eq("Hemoglobin"), + eq("High"), eq(startDate), eq(endDate), any(Pageable.class)); + } } diff --git a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/HematologyAnalyticControllerTest.java b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/HematologyAnalyticControllerTest.java index f3211d7..8f4a93a 100644 --- a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/HematologyAnalyticControllerTest.java +++ b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/HematologyAnalyticControllerTest.java @@ -19,13 +19,18 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; - +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import static leonardo.labutilities.qualitylabpro.utils.AnalyticsHelperMocks.createSampleRecordList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -38,101 +43,100 @@ @ActiveProfiles("test") public class HematologyAnalyticControllerTest { - @MockitoBean - private TokenService tokenService; - - @MockitoBean - private UserRepository userRepository; - - @Autowired - private MockMvc mockMvc; - - @MockitoBean - private HematologyAnalyticService hematologyAnalyticsService; - - @Autowired - private JacksonTester> jacksonGenericValuesRecord; - - @Test - @DisplayName("It should return a list of all analytics by level") - void getAllAnalytics_by_level_return_list() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(hematologyAnalyticsService - .findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), any(Pageable.class))) - .thenReturn(page); - - mockMvc.perform(get("/hematology-analytics/level-date-range") - .param("level", "PCCC1") - .param("startDate", "2025-01-01 00:00:00") - .param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(hematologyAnalyticsService, times(1)) - .findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), any(Pageable.class)); - } - - @Test - @DisplayName("It should return HTTP code 201 when analytics records are saved") - void analytics_post_return_201() throws Exception { - List records = createSampleRecordList(); - mockMvc.perform(post("/hematology-analytics").contentType(MediaType.APPLICATION_JSON) - .content(jacksonGenericValuesRecord.write(records).getJson())) - .andExpect(status().isCreated()); - verify(hematologyAnalyticsService, times(1)).saveNewAnalyticsRecords(anyList()); - } - - @Test - @DisplayName("It should return a list of all analytics with pagination") - void getAllAnalytics_return_list() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(hematologyAnalyticsService.findAnalyticsPagedByNameIn(anyList(), any(Pageable.class))) - .thenReturn(page); - - mockMvc.perform(get("/hematology-analytics") - .param("page", "0") - .param("size", "10")) - .andExpect(status().isOk()); - - verify(hematologyAnalyticsService, times(1)) - .findAnalyticsPagedByNameIn(anyList(), any(Pageable.class)); - } - - - @Test - @DisplayName("It should return analytics records for a date range") - void getAnalyticsByDateRange_return_analytics() throws Exception { - List records = createSampleRecordList(); - Page page = new PageImpl<>(records); - - when(hematologyAnalyticsService.findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any())) - .thenReturn(page); - - mockMvc.perform(get("/hematology-analytics/date-range") - .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - - verify(hematologyAnalyticsService, times(1)) - .findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), any()); - } - - @Test - @DisplayName("It should return mean and standard deviation for a date range") - void getMeanAndStandardDeviation_return_result() throws Exception { - MeanAndStdDeviationDTO result = new MeanAndStdDeviationDTO(10.5, 2.3); - when(hematologyAnalyticsService.calculateMeanAndStandardDeviation(any(), any(), any(), - any())).thenReturn(result); - - mockMvc.perform(get("/hematology-analytics/mean-standard-deviation") - .param("name", "Hemoglobin").param("level", "High") - .param("startDate", "2025-01-01 00:00:00").param("endDate", - "2025-01-05 00:00:00")) - .andExpect(status().isOk()); - verify(hematologyAnalyticsService, times(1)) - .calculateMeanAndStandardDeviation(any(), any(), - any(), any()); - } + @MockitoBean + private TokenService tokenService; + + @MockitoBean + private UserRepository userRepository; + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private HematologyAnalyticService hematologyAnalyticsService; + + @Autowired + private JacksonTester> jacksonGenericValuesRecord; + + @Test + @DisplayName("It should return a list of all analytics by level") + void getAllAnalytics_by_level_return_list() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(hematologyAnalyticsService.findAnalyticsByNameInByLevel(anyList(), any(), any(), any(), + any(Pageable.class))).thenReturn(page); + + mockMvc.perform(get("/hematology-analytics/level-date-range").param("level", "PCCC1") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) + .andExpect(status().isOk()); + + verify(hematologyAnalyticsService, times(1)).findAnalyticsByNameInByLevel(anyList(), any(), + any(), any(), any(Pageable.class)); + } + + @Test + @DisplayName("It should return HTTP code 201 when analytics records are saved") + void analytics_post_return_201() throws Exception { + List records = createSampleRecordList(); + mockMvc.perform(post("/hematology-analytics").contentType(MediaType.APPLICATION_JSON) + .content(jacksonGenericValuesRecord.write(records).getJson())) + .andExpect(status().isCreated()); + verify(hematologyAnalyticsService, times(1)).saveNewAnalyticsRecords(anyList()); + } + + @Test + @DisplayName("It should return a list of all analytics with pagination") + void getAllAnalytics_return_list() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(hematologyAnalyticsService.findAnalyticsPagedByNameIn(anyList(), any(Pageable.class))) + .thenReturn(page); + + mockMvc.perform(get("/hematology-analytics").param("page", "0").param("size", "10")) + .andExpect(status().isOk()); + + verify(hematologyAnalyticsService, times(1)).findAnalyticsPagedByNameIn(anyList(), + any(Pageable.class)); + } + + + @Test + @DisplayName("It should return analytics records for a date range") + void getAnalyticsByDateRange_return_analytics() throws Exception { + List records = createSampleRecordList(); + Page page = new PageImpl<>(records); + + when(hematologyAnalyticsService.findAnalyticsByNameInAndDateBetween(anyList(), any(), any(), + any())).thenReturn(page); + + mockMvc.perform(get("/hematology-analytics/date-range") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00")) + .andExpect(status().isOk()); + + verify(hematologyAnalyticsService, times(1)).findAnalyticsByNameInAndDateBetween(anyList(), + any(), any(), any()); + } + + @Test + @DisplayName("It should return mean and standard deviation for a date range") + void getMeanAndStandardDeviation_return_result() throws Exception { + MeanAndStdDeviationDTO result = new MeanAndStdDeviationDTO(10.5, 2.3); + LocalDateTime startDate = LocalDateTime.parse("2025-01-01 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + LocalDateTime endDate = LocalDateTime.parse("2025-01-05 00:00:00", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + when(hematologyAnalyticsService.calculateMeanAndStandardDeviation(eq("Hemoglobin"), + eq("High"), eq(startDate), eq(endDate), any(Pageable.class))).thenReturn(result); + + mockMvc.perform(get("/hematology-analytics/mean-standard-deviation") + .param("name", "Hemoglobin").param("level", "High") + .param("startDate", "2025-01-01 00:00:00").param("endDate", "2025-01-05 00:00:00") + .param("page", "0").param("size", "10")).andExpect(status().isOk()); + + verify(hematologyAnalyticsService).calculateMeanAndStandardDeviation(eq("Hemoglobin"), + eq("High"), eq(startDate), eq(endDate), any(Pageable.class)); + } } diff --git a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/UsersControllerTest.java b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/UsersControllerTest.java index 15e66a8..7a64832 100644 --- a/src/test/java/leonardo/labutilities/qualitylabpro/controllers/UsersControllerTest.java +++ b/src/test/java/leonardo/labutilities/qualitylabpro/controllers/UsersControllerTest.java @@ -1,17 +1,17 @@ package leonardo.labutilities.qualitylabpro.controllers; -import leonardo.labutilities.qualitylabpro.configs.TestSecurityConfig; -import leonardo.labutilities.qualitylabpro.controllers.users.UsersController; -import leonardo.labutilities.qualitylabpro.dtos.authentication.LoginUserDTO; -import leonardo.labutilities.qualitylabpro.dtos.authentication.TokenJwtDTO; -import leonardo.labutilities.qualitylabpro.dtos.users.RecoverPasswordDTO; -import leonardo.labutilities.qualitylabpro.dtos.users.UpdatePasswordDTO; -import leonardo.labutilities.qualitylabpro.dtos.users.UsersDTO; -import leonardo.labutilities.qualitylabpro.entities.User; -import leonardo.labutilities.qualitylabpro.enums.UserRoles; -import leonardo.labutilities.qualitylabpro.repositories.UserRepository; -import leonardo.labutilities.qualitylabpro.services.authentication.TokenService; -import leonardo.labutilities.qualitylabpro.services.users.UserService; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -32,16 +32,19 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; +import leonardo.labutilities.qualitylabpro.configs.TestSecurityConfig; +import leonardo.labutilities.qualitylabpro.controllers.users.UsersController; +import leonardo.labutilities.qualitylabpro.dtos.authentication.LoginUserDTO; +import leonardo.labutilities.qualitylabpro.dtos.authentication.TokenJwtDTO; +import leonardo.labutilities.qualitylabpro.dtos.users.RecoverPasswordDTO; +import leonardo.labutilities.qualitylabpro.dtos.users.SignUpUsersDTO; +import leonardo.labutilities.qualitylabpro.dtos.users.UpdatePasswordDTO; +import leonardo.labutilities.qualitylabpro.dtos.users.UsersDTO; +import leonardo.labutilities.qualitylabpro.entities.User; +import leonardo.labutilities.qualitylabpro.enums.UserRoles; +import leonardo.labutilities.qualitylabpro.repositories.UserRepository; +import leonardo.labutilities.qualitylabpro.services.authentication.TokenService; +import leonardo.labutilities.qualitylabpro.services.users.UserService; @WebMvcTest(UsersController.class) @Import(TestSecurityConfig.class) @@ -68,6 +71,10 @@ class UsersControllerTest { @Autowired private JacksonTester usersRecordJacksonTester; + @Autowired + private JacksonTester signUpUsersDTOJacksonTester; + + @Autowired private JacksonTester loginRecordJacksonTester; @@ -80,18 +87,15 @@ class UsersControllerTest { @Test @DisplayName("Should return 204 when signing up a new user") void signUp_return_204() throws Exception { - UsersDTO usersDTO = new UsersDTO("testUser", "Marmota2024@", "test@example.com"); - User user = new User("testUser", "password", "test@example.com", UserRoles.USER); - - when(userService.signUp(anyString(), anyString(), anyString())).thenReturn(user); + SignUpUsersDTO signUpUsersDTO = + new SignUpUsersDTO("Leonardo Meireles", "marmotas2024@email.com", "marmotas2024@"); mockMvc.perform(post("/users/sign-up").contentType(MediaType.APPLICATION_JSON) - .content(usersRecordJacksonTester.write(usersDTO).getJson())) - .andExpect(status().isNoContent()) - .andExpect(jsonPath("$.identifier").value("testUser")) - .andExpect(jsonPath("$.identifier").value("test@example.com")); + .content(signUpUsersDTOJacksonTester.write(signUpUsersDTO).getJson())) + .andExpect(status().isNoContent()); - verify(userService).signUp(usersDTO.username(), usersDTO.password(), usersDTO.email()); + verify(userService).signUp(signUpUsersDTO.identifier(), signUpUsersDTO.email(), + signUpUsersDTO.password()); } @Test diff --git a/src/test/java/leonardo/labutilities/qualitylabpro/services/AnalyticHelperServiceTests.java b/src/test/java/leonardo/labutilities/qualitylabpro/services/AnalyticHelperServiceTests.java index b343351..9b18150 100644 --- a/src/test/java/leonardo/labutilities/qualitylabpro/services/AnalyticHelperServiceTests.java +++ b/src/test/java/leonardo/labutilities/qualitylabpro/services/AnalyticHelperServiceTests.java @@ -51,14 +51,15 @@ void setUp() { @Override public List findAnalyticsByNameAndLevel(Pageable pageable, - String name, String level) { + String name, String level) { return analyticsRepository.findByNameAndLevel(pageable, name, level).stream() .map(AnalyticMapper::toRecord).toList(); } @Override public List findAnalyticsByNameAndLevelAndDate(String name, - String level, LocalDateTime dateStart, LocalDateTime dateEnd) { + String level, LocalDateTime dateStart, LocalDateTime dateEnd, + Pageable pageable) { return analyticsRepository .findByNameAndLevelAndDateBetween(name, level, dateStart, dateEnd, PageRequest.of(0, 200)) @@ -123,7 +124,8 @@ void saveNewAnalyticsRecords_WithValidRecords_ShouldSaveSuccessfully() { List records = createSampleRecordList(); when(analyticsRepository.existsByDateAndLevelAndName(any(), any(), any())) .thenReturn(false); - when(analyticsRepository.saveAll(any())).thenReturn(null); + when(analyticsRepository.saveAll(any())) + .thenAnswer(invocation -> invocation.getArgument(0)); assertDoesNotThrow(() -> analyticHelperService.saveNewAnalyticsRecords(records)); verify(analyticsRepository, times(1)).saveAll(any()); @@ -191,8 +193,8 @@ void findAllAnalyticsByNameAndLevelAndDate_WithDateRange_ShouldReturnFilteredRec when(analyticsRepository.findByNameAndLevelAndDateBetween(eq(name), eq(level), eq(startDate), eq(endDate), any(Pageable.class))).thenReturn(expectedRecords); - List result = analyticHelperService - .findAnalyticsByNameAndLevelAndDate(name, level, startDate, endDate); + List result = analyticHelperService.findAnalyticsByNameAndLevelAndDate(name, + level, startDate, endDate, null); assertNotNull(result); assertEquals(expectedRecords.size(), result.size()); @@ -281,8 +283,8 @@ void findGroupedAnalyticsByLevel_WithValidInputs_ShouldReturnGroupedRecords() { when(analyticsRepository.findByNameAndDateBetweenGroupByLevel(eq(name), eq(startDate), eq(endDate), any(Pageable.class))).thenReturn(records); - List result = - analyticHelperService.findGroupedAnalyticsByLevel(name, startDate, endDate); + List result = analyticHelperService + .findGroupedAnalyticsByLevel(name, startDate, endDate, Pageable.unpaged()); assertNotNull(result); assertFalse(result.isEmpty()); diff --git a/src/test/java/leonardo/labutilities/qualitylabpro/services/UserServiceTest.java b/src/test/java/leonardo/labutilities/qualitylabpro/services/UserServiceTest.java index 9e309df..0f4af46 100644 --- a/src/test/java/leonardo/labutilities/qualitylabpro/services/UserServiceTest.java +++ b/src/test/java/leonardo/labutilities/qualitylabpro/services/UserServiceTest.java @@ -23,101 +23,102 @@ @ExtendWith(MockitoExtension.class) class UserServiceTest { - @Mock - private UserRepository userRepository; - - @Mock - private PasswordRecoveryTokenManager passwordRecoveryTokenManager; - - @Mock - private EmailService emailService; - - @InjectMocks - private UserService userService; - - @Test - void testRecoverPassword_UserExists() { - when(userRepository.existsByUsernameAndEmail(anyString(), anyString())).thenReturn(true); - when(passwordRecoveryTokenManager.generateTemporaryPassword()).thenReturn("tempPassword"); - - userService.recoverPassword("identifier", "identifier@example.com"); - - verify(passwordRecoveryTokenManager).generateAndStoreToken("identifier@example.com", - "tempPassword"); - verify(emailService).sendPlainTextEmail(any()); - } - - @Test - void testRecoverPassword_UserDoesNotExist() { - when(userRepository.existsByUsernameAndEmail(anyString(), anyString())).thenReturn(false); - - assertThrows(CustomGlobalErrorHandling.ResourceNotFoundException.class, - () -> userService.recoverPassword("identifier", "identifier@example.com")); - } - - @Test - void testChangePassword_ValidToken() { - User user = new User("identifier", BCryptEncoderComponent.encrypt("newPassword"), - "identifier@example.com", UserRoles.USER); - when(passwordRecoveryTokenManager.isRecoveryTokenValid(anyString(), anyString())) - .thenReturn(true); - userService.changePassword("identifier@example.com", "tempPassword", "newPassword"); - assertThat(passwordRecoveryTokenManager.isRecoveryTokenValid("tempPassword", - "identifier@example.com")).isTrue(); - } - - @Test - void testChangePassword_InvalidToken() { - when(passwordRecoveryTokenManager.isRecoveryTokenValid(anyString(), anyString())) - .thenReturn(false); - - assertThrows(CustomGlobalErrorHandling.ResourceNotFoundException.class, () -> userService - .changePassword("identifier@example.com", "tempPassword", "newPassword")); - } - - @Test - void testSignUp_UserAlreadyExists() { - when(userRepository.existsByEmail(anyString())).thenReturn(true); - - assertThrows(CustomGlobalErrorHandling.UserAlreadyExistException.class, - () -> userService.signUp("identifier", "password", "identifier@example.com")); - } - - @Test - void testSignUp_NewUser() { - when(userRepository.existsByEmail(anyString())).thenReturn(false); - when(userRepository.save(any(User.class))).thenReturn( - new User("identifier", "encryptedPassword", "identifier@example.com", UserRoles.USER)); - - User user = userService.signUp("identifier", "password", "identifier@example.com"); - - assertNotNull(user); - assertEquals("identifier", user.getUsername()); - assertEquals("identifier@example.com", user.getEmail()); - } - - @Test - void should_return_error_with_testUpdateUserPassword_PasswordMatches() { - User user = new User("identifier", BCryptEncoderComponent.encrypt("newPassword"), - "identifier@example.com", UserRoles.USER); - - when(userRepository.getReferenceByUsernameAndEmail(anyString(), anyString())) - .thenReturn(user); - - assertThrows(CustomGlobalErrorHandling.PasswordNotMatchesException.class, () -> userService - .updateUserPassword("identifier", "identifier@example.com", "oldPassword", "newPassword")); - verify(userRepository, never()).setPasswordWhereByUsername(anyString(), anyString()); - } - - @Test - void testUpdateUserPassword_PasswordDoesNotMatch() { - User user = new User("identifier", BCryptEncoderComponent.encrypt("oldPassword"), - "identifier@example.com", UserRoles.USER); - when(userRepository.getReferenceByUsernameAndEmail(anyString(), anyString())) - .thenReturn(user); - - assertThrows(CustomGlobalErrorHandling.PasswordNotMatchesException.class, - () -> userService.updateUserPassword("identifier", "identifier@example.com", - "wrongPassword", "newPassword")); - } + @Mock + private UserRepository userRepository; + + @Mock + private PasswordRecoveryTokenManager passwordRecoveryTokenManager; + + @Mock + private EmailService emailService; + + @InjectMocks + private UserService userService; + + @Test + void testRecoverPassword_UserExists() { + when(userRepository.existsByUsernameAndEmail(anyString(), anyString())).thenReturn(true); + when(passwordRecoveryTokenManager.generateTemporaryPassword()).thenReturn("tempPassword"); + + userService.recoverPassword("identifier", "identifier@example.com"); + + verify(passwordRecoveryTokenManager).generateAndStoreToken("identifier@example.com", + "tempPassword"); + verify(emailService).sendPlainTextEmail(any()); + } + + @Test + void testRecoverPassword_UserDoesNotExist() { + when(userRepository.existsByUsernameAndEmail(anyString(), anyString())).thenReturn(false); + + assertThrows(CustomGlobalErrorHandling.ResourceNotFoundException.class, + () -> userService.recoverPassword("identifier", "identifier@example.com")); + } + + @Test + void testChangePassword_ValidToken() { + User user = new User("identifier", BCryptEncoderComponent.encrypt("newPassword"), + "identifier@example.com", UserRoles.USER); + when(passwordRecoveryTokenManager.isRecoveryTokenValid(anyString(), anyString())) + .thenReturn(true); + userService.changePassword("identifier@example.com", "tempPassword", "newPassword"); + assertThat(passwordRecoveryTokenManager.isRecoveryTokenValid("tempPassword", + "identifier@example.com")).isTrue(); + } + + @Test + void testChangePassword_InvalidToken() { + when(passwordRecoveryTokenManager.isRecoveryTokenValid(anyString(), anyString())) + .thenReturn(false); + + assertThrows(CustomGlobalErrorHandling.ResourceNotFoundException.class, () -> userService + .changePassword("identifier@example.com", "tempPassword", "newPassword")); + } + + @Test + void testSignUp_UserAlreadyExists() { + when(userRepository.existsByUsernameOrEmail(anyString(), anyString())).thenReturn(true); + + assertThrows(CustomGlobalErrorHandling.UserAlreadyExistException.class, + () -> userService.signUp("identifier", "identifier@example.com", "password")); + } + + @Test + void testSignUp_NewUser() { + when(userRepository.existsByUsernameOrEmail(anyString(), anyString())).thenReturn(false); + when(userRepository.save(any(User.class))).thenReturn(new User("identifier", + "encryptedPassword", "identifier@example.com", UserRoles.USER)); + + User user = userService.signUp("identifier", "password", "identifier@example.com"); + + assertNotNull(user); + assertEquals("identifier", user.getUsername()); + assertEquals("identifier@example.com", user.getEmail()); + } + + @Test + void should_return_error_with_testUpdateUserPassword_PasswordMatches() { + User user = new User("identifier", BCryptEncoderComponent.encrypt("newPassword"), + "identifier@example.com", UserRoles.USER); + + when(userRepository.getReferenceByUsernameAndEmail(anyString(), anyString())) + .thenReturn(user); + + assertThrows(CustomGlobalErrorHandling.PasswordNotMatchesException.class, + () -> userService.updateUserPassword("identifier", "identifier@example.com", + "oldPassword", "newPassword")); + verify(userRepository, never()).setPasswordWhereByUsername(anyString(), anyString()); + } + + @Test + void testUpdateUserPassword_PasswordDoesNotMatch() { + User user = new User("identifier", BCryptEncoderComponent.encrypt("oldPassword"), + "identifier@example.com", UserRoles.USER); + when(userRepository.getReferenceByUsernameAndEmail(anyString(), anyString())) + .thenReturn(user); + + assertThrows(CustomGlobalErrorHandling.PasswordNotMatchesException.class, + () -> userService.updateUserPassword("identifier", "identifier@example.com", + "wrongPassword", "newPassword")); + } }