package com.dalab.autodelete.controller; import com.dalab.autodelete.dto.*; import com.dalab.autodelete.service.IDeletionTaskService; import com.dalab.autodelete.service.exception.DeletionTaskNotFoundException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(DeletionTaskController.class) class DeletionTaskControllerTest { @Autowired private MockMvc mockMvc; @MockBean private IDeletionTaskService taskService; @Autowired private ObjectMapper objectMapper; private DeletionTaskRequest sampleTaskRequest; private DeletionTaskResponse sampleTaskResponse; private DeletionTaskStatusDTO sampleTaskStatusDTO; private String sampleTaskId; @BeforeEach void setUp() { sampleTaskId = UUID.randomUUID().toString(); DeletionTaskRequest.DeletionScope scope = DeletionTaskRequest.DeletionScope.builder() .assetIds(List.of("asset1", "asset2")) .build(); sampleTaskRequest = DeletionTaskRequest.builder() .taskName("Test Deletion Task") .scope(scope) .build(); sampleTaskResponse = DeletionTaskResponse.builder() .taskId(sampleTaskId) .taskName("Test Deletion Task") .status("SUBMITTED") .submittedAt(LocalDateTime.now()) .build(); sampleTaskStatusDTO = DeletionTaskStatusDTO.builder() .taskId(sampleTaskId) .taskName("Test Deletion Task") .status("SUBMITTED") .submittedAt(LocalDateTime.now()) .scope(scope) // Assuming DeletionTaskRequest.DeletionScope is compatible or mapped .totalAssetsInScope(2) .build(); } @Test @WithMockUser(authorities = "ROLE_ADMIN") void submitDeletionTask_AsAdmin_ValidRequest_ShouldReturnAccepted() throws Exception { when(taskService.submitDeletionTask(any(DeletionTaskRequest.class))).thenReturn(sampleTaskResponse); mockMvc.perform(post("/api/v1/deletion/tasks") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(sampleTaskRequest))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$.taskId").value(sampleTaskId)) .andExpect(jsonPath("$.status").value("SUBMITTED")); } @Test @WithMockUser(authorities = "ROLE_ADMIN") void submitDeletionTask_InvalidScope_ShouldReturnBadRequest() throws Exception { DeletionTaskRequest invalidRequest = DeletionTaskRequest.builder().taskName("Invalid Task").scope(null).build(); // Service returns a specific response for validation failure DeletionTaskResponse validationFailedResponse = DeletionTaskResponse.builder() .taskId(null) .taskName(invalidRequest.getTaskName()) .status("FAILED_VALIDATION") .message("Scope is null or empty") .submittedAt(LocalDateTime.now()) .build(); when(taskService.submitDeletionTask(any(DeletionTaskRequest.class))).thenReturn(validationFailedResponse); mockMvc.perform(post("/api/v1/deletion/tasks") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(invalidRequest))) .andExpect(status().isBadRequest()) // Controller logic changes status for FAILED_VALIDATION .andExpect(jsonPath("$.status").value("FAILED_VALIDATION")); } @Test @WithMockUser(authorities = "ROLE_USER") // Assuming USER can get status of their own tasks (logic in service) void getDeletionTaskStatus_WhenTaskExists_ShouldReturnStatus() throws Exception { when(taskService.getTaskStatus(sampleTaskId)).thenReturn(sampleTaskStatusDTO); mockMvc.perform(get("/api/v1/deletion/tasks/{taskId}", sampleTaskId)) .andExpect(status().isOk()) .andExpect(jsonPath("$.taskId").value(sampleTaskId)) .andExpect(jsonPath("$.status").value("SUBMITTED")); } @Test @WithMockUser(authorities = "ROLE_USER") void getDeletionTaskStatus_WhenTaskNotExists_ShouldReturnNotFound() throws Exception { when(taskService.getTaskStatus(sampleTaskId)).thenReturn(null); // Service returns null for not found mockMvc.perform(get("/api/v1/deletion/tasks/{taskId}", sampleTaskId)) .andExpect(status().isNotFound()); } @Test @WithMockUser(authorities = "ROLE_DATA_STEWARD") void listDeletionTasks_ShouldReturnPageOfTasks() throws Exception { List taskList = Collections.singletonList(sampleTaskStatusDTO); PageImpl page = new PageImpl<>(taskList, PageRequest.of(0, 20), 1); DeletionTaskListResponse listResponse = DeletionTaskListResponse.fromPage(page); when(taskService.listTasks(any(Pageable.class))).thenReturn(listResponse); mockMvc.perform(get("/api/v1/deletion/tasks").param("page", "0").param("size", "20")) .andExpect(status().isOk()) .andExpect(jsonPath("$.tasks[0].taskId").value(sampleTaskId)); } @Test @WithMockUser(authorities = "ROLE_ADMIN") void approveDeletionTask_AsAdmin_WhenTaskApprovable_ShouldReturnOk() throws Exception { DeletionTaskStatusDTO approvedStatus = DeletionTaskStatusDTO.builder().taskId(sampleTaskId).status("APPROVED").build(); when(taskService.approveTask(eq(sampleTaskId), any())).thenReturn(approvedStatus); mockMvc.perform(post("/api/v1/deletion/tasks/{taskId}/approve", sampleTaskId) .with(csrf())) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("APPROVED")); } @Test @WithMockUser(authorities = "ROLE_ADMIN") void approveDeletionTask_TaskNotApprovable_ShouldReturnConflict() throws Exception { DeletionTaskStatusDTO conflictStatus = DeletionTaskStatusDTO.builder() .taskId(sampleTaskId) .status("COMPLETED") // Example of a non-approvable state .errorMessages(List.of("Task not in approvable state")) .build(); when(taskService.approveTask(eq(sampleTaskId), any())).thenReturn(conflictStatus); mockMvc.perform(post("/api/v1/deletion/tasks/{taskId}/approve", sampleTaskId) .with(csrf())) .andExpect(status().isConflict()) // Controller maps this from DTO having errorMessages .andExpect(jsonPath("$.status").value("COMPLETED")); } @Test @WithMockUser(authorities = "ROLE_DATA_STEWARD") void rejectDeletionTask_AsDataSteward_WhenTaskRejectable_ShouldReturnOk() throws Exception { DeletionTaskStatusDTO rejectedStatus = DeletionTaskStatusDTO.builder().taskId(sampleTaskId).status("REJECTED").build(); when(taskService.rejectTask(eq(sampleTaskId), any())).thenReturn(rejectedStatus); mockMvc.perform(post("/api/v1/deletion/tasks/{taskId}/reject", sampleTaskId) .with(csrf())) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("REJECTED")); } }