Merge commit 'c3f92d4e310d625d95d374e974c478444331f6d7' into work/tasks

This commit is contained in:
sechmachine
2026-08-15 01:06:20 +07:00
15 changed files with 454 additions and 22 deletions
@@ -3,6 +3,8 @@ package com.lab.labtimesheet.feature.project.controller;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -13,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import com.lab.labtimesheet.feature.project.exception.ProjectAccessDeniedException;
import com.lab.labtimesheet.feature.project.model.dto.ProjectCreateCommand;
import com.lab.labtimesheet.feature.project.model.dto.ProjectActorView;
import com.lab.labtimesheet.feature.project.model.dto.ProjectDetail;
import com.lab.labtimesheet.feature.project.model.dto.ProjectSummary;
import com.lab.labtimesheet.feature.project.service.ProjectQueryService;
@@ -41,7 +44,8 @@ class ProjectControllerTest {
@Test
@WithMockUser(username = "mentor@example.test")
void listsOnlyTheAuthenticatedUsersAuthorizedProjects() throws Exception {
when(pages.authenticatedUserId("mentor@example.test")).thenReturn(10L);
when(pages.authenticatedActor("mentor@example.test"))
.thenReturn(new ProjectActorView(10L, "MENTOR"));
when(pages.listVisible(10L)).thenReturn(List.of(new ProjectSummary(
30L,
"Intern Portal Refresh",
@@ -52,11 +56,26 @@ class ProjectControllerTest {
mvc.perform(get("/projects"))
.andExpect(status().isOk())
.andExpect(view().name("projects/list"))
.andExpect(model().attributeExists("projects"));
.andExpect(model().attributeExists("projects"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.content()
.string(containsString("Create Project")));
verify(pages).listVisible(10L);
}
@Test
@WithMockUser(username = "member@example.test")
void nonMentorProjectListOmitsTheCreateLink() throws Exception {
when(pages.authenticatedActor("member@example.test"))
.thenReturn(new ProjectActorView(20L, "INTERN"));
when(pages.listVisible(20L)).thenReturn(List.of());
mvc.perform(get("/projects"))
.andExpect(status().isOk())
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.content()
.string(not(containsString("Create Project"))));
}
@Test
@WithMockUser(username = "member@example.test")
void guessedProjectIdReturnsTheSameNotFoundResponseAsAMissingProject() throws Exception {
@@ -79,16 +98,31 @@ class ProjectControllerTest {
LocalDate.of(2026, 8, 15),
LocalDate.of(2026, 9, 30),
"Mentor",
"Leader"));
"Leader",
false));
when(pages.members(20L, 30L)).thenReturn(List.of());
when(pages.leadership(20L, 30L)).thenReturn(List.of());
mvc.perform(get("/projects/30/members"))
.andExpect(status().isOk())
.andExpect(view().name("projects/members"));
.andExpect(view().name("projects/members"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.content()
.string(not(containsString("Add member"))));
mvc.perform(get("/projects/30/leadership"))
.andExpect(status().isOk())
.andExpect(view().name("projects/leadership"));
.andExpect(view().name("projects/leadership"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.content()
.string(not(containsString("Change Leader"))));
}
@Test
@WithMockUser(username = "member@example.test")
void nonMentorCannotOpenProjectCreationForm() throws Exception {
when(pages.authenticatedActor("member@example.test"))
.thenReturn(new ProjectActorView(20L, "INTERN"));
mvc.perform(get("/projects/new"))
.andExpect(status().isNotFound());
}
@Test
@@ -144,11 +144,14 @@ class ProjectServiceIntegrationTest {
assertEquals(List.of(), projectPages.listVisible(unrelatedId));
assertEquals(projectId, projectPages.detail(memberId, projectId).id());
assertEquals("INTERN", projectPages.authenticatedActor("member-view@example.test").role());
var taskContext = projectPages.taskContext(memberId, projectId);
var taskContext = projectService.taskMutationContext(memberId, projectId);
assertEquals(mentorId, taskContext.mentorUserId());
assertEquals("PLANNED", taskContext.status());
assertEquals(2, taskContext.activeMembers().size());
assertEquals(membershipId(projectId, leaderId), taskContext.currentLeaderMembershipId());
assertEquals(taskContext, projectPages.taskContext(memberId, projectId));
assertThrows(ProjectAccessDeniedException.class,
() -> projectService.taskMutationContext(otherMentorId, projectId));
jdbc.update("""
update projects set status = 'ACTIVE', activated_at = ?, updated_at = ? where id = ?
""", dbTime(NOW.plusSeconds(30)), dbTime(NOW.plusSeconds(30)), projectId);
@@ -169,7 +172,7 @@ class ProjectServiceIntegrationTest {
entityManager.clear();
assertEquals(projectId, projectPages.detail(memberId, projectId).id());
assertTrue(projectPages.taskContext(memberId, projectId).activeMembers().stream()
assertTrue(projectService.taskMutationContext(memberId, projectId).activeMembers().stream()
.noneMatch(member -> member.userId() == memberId));
assertEquals(0, projectPages.dashboardSummary(memberId).activeProjectCount());
assertEquals(1, projectPages.dashboardSummary(mentorId).distinctActiveMemberCount());
@@ -0,0 +1,59 @@
package com.lab.labtimesheet.feature.project.service;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.lab.labtimesheet.feature.account.service.AccountService;
import com.lab.labtimesheet.feature.project.model.dto.ProjectTaskContext;
import com.lab.labtimesheet.feature.project.model.entity.ProjectEntity;
import com.lab.labtimesheet.feature.project.repository.ProjectRepository;
import java.time.Clock;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class ProjectTaskMutationContextTest {
@Mock
private ProjectRepository projects;
@Mock
private AccountService accounts;
@Mock
private ProjectQueryService queries;
@Mock
private ProjectEntity project;
@Test
void loadsTheProjectForUpdateBeforeBuildingTheTaskMutationContext() {
long actorUserId = 20L;
long projectId = 30L;
var expected = new ProjectTaskContext(
projectId,
10L,
"ACTIVE",
LocalDate.of(2026, 8, 15),
LocalDate.of(2026, 9, 30),
40L,
List.of());
var service = new ProjectService(projects, accounts, queries, Clock.systemUTC());
when(projects.findLockedById(projectId)).thenReturn(Optional.of(project));
when(queries.taskContext(actorUserId, project)).thenReturn(expected);
var actual = service.taskMutationContext(actorUserId, projectId);
assertSame(expected, actual);
verify(projects).findLockedById(projectId);
verify(projects, never()).findById(projectId);
verify(queries).taskContext(actorUserId, project);
}
}