diff --git a/.forge/role-developer-live-v3-001-attempt-1-run-2d01a4fe6309.md b/.forge/role-developer-live-v3-001-attempt-1-run-2d01a4fe6309.md new file mode 100644 index 0000000..239b8ea --- /dev/null +++ b/.forge/role-developer-live-v3-001-attempt-1-run-2d01a4fe6309.md @@ -0,0 +1,3 @@ +# role-developer-live-v3-001-attempt-1-run-2d01a4fe6309 + +Forge 이슈 작업 브랜치 `forge/role-developer-live-v3-001-attempt-1-run-2d01a4fe6309`. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a83fd16 --- /dev/null +++ b/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + com.example + runtime-role-matrix-live-202607141522-v3 + 1.0.0 + jar + Runtime Role Matrix Live + Developer role Spring Boot skeleton application + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/src/main/java/com/example/demo/DeveloperApplication.java b/src/main/java/com/example/demo/DeveloperApplication.java new file mode 100644 index 0000000..68daf7a --- /dev/null +++ b/src/main/java/com/example/demo/DeveloperApplication.java @@ -0,0 +1,12 @@ +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DeveloperApplication { + + public static void main(String[] args) { + SpringApplication.run(DeveloperApplication.class, args); + } +} diff --git a/src/main/java/com/example/demo/controller/DeveloperController.java b/src/main/java/com/example/demo/controller/DeveloperController.java new file mode 100644 index 0000000..6cb5a3f --- /dev/null +++ b/src/main/java/com/example/demo/controller/DeveloperController.java @@ -0,0 +1,37 @@ +package com.example.demo.controller; + +import com.example.demo.service.DeveloperService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/roles") +public class DeveloperController { + + private final DeveloperService developerService; + + public DeveloperController(DeveloperService developerService) { + this.developerService = developerService; + } + + @GetMapping + public ResponseEntity> getAllRoles() { + return ResponseEntity.ok(developerService.getAllRoles()); + } + + @GetMapping("/{role}") + public ResponseEntity> getRolePermission(@PathVariable String role) { + String permission = developerService.getRolePermission(role); + return ResponseEntity.ok(Map.of("role", role, "permission", permission)); + } + + @GetMapping("/{role}/check") + public ResponseEntity> checkPermission( + @PathVariable String role, + @RequestParam String action) { + boolean allowed = developerService.hasPermission(role, action); + return ResponseEntity.ok(Map.of("action", action, "allowed", allowed)); + } +} diff --git a/src/main/java/com/example/demo/service/DeveloperService.java b/src/main/java/com/example/demo/service/DeveloperService.java new file mode 100644 index 0000000..8fde18e --- /dev/null +++ b/src/main/java/com/example/demo/service/DeveloperService.java @@ -0,0 +1,37 @@ +package com.example.demo.service; + +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +@Service +public class DeveloperService { + + private final Map roleMatrix = new HashMap<>(); + + public DeveloperService() { + roleMatrix.put("developer", "read-write"); + roleMatrix.put("viewer", "read-only"); + roleMatrix.put("admin", "full-access"); + } + + public String getRolePermission(String role) { + return roleMatrix.getOrDefault(role.toLowerCase(), "no-access"); + } + + public List getAllRoles() { + return List.copyOf(roleMatrix.keySet()); + } + + public boolean hasPermission(String role, String requiredPermission) { + String permission = getRolePermission(role); + if ("full-access".equals(permission)) { + return true; + } + if ("read-write".equals(permission)) { + return !"delete".equals(requiredPermission); + } + return "read".equals(requiredPermission); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..29cb1c3 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=runtime-role-matrix-live-202607141522-v3 +server.port=8080 diff --git a/src/test/java/com/example/demo/DeveloperApplicationTests.java b/src/test/java/com/example/demo/DeveloperApplicationTests.java new file mode 100644 index 0000000..7987d88 --- /dev/null +++ b/src/test/java/com/example/demo/DeveloperApplicationTests.java @@ -0,0 +1,14 @@ +package com.example.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true"}) +class DeveloperApplicationTests { + + @Test + void contextLoads() { + } +} diff --git a/src/test/java/com/example/demo/controller/DeveloperControllerTest.java b/src/test/java/com/example/demo/controller/DeveloperControllerTest.java new file mode 100644 index 0000000..2a2174e --- /dev/null +++ b/src/test/java/com/example/demo/controller/DeveloperControllerTest.java @@ -0,0 +1,65 @@ +package com.example.demo.controller; + +import com.example.demo.service.DeveloperService; +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.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(DeveloperController.class) +class DeveloperControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private DeveloperService developerService; + + @Test + void getAllRoles_returnsRoleList() throws Exception { + when(developerService.getAllRoles()).thenReturn(List.of("developer", "viewer", "admin")); + + mockMvc.perform(get("/api/roles")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(3)) + .andExpect(jsonPath("$[0]").value("developer")); + } + + @Test + void getRolePermission_returnsPermission() throws Exception { + when(developerService.getRolePermission("developer")).thenReturn("read-write"); + + mockMvc.perform(get("/api/roles/developer")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.role").value("developer")) + .andExpect(jsonPath("$.permission").value("read-write")); + } + + @Test + void checkPermission_allowed_returnsTrue() throws Exception { + when(developerService.hasPermission("developer", "read")).thenReturn(true); + + mockMvc.perform(get("/api/roles/developer/check").param("action", "read")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.action").value("read")) + .andExpect(jsonPath("$.allowed").value(true)); + } + + @Test + void checkPermission_denied_returnsFalse() throws Exception { + when(developerService.hasPermission("developer", "delete")).thenReturn(false); + + mockMvc.perform(get("/api/roles/developer/check").param("action", "delete")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.action").value("delete")) + .andExpect(jsonPath("$.allowed").value(false)); + } +} diff --git a/src/test/java/com/example/demo/service/DeveloperServiceTest.java b/src/test/java/com/example/demo/service/DeveloperServiceTest.java new file mode 100644 index 0000000..59a4637 --- /dev/null +++ b/src/test/java/com/example/demo/service/DeveloperServiceTest.java @@ -0,0 +1,88 @@ +package com.example.demo.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class DeveloperServiceTest { + + private DeveloperService developerService; + + @BeforeEach + void setUp() { + developerService = new DeveloperService(); + } + + @Test + @DisplayName("Developer role should have read-write permission") + void getRolePermission_developer_returnsReadWrite() { + String permission = developerService.getRolePermission("developer"); + assertEquals("read-write", permission); + } + + @Test + @DisplayName("Viewer role should have read-only permission") + void getRolePermission_viewer_returnsReadOnly() { + String permission = developerService.getRolePermission("viewer"); + assertEquals("read-only", permission); + } + + @Test + @DisplayName("Admin role should have full-access permission") + void getRolePermission_admin_returnsFullAccess() { + String permission = developerService.getRolePermission("admin"); + assertEquals("full-access", permission); + } + + @Test + @DisplayName("Unknown role should return no-access") + void getRolePermission_unknown_returnsNoAccess() { + String permission = developerService.getRolePermission("unknown"); + assertEquals("no-access", permission); + } + + @Test + @DisplayName("Role lookup should be case-insensitive") + void getRolePermission_caseInsensitive() { + assertEquals("read-write", developerService.getRolePermission("DEVELOPER")); + assertEquals("read-write", developerService.getRolePermission("Developer")); + } + + @Test + @DisplayName("getAllRoles should return all defined roles") + void getAllRoles_returnsAllRoles() { + List roles = developerService.getAllRoles(); + assertEquals(3, roles.size()); + assertTrue(roles.contains("developer")); + assertTrue(roles.contains("viewer")); + assertTrue(roles.contains("admin")); + } + + @Test + @DisplayName("Admin should have all permissions") + void hasPermission_admin_hasAllPermissions() { + assertTrue(developerService.hasPermission("admin", "read")); + assertTrue(developerService.hasPermission("admin", "write")); + assertTrue(developerService.hasPermission("admin", "delete")); + } + + @Test + @DisplayName("Developer should have read and write but not delete") + void hasPermission_developer_readWriteNotDelete() { + assertTrue(developerService.hasPermission("developer", "read")); + assertTrue(developerService.hasPermission("developer", "write")); + assertFalse(developerService.hasPermission("developer", "delete")); + } + + @Test + @DisplayName("Viewer should only have read permission") + void hasPermission_viewer_onlyRead() { + assertTrue(developerService.hasPermission("viewer", "read")); + assertFalse(developerService.hasPermission("viewer", "write")); + assertFalse(developerService.hasPermission("viewer", "delete")); + } +}