test(ai-character): 관리자 API 인가 검증을 보강한다

This commit is contained in:
2026-07-30 00:34:53 +09:00
parent 1bfb387e78
commit d86a9fb059

View File

@@ -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.SpringBootTest
import org.springframework.boot.test.context.TestComponent import org.springframework.boot.test.context.TestComponent
import org.springframework.context.annotation.Import 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.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous 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.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.ContextConfiguration
import org.springframework.test.web.servlet.MockMvc 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.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.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
@@ -99,6 +106,54 @@ class AiCharacterAdminAuthorizationTest @Autowired constructor(
.andExpect(jsonPath("$.success").value(true)) .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) = private fun createAuthentication(jwtRole: MemberRole, currentRole: MemberRole) =
UsernamePasswordAuthenticationToken( UsernamePasswordAuthenticationToken(
MemberAdapter( MemberAdapter(
@@ -113,6 +168,81 @@ class AiCharacterAdminAuthorizationTest @Autowired constructor(
listOf(SimpleGrantedAuthority("ROLE_${jwtRole.name}")) 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 @TestComponent
@RestController @RestController
@RequestMapping("/api/v2/admin/ai-characters/sample") @RequestMapping("/api/v2/admin/ai-characters/sample")