diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt index 0135a8bc..10c4e00d 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt @@ -12,6 +12,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestComponent import org.springframework.context.annotation.Import +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous @@ -19,7 +21,12 @@ import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequ import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.web.bind.annotation.GetMapping @@ -99,6 +106,54 @@ class AiCharacterAdminAuthorizationTest @Autowired constructor( .andExpect(jsonPath("$.success").value(true)) } + @Test + @DisplayName("시리즈 endpoint는 JWT role과 현재 DB role이 모두 ADMIN이어야 접근할 수 있다") + fun shouldRequireAdminJwtAndCurrentAdminRoleForSeriesEndpoints() { + seriesEndpointRequests().forEach { endpoint -> + mockMvc.perform( + endpoint.request() + .with(authentication(createAuthentication(MemberRole.USER, MemberRole.ADMIN))) + ).andExpect(status().isForbidden) + + mockMvc.perform( + endpoint.request() + .with(authentication(createAuthentication(MemberRole.ADMIN, MemberRole.USER))) + ).andExpect(status().isForbidden) + } + } + + @Test + @DisplayName("커뮤니티 endpoint는 JWT role과 현재 DB role이 모두 ADMIN이어야 접근할 수 있다") + fun shouldRequireAdminJwtAndCurrentAdminRoleForCommunityEndpoints() { + communityEndpointRequests().forEach { endpoint -> + mockMvc.perform( + endpoint.request() + .with(authentication(createAuthentication(MemberRole.USER, MemberRole.ADMIN))) + ).andExpect(status().isForbidden) + + mockMvc.perform( + endpoint.request() + .with(authentication(createAuthentication(MemberRole.ADMIN, MemberRole.USER))) + ).andExpect(status().isForbidden) + } + } + + @Test + @DisplayName("FanTalk endpoint는 JWT role과 현재 DB role이 모두 ADMIN이어야 접근할 수 있다") + fun shouldRequireAdminJwtAndCurrentAdminRoleForFanTalkEndpoints() { + fanTalkEndpointRequests().forEach { endpoint -> + mockMvc.perform( + endpoint.request() + .with(authentication(createAuthentication(MemberRole.USER, MemberRole.ADMIN))) + ).andExpect(status().isForbidden) + + mockMvc.perform( + endpoint.request() + .with(authentication(createAuthentication(MemberRole.ADMIN, MemberRole.USER))) + ).andExpect(status().isForbidden) + } + } + private fun createAuthentication(jwtRole: MemberRole, currentRole: MemberRole) = UsernamePasswordAuthenticationToken( MemberAdapter( @@ -113,6 +168,81 @@ class AiCharacterAdminAuthorizationTest @Autowired constructor( listOf(SimpleGrantedAuthority("ROLE_${jwtRole.name}")) ) + private fun seriesEndpointRequests() = listOf( + SeriesEndpointRequest("series list") { + get("/api/v2/admin/ai-characters/1/series") + }, + SeriesEndpointRequest("series detail") { + get("/api/v2/admin/ai-characters/1/series/2") + }, + SeriesEndpointRequest("series contents") { + get("/api/v2/admin/ai-characters/1/series/2/contents") + }, + SeriesEndpointRequest("series content search") { + get("/api/v2/admin/ai-characters/1/series/2/contents/search") + .param("search_word", "night") + }, + SeriesEndpointRequest("series content add") { + post("/api/v2/admin/ai-characters/1/series/2/contents") + .contentType(MediaType.APPLICATION_JSON) + .content("""{"contentIdList":[3]}""") + }, + SeriesEndpointRequest("series content remove") { + delete("/api/v2/admin/ai-characters/1/series/2/contents/3") + }, + SeriesEndpointRequest("series order") { + put("/api/v2/admin/ai-characters/1/series/orders") + .contentType(MediaType.APPLICATION_JSON) + .content("""{"ids":[2,3]}""") + }, + SeriesEndpointRequest("series create") { + multipart("/api/v2/admin/ai-characters/1/series") + }, + SeriesEndpointRequest("series update") { + multipart(HttpMethod.PUT, "/api/v2/admin/ai-characters/1/series/2") + }, + SeriesEndpointRequest("series delete") { + delete("/api/v2/admin/ai-characters/1/series/2") + } + ) + + private fun communityEndpointRequests() = listOf( + SeriesEndpointRequest("community list") { + get("/api/v2/admin/ai-characters/1/community-posts") + .param("timezone", "UTC") + }, + SeriesEndpointRequest("community create") { + multipart("/api/v2/admin/ai-characters/1/community-posts") + }, + SeriesEndpointRequest("community update") { + multipart(HttpMethod.PUT, "/api/v2/admin/ai-characters/1/community-posts/2") + } + ) + + private fun fanTalkEndpointRequests() = listOf( + SeriesEndpointRequest("FanTalk list") { + get("/api/v2/admin/ai-characters/1/fan-talks") + }, + SeriesEndpointRequest("FanTalk reply") { + post("/api/v2/admin/ai-characters/1/fan-talks/2/replies") + .contentType(MediaType.APPLICATION_JSON) + .content("""{"content":"reply"}""") + }, + SeriesEndpointRequest("FanTalk reply update") { + put("/api/v2/admin/ai-characters/1/fan-talks/2/replies/3") + .contentType(MediaType.APPLICATION_JSON) + .content("""{"content":"reply","isActive":true}""") + }, + SeriesEndpointRequest("FanTalk delete") { + delete("/api/v2/admin/ai-characters/1/fan-talks/2") + } + ) + + private data class SeriesEndpointRequest( + val name: String, + val request: () -> MockHttpServletRequestBuilder + ) + @TestComponent @RestController @RequestMapping("/api/v2/admin/ai-characters/sample")