From 2f2573159c34833b2e70a6c6a07f433da308af7b Mon Sep 17 00:00:00 2001 From: sechmachine <97589681+sechmachine727@users.noreply.github.com> Date: Sat, 15 Aug 2026 00:38:03 +0700 Subject: [PATCH] fix(project): hide unavailable create action --- docs/tests/web/projects-pages.md | 14 ++++++++++++- .../project/controller/ProjectController.java | 4 +++- .../resources/templates/projects/list.html | 2 +- .../controller/ProjectControllerTest.java | 20 +++++++++++++++++-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/docs/tests/web/projects-pages.md b/docs/tests/web/projects-pages.md index df25b1f..905adb2 100644 --- a/docs/tests/web/projects-pages.md +++ b/docs/tests/web/projects-pages.md @@ -4,7 +4,7 @@ - **Requirement IDs:** `AUTH-001`, `AUTH-002`, `AUTH-006`, `PRJ-001`, `PRJ-004`–`PRJ-006`, `SEC-001`, `ERR-001` - **Scenario IDs:** `AC-AUTH-001`, `AC-AUTH-002`, `AC-AUTH-007`, `I1-PRJ-05` - **Test class/method:** `com.lab.labtimesheet.feature.project.controller.ProjectControllerTest` -- **Implementation commit:** `25a855e` +- **Implementation commits:** `25a855e`, `a9ee99a` ## Protected behavior @@ -68,6 +68,18 @@ export DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock [INFO] BUILD SUCCESS ``` +## Mentor-only control regression + +**RED:** the focused MockMvc run reported two expected failures: `GET /projects/new` returned `200` for an Intern instead of non-disclosing `404`, and the member page rendered the `Add member` form for a non-owner. + +**GREEN:** rerunning `./mvnw -Dtest=ProjectControllerTest test` after the controller/DTO/template correction passed 7 tests with zero failures, errors, or skips. + +## Role-aware Project-list action regression + +**RED:** the focused MockMvc run reported two expected failures after adding the list-action regression: the controller still resolved only a user ID, so the Mentor fixture was queried as user `0`, and an Intern-facing Project list rendered the `Create Project` link. + +**GREEN:** rerunning `./mvnw -Dtest=ProjectControllerTest test` after resolving the public actor view and conditionally rendering the link passed 8 tests with zero failures, errors, or skips. + ## External-test boundaries This slice does not prove PostgreSQL query correctness, a real login flow, shared-shell navigation, browser accessibility, or Iteration 2 invitation/exit/completion pages. The activation route remains deferred with `I1-PRJ-04` until the Task feature query dependency is available. diff --git a/src/main/java/com/lab/labtimesheet/feature/project/controller/ProjectController.java b/src/main/java/com/lab/labtimesheet/feature/project/controller/ProjectController.java index 79cfd10..113b50a 100644 --- a/src/main/java/com/lab/labtimesheet/feature/project/controller/ProjectController.java +++ b/src/main/java/com/lab/labtimesheet/feature/project/controller/ProjectController.java @@ -30,7 +30,9 @@ public class ProjectController { @GetMapping public String list(Principal principal, Model model) { - model.addAttribute("projects", pages.listVisible(actorId(principal))); + var actor = pages.authenticatedActor(principal.getName()); + model.addAttribute("projects", pages.listVisible(actor.userId())); + model.addAttribute("canCreateProject", "MENTOR".equals(actor.role())); return "projects/list"; } diff --git a/src/main/resources/templates/projects/list.html b/src/main/resources/templates/projects/list.html index 9379ac8..67ec7ac 100644 --- a/src/main/resources/templates/projects/list.html +++ b/src/main/resources/templates/projects/list.html @@ -4,7 +4,7 @@

Projects

- Create Project + Create Project

No authorized Projects.

diff --git a/src/test/java/com/lab/labtimesheet/feature/project/controller/ProjectControllerTest.java b/src/test/java/com/lab/labtimesheet/feature/project/controller/ProjectControllerTest.java index 4a13cec..2a26916 100644 --- a/src/test/java/com/lab/labtimesheet/feature/project/controller/ProjectControllerTest.java +++ b/src/test/java/com/lab/labtimesheet/feature/project/controller/ProjectControllerTest.java @@ -44,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", @@ -55,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 {
Authorized Projects