diff --git a/endpoint-insights-api/pom.xml b/endpoint-insights-api/pom.xml index 92a3067..7e9d679 100644 --- a/endpoint-insights-api/pom.xml +++ b/endpoint-insights-api/pom.xml @@ -100,7 +100,26 @@ 3.5.7 - + + org.mapstruct + mapstruct + 1.5.5.Final + + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + provided + + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + @@ -164,6 +183,16 @@ lombok 1.18.42 + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + diff --git a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/controller/BatchesController.java b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/controller/BatchesController.java index a998f25..3b46bfc 100644 --- a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/controller/BatchesController.java +++ b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/controller/BatchesController.java @@ -2,14 +2,16 @@ import com.vsp.endpointinsightsapi.dto.BatchRequestDTO; import com.vsp.endpointinsightsapi.dto.BatchResponseDTO; -import com.vsp.endpointinsightsapi.model.TestBatch; import com.vsp.endpointinsightsapi.service.BatchService; +import com.vsp.endpointinsightsapi.model.TestBatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -17,30 +19,45 @@ @RequestMapping("/api/batches") public class BatchesController { - private static final Logger LOG = LoggerFactory.getLogger(BatchesController.class); + private static final Logger LOG = LoggerFactory.getLogger(BatchesController.class); - private final BatchService batchService; + private final BatchService batchService; - public BatchesController(BatchService batchService) { - this.batchService = batchService; - } + public BatchesController(BatchService batchService){ + this.batchService = batchService; + } - // GET /api/batches — stub list (unchanged) + // GET /api/batches — stub list (unchanged) @GetMapping public ResponseEntity> listBatches() { - List batches = List.of( - new BatchResponseDTO(1L, "Daily API Tests", "ACTIVE"), - new BatchResponseDTO(2L, "Weekly Regression", "INACTIVE") - ); - return ResponseEntity.ok(batches); + List batches = List.of( + BatchResponseDTO.builder() + .id(UUID.randomUUID()) + .batchName("Daily API Tests") + .scheduleId(334523453L) + .startTime(LocalDate.now().minusDays(1)) + .lastTimeRun(LocalDate.now()) + .active(true) +// .jobs(Collections.emptyList()) + .build(), + BatchResponseDTO.builder() + .id(UUID.randomUUID()) + .batchName("Weekly Regression") + .scheduleId(42L) + .startTime(LocalDate.now().minusWeeks(1)) + .lastTimeRun(LocalDate.now().minusDays(3)) + .active(false) +// .jobs(Collections.emptyList()) + .build() + ); + return ResponseEntity.ok(batches); } // GET /api/batches/{id} @GetMapping("/{id}") - public ResponseEntity getBatch(@PathVariable UUID id) { - return batchService.getBatchById(id) - .map(b -> ResponseEntity.ok(b)) - .orElseGet(() -> ResponseEntity.notFound().build()); + public ResponseEntity getBatch(@PathVariable UUID id) { + BatchResponseDTO batch = batchService.getBatchById(id); + return ResponseEntity.ok(batch); } // POST /api/batches @@ -52,15 +69,20 @@ public ResponseEntity createBatch(@RequestBody BatchRequestDTO reques // PUT /api/batches/{id} — stubbed @PutMapping("/{id}") - public ResponseEntity updateBatch(@PathVariable Long id, - @RequestBody BatchRequestDTO request) { - BatchResponseDTO updated = new BatchResponseDTO(id, request.getName(), "UPDATED"); - return ResponseEntity.ok(updated); + public ResponseEntity updateBatch(@PathVariable UUID id, @RequestBody BatchRequestDTO request) { + BatchResponseDTO updated = BatchResponseDTO.builder() + .id(id) + .batchName(request.getName()) + .lastTimeRun(LocalDate.now()) + .build(); + + return ResponseEntity.ok(updated); } - // DELETE /api/batches/{id} — stubbed + // DELETE /api/batches/{id} @DeleteMapping("/{id}") - public ResponseEntity deleteBatch(@PathVariable Long id) { - return ResponseEntity.noContent().build(); + public ResponseEntity deleteBatch(@PathVariable UUID id) { + batchService.deleteBatchById(id); + return ResponseEntity.noContent().build(); } } diff --git a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/dto/BatchResponseDTO.java b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/dto/BatchResponseDTO.java index 5e5ebea..0057393 100644 --- a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/dto/BatchResponseDTO.java +++ b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/dto/BatchResponseDTO.java @@ -1,17 +1,23 @@ package com.vsp.endpointinsightsapi.dto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import com.vsp.endpointinsightsapi.model.Job; +import lombok.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; @Getter @Setter +@Builder @NoArgsConstructor @AllArgsConstructor public class BatchResponseDTO { - private Long id; - private String name; - private String status; + private UUID id; + private String batchName; + private Long scheduleId; + private LocalDate startTime; + private LocalDate lastTimeRun; + private Boolean active; } diff --git a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/exception/BatchNotFoundException.java b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/exception/BatchNotFoundException.java new file mode 100644 index 0000000..e0c40b6 --- /dev/null +++ b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/exception/BatchNotFoundException.java @@ -0,0 +1,14 @@ +package com.vsp.endpointinsightsapi.exception; + +import org.springframework.http.HttpStatus; + +/** + * Thrown when a batch with the given ID does not exist in the database. + */ +public class BatchNotFoundException extends CustomException { + + public BatchNotFoundException(String batchId) { + super(HttpStatus.NOT_FOUND, + new ErrorResponse("BATCH_NOT_FOUND", "Batch not found with ID: " + batchId, null)); + } +} diff --git a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/mapper/BatchMapper.java b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/mapper/BatchMapper.java new file mode 100644 index 0000000..72ad45b --- /dev/null +++ b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/mapper/BatchMapper.java @@ -0,0 +1,14 @@ +package com.vsp.endpointinsightsapi.mapper; + +import com.vsp.endpointinsightsapi.dto.BatchResponseDTO; +import com.vsp.endpointinsightsapi.model.TestBatch; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface BatchMapper { + + // MapStruct will generate the implementation automatically + @Mapping(source = "batch_id", target = "id") + BatchResponseDTO toDto(TestBatch entity); +} diff --git a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/repository/TestBatchRepository.java b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/repository/TestBatchRepository.java index 29fc734..4559307 100644 --- a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/repository/TestBatchRepository.java +++ b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/repository/TestBatchRepository.java @@ -2,16 +2,10 @@ import com.vsp.endpointinsightsapi.model.TestBatch; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.Optional; import java.util.UUID; - @Repository public interface TestBatchRepository extends JpaRepository { - @Query("SELECT t FROM TestBatch t where t.batch_id = :id") - Optional findById(@Param("id")UUID id); -} +} \ No newline at end of file diff --git a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/service/BatchService.java b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/service/BatchService.java index 99810fb..f2d7c43 100644 --- a/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/service/BatchService.java +++ b/endpoint-insights-api/src/main/java/com/vsp/endpointinsightsapi/service/BatchService.java @@ -1,7 +1,9 @@ package com.vsp.endpointinsightsapi.service; +import com.vsp.endpointinsightsapi.dto.BatchResponseDTO; +import com.vsp.endpointinsightsapi.exception.BatchNotFoundException; +import com.vsp.endpointinsightsapi.mapper.BatchMapper; import java.util.List; -import java.util.Optional; import java.util.UUID; import com.vsp.endpointinsightsapi.dto.BatchRequestDTO; @@ -12,23 +14,42 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.util.Objects; @Service public class BatchService { private static final Logger LOG = LoggerFactory.getLogger(BatchService.class); - - private final TestBatchRepository batchRepository; + private final TestBatchRepository testBatchRepository; + private final BatchMapper batchMapper; private final JobRepository jobRepository; - public BatchService(TestBatchRepository batchRepository, JobRepository jobRepository) { - this.batchRepository = batchRepository; - this.jobRepository = jobRepository; + public BatchService(TestBatchRepository testBatchRepository, BatchMapper batchMapper, JobRepository jobRepository) { + this.testBatchRepository = Objects.requireNonNull(testBatchRepository, "testBatchRepository must not be null"); + this.batchMapper = Objects.requireNonNull(batchMapper, "batchMapper must not be null"); + this.jobRepository = Objects.requireNonNull(jobRepository, "jobRepository must not be null"); + } + + //Get Batch — used by GET /api/batches/{id} + public BatchResponseDTO getBatchById(UUID batchId) { + TestBatch b = testBatchRepository.findById(batchId) + .orElseThrow(() -> { + LOG.debug("Batch {} not found", batchId); + return new BatchNotFoundException(batchId.toString()); + }); + return batchMapper.toDto(b); } - //GET by id — used by BatchesController and its unit test - public Optional getBatchById(UUID id) { - return batchRepository.findById(id); + //Delete Batch — used by DELETE /api/batches/{id} + public void deleteBatchById(UUID batchId) { + if (!testBatchRepository.existsById(batchId)) { + LOG.debug("Batch {} not found", batchId); + throw new BatchNotFoundException(batchId.toString()); + } + + testBatchRepository.deleteById(batchId); + LOG.info("Deleted batch {}", batchId); + } //Create Batch — used by POST /api/batches @@ -43,6 +64,6 @@ public TestBatch createBatch(BatchRequestDTO request) { } batch.setJobs(jobs); } - return batchRepository.save(batch); + return testBatchRepository.save(batch); } } diff --git a/endpoint-insights-api/src/main/resources/application.properties b/endpoint-insights-api/src/main/resources/application.properties deleted file mode 100644 index f764801..0000000 --- a/endpoint-insights-api/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -spring: - application: - name: endpoint-insights-api diff --git a/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/controller/BatchesControllerUnitTest.java b/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/controller/BatchesControllerUnitTest.java index fc2705e..bc8b01a 100644 --- a/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/controller/BatchesControllerUnitTest.java +++ b/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/controller/BatchesControllerUnitTest.java @@ -4,6 +4,8 @@ import com.vsp.endpointinsightsapi.dto.BatchRequestDTO; import com.vsp.endpointinsightsapi.model.TestBatch; import com.vsp.endpointinsightsapi.service.BatchService; +import com.vsp.endpointinsightsapi.dto.BatchResponseDTO; +import com.vsp.endpointinsightsapi.exception.BatchNotFoundException; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +21,7 @@ import java.util.UUID; import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.any; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -39,33 +42,40 @@ void shouldReturnListOfBatches() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(greaterThan(0)))) .andExpect(jsonPath("$[0].id", notNullValue())) - .andExpect(jsonPath("$[0].name", not(emptyString()))) - .andExpect(jsonPath("$[0].status", not(emptyString()))); + .andExpect(jsonPath("$[0].batchName", not(emptyString()))) + .andExpect(jsonPath("$[0].scheduleId", notNullValue())) + .andExpect(jsonPath("$[0].active", anyOf(is(true), is(false)))); } @Test void shouldReturnBatchById() throws Exception { UUID id = UUID.randomUUID(); - TestBatch batch = new TestBatch(id, null, "Example Batch", - 1L, LocalDate.now(), LocalDate.now(), true); - Mockito.when(batchService.getBatchById(any(UUID.class))) - .thenReturn(Optional.of(batch)); + BatchResponseDTO dto = BatchResponseDTO.builder() + .id(id) + .batchName("Daily API Tests") + .scheduleId(1001L) + .startTime(LocalDate.parse("2025-11-08")) + .lastTimeRun(LocalDate.parse("2025-11-09")) + .active(true) + .build(); - mockMvc.perform(get("/api/batches/" + id)) + when(batchService.getBatchById(id)).thenReturn(dto); + + mockMvc.perform(get("/api/batches/{id}", id)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.batchName", is("Example Batch"))) - .andExpect(jsonPath("$.active", is(true))); + .andExpect(jsonPath("$.id").value(id.toString())) + .andExpect(jsonPath("$.batchName").value("Daily API Tests")) + .andExpect(jsonPath("$.scheduleId").value(1001)) + .andExpect(jsonPath("$.active").value(true)); } @Test - void shouldReturnNotFoundWhenBatchMissing() throws Exception { + void getBatchById_notFound_shouldReturn404() throws Exception { UUID id = UUID.randomUUID(); + when(batchService.getBatchById(id)).thenThrow(new BatchNotFoundException(id.toString())); - Mockito.when(batchService.getBatchById(any(UUID.class))) - .thenReturn(Optional.empty()); - - mockMvc.perform(get("/api/batches/" + id)) + mockMvc.perform(get("/api/batches/{id}", id)) .andExpect(status().isNotFound()); } @@ -89,18 +99,35 @@ void shouldCreateBatch() throws Exception { void shouldUpdateBatch() throws Exception { BatchRequestDTO request = new BatchRequestDTO("Updated Batch", Collections.emptyList()); - mockMvc.perform(put("/api/batches/2") + UUID id = UUID.randomUUID(); + + mockMvc.perform(put("/api/batches/{id}", id) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is(2))) - .andExpect(jsonPath("$.name", is("Updated Batch"))) - .andExpect(jsonPath("$.status", is("UPDATED"))); + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.batchName", is("Updated Batch"))); } @Test void shouldDeleteBatch() throws Exception { - mockMvc.perform(delete("/api/batches/1")) + UUID id = UUID.randomUUID(); + + org.mockito.Mockito.doNothing() + .when(batchService).deleteBatchById(id); + + mockMvc.perform(delete("/api/batches/{id}", id)) .andExpect(status().isNoContent()); } + + @Test + void shouldReturn404WhenDeletingNonexistentBatch() throws Exception { + UUID id = UUID.randomUUID(); + + org.mockito.Mockito.doThrow(new BatchNotFoundException(id.toString())) + .when(batchService).deleteBatchById(id); + + mockMvc.perform(delete("/api/batches/{id}", id)) + .andExpect(status().isNotFound()); + } } diff --git a/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/service/BatchServiceTest.java b/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/service/BatchServiceTest.java index 64cd927..1951368 100644 --- a/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/service/BatchServiceTest.java +++ b/endpoint-insights-api/src/test/java/com/vsp/endpointinsightsapi/service/BatchServiceTest.java @@ -1,59 +1,89 @@ +// BatchServiceTest.java package com.vsp.endpointinsightsapi.service; +import com.vsp.endpointinsightsapi.dto.BatchResponseDTO; +import com.vsp.endpointinsightsapi.exception.BatchNotFoundException; +import com.vsp.endpointinsightsapi.mapper.BatchMapper; import com.vsp.endpointinsightsapi.model.TestBatch; import com.vsp.endpointinsightsapi.repository.JobRepository; import com.vsp.endpointinsightsapi.repository.TestBatchRepository; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import java.time.LocalDate; import java.util.Optional; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class BatchServiceTest { - private TestBatchRepository testBatchRepository; - private JobRepository jobRepository; - private BatchService batchService; - - @BeforeEach - void setUp() { - testBatchRepository = Mockito.mock(TestBatchRepository.class); - jobRepository = Mockito.mock(JobRepository.class); - batchService = new BatchService(testBatchRepository, jobRepository); - } + @Mock TestBatchRepository testBatchRepository; + @Mock BatchMapper batchMapper; + @InjectMocks BatchService batchService; + @Mock JobRepository jobRepository; @Test - void getBatchById_shouldReturnBatchWhenFound() { + void getBatchById_returnsDto() { UUID id = UUID.randomUUID(); - TestBatch mockBatch = new TestBatch(); - mockBatch.setBatch_id(id); - mockBatch.setBatchName("Sample Batch"); - mockBatch.setActive(true); + TestBatch entity = new TestBatch(); + // adjust getters/setters to your model + entity.setBatch_id(id); + entity.setBatchName("Example"); + entity.setScheduleId(1001L); + entity.setStartTime(LocalDate.parse("2025-11-08")); + entity.setLastTimeRun(LocalDate.parse("2025-11-09")); + entity.setActive(true); + + when(testBatchRepository.findById(id)).thenReturn(Optional.of(entity)); - when(testBatchRepository.findById(any(UUID.class))) - .thenReturn(Optional.of(mockBatch)); + BatchResponseDTO dto = BatchResponseDTO.builder() + .id(id) + .batchName("Example") + .scheduleId(1001L) + .startTime(LocalDate.parse("2025-11-08")) + .lastTimeRun(LocalDate.parse("2025-11-09")) + .active(true) + .build(); + when(batchMapper.toDto(entity)).thenReturn(dto); + + BatchResponseDTO out = batchService.getBatchById(id); + + assertEquals(id, out.getId()); + assertEquals("Example", out.getBatchName()); + verify(testBatchRepository).findById(id); + verify(batchMapper).toDto(entity); + } - Optional result = batchService.getBatchById(id); + @Test + void getBatchById_notFound_throws() { + UUID id = UUID.randomUUID(); + when(testBatchRepository.findById(id)).thenReturn(Optional.empty()); - assertThat(result).isPresent(); - assertThat(result.get().getBatch_id()).isEqualTo(id); - assertThat(result.get().getBatchName()).isEqualTo("Sample Batch"); + assertThrows(BatchNotFoundException.class, () -> batchService.getBatchById(id)); } @Test - void getBatchById_shouldReturnEmptyWhenNotFound() { + void deleteBatchById_Exists() { UUID id = UUID.randomUUID(); + when(testBatchRepository.existsById(id)).thenReturn(true); + + batchService.deleteBatchById(id); - when(testBatchRepository.findById(any(UUID.class))) - .thenReturn(Optional.empty()); + verify(testBatchRepository).deleteById(id); + } - Optional result = batchService.getBatchById(id); + @Test + void deleteBatchById_NotFound() { + UUID id = UUID.randomUUID(); + when(testBatchRepository.existsById(id)).thenReturn(false); - assertThat(result).isEmpty(); + assertThrows(BatchNotFoundException.class, () -> batchService.deleteBatchById(id)); } }