Merge commit 'e38e2cdea912160b183c65398c4e8d5682c1b00e' into work/reports-ui
# Conflicts: # src/main/resources/templates/tasks/form.html
This commit is contained in:
@@ -16,6 +16,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
|
||||
|
||||
import com.lab.labtimesheet.feature.task.exception.TaskNotFoundException;
|
||||
import com.lab.labtimesheet.feature.task.exception.TaskValidationException;
|
||||
import com.lab.labtimesheet.feature.task.model.TaskProgress;
|
||||
import com.lab.labtimesheet.feature.task.model.TaskStatus;
|
||||
import com.lab.labtimesheet.feature.task.model.dto.CreateTaskCommand;
|
||||
@@ -28,7 +29,11 @@ import com.lab.labtimesheet.feature.task.service.TaskService;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
@@ -125,6 +130,46 @@ class TaskControllerTest {
|
||||
.create(org.mockito.ArgumentMatchers.eq(ACTOR_EMAIL), any(CreateTaskCommand.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidDueDateRendersFieldErrorAndRetainsSafeInput() throws Exception {
|
||||
TaskAssigneeChoice assignee = new TaskAssigneeChoice(7L, "Member Name");
|
||||
given(taskService.create(org.mockito.ArgumentMatchers.eq(ACTOR_EMAIL), any(CreateTaskCommand.class)))
|
||||
.willThrow(new TaskValidationException("Due date must fall within the Project dates"));
|
||||
given(taskService.assignmentChoices(ACTOR_EMAIL, 10L)).willReturn(List.of(assignee));
|
||||
|
||||
mockMvc.perform(post("/projects/10/tasks")
|
||||
.with(user(ACTOR_EMAIL))
|
||||
.with(csrf())
|
||||
.param("title", "Draft")
|
||||
.param("description", "Safe notes")
|
||||
.param("assigneeMembershipId", "7")
|
||||
.param("dueDate", "2026-09-01"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("tasks/form"))
|
||||
.andExpect(model().attributeHasFieldErrors("taskForm", "dueDate"))
|
||||
.andExpect(model().attribute("assignees", List.of(assignee)))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("Draft")))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("Safe notes")))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("2026-09-01")))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString(
|
||||
"Due date must fall within the Project dates")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void guessedProjectDuringCreateRemainsNotFound() throws Exception {
|
||||
given(taskService.create(org.mockito.ArgumentMatchers.eq(ACTOR_EMAIL), any(CreateTaskCommand.class)))
|
||||
.willThrow(new TaskNotFoundException());
|
||||
|
||||
mockMvc.perform(post("/projects/999/tasks")
|
||||
.with(user(ACTOR_EMAIL))
|
||||
.with(csrf())
|
||||
.param("title", "Draft")
|
||||
.param("assigneeMembershipId", "7"))
|
||||
.andExpect(status().isNotFound());
|
||||
|
||||
verify(taskService, org.mockito.Mockito.never()).assignmentChoices(ACTOR_EMAIL, 999L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void statusAndCommentPostsUseAuthenticatedIdentityAndCsrf() throws Exception {
|
||||
given(taskService.changeStatus(ACTOR_EMAIL, 10L, 25L, TaskStatus.IN_PROGRESS))
|
||||
@@ -171,10 +216,33 @@ class TaskControllerTest {
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("Add comment")));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} exposes only {1}")
|
||||
@MethodSource("allowedStatusChoices")
|
||||
void taskDetailsExposeOnlyAllowedStatusTransitions(TaskStatus current, List<TaskStatus> expected) throws Exception {
|
||||
given(taskService.details(ACTOR_EMAIL, 10L, 25L))
|
||||
.willReturn(new TaskDetails(task(25L, current), List.of(), true, true));
|
||||
|
||||
mockMvc.perform(get("/projects/10/tasks/25").with(user(ACTOR_EMAIL)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attribute("statuses", expected));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> allowedStatusChoices() {
|
||||
return Stream.of(
|
||||
Arguments.of(TaskStatus.TODO, List.of(TaskStatus.IN_PROGRESS, TaskStatus.BLOCKED)),
|
||||
Arguments.of(TaskStatus.IN_PROGRESS, List.of(TaskStatus.BLOCKED, TaskStatus.DONE)),
|
||||
Arguments.of(TaskStatus.BLOCKED, List.of(TaskStatus.TODO, TaskStatus.IN_PROGRESS)),
|
||||
Arguments.of(TaskStatus.DONE, List.of(TaskStatus.IN_PROGRESS)));
|
||||
}
|
||||
|
||||
private static TaskView task(long id) {
|
||||
return task(id, TaskStatus.TODO);
|
||||
}
|
||||
|
||||
private static TaskView task(long id, TaskStatus status) {
|
||||
Instant instant = Instant.parse("2026-08-14T10:00:00Z");
|
||||
return new TaskView(
|
||||
id, 10L, 7L, "Member Name", "Draft", "Notes", TaskStatus.TODO,
|
||||
id, 10L, 7L, "Member Name", "Draft", "Notes", status,
|
||||
LocalDate.of(2026, 8, 20), 7L, 7L, instant, instant);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user