feat(auth): 본인인증 요청의 콘텐츠 선호값을 반영한다

This commit is contained in:
2026-10-12 13:28:14 +09:00
parent 6115792dcd
commit ed3d2ff320
3 changed files with 103 additions and 10 deletions
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.member.auth
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.useraction.ActionType import kr.co.vividnext.sodalive.useraction.ActionType
@@ -34,7 +35,11 @@ class AuthController(
} }
val authResponse = service.authenticate(authenticateData, member.id!!) val authResponse = service.authenticate(authenticateData, member.id!!)
memberContentPreferenceService.markAdultVisibleAfterAuthVerify(member.id!!) memberContentPreferenceService.markAdultVisibleAfterAuthVerify(
memberId = member.id!!,
isAdultContentVisible = request.isAdultContentVisible ?: true,
contentType = request.contentType ?: ContentType.ALL
)
try { try {
userActionService.recordAction( userActionService.recordAction(
@@ -1,12 +1,17 @@
package kr.co.vividnext.sodalive.member.auth package kr.co.vividnext.sodalive.member.auth
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import kr.co.vividnext.sodalive.content.ContentType
data class AuthVerifyRequest( data class AuthVerifyRequest(
@JsonProperty("receipt_id") @JsonProperty("receipt_id")
val receiptId: String, val receiptId: String,
@JsonProperty("version") @JsonProperty("version")
val version: String? val version: String?,
@JsonProperty("isAdultContentVisible")
val isAdultContentVisible: Boolean? = null,
@JsonProperty("contentType")
val contentType: ContentType? = null
) )
data class AuthVerifyCertificate( data class AuthVerifyCertificate(
@@ -1,14 +1,23 @@
package kr.co.vividnext.sodalive.member.auth package kr.co.vividnext.sodalive.member.auth
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.useraction.ActionType import kr.co.vividnext.sodalive.useraction.ActionType
import kr.co.vividnext.sodalive.useraction.UserActionService import kr.co.vividnext.sodalive.useraction.UserActionService
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertSame
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.Mockito import org.mockito.Mockito
class AuthControllerTest { class AuthControllerTest {
@@ -31,7 +40,7 @@ class AuthControllerTest {
} }
@Test @Test
@DisplayName("authVerify 성공 시 성인노출 true 저장을 호출한다") @DisplayName("authVerify 성공 시 입력이 없으면 true/ALL 저장을 호출한다")
fun shouldSaveAdultPreferenceWhenAuthVerifySucceeds() { fun shouldSaveAdultPreferenceWhenAuthVerifySucceeds() {
val member = createMember(id = 10L) val member = createMember(id = 10L)
val request = AuthVerifyRequest(receiptId = "receipt-1", version = "v1") val request = AuthVerifyRequest(receiptId = "receipt-1", version = "v1")
@@ -47,9 +56,13 @@ class AuthControllerTest {
Mockito.`when`(authService.isBlockAuth(certificate)).thenReturn(false) Mockito.`when`(authService.isBlockAuth(certificate)).thenReturn(false)
Mockito.`when`(authService.authenticate(certificate, 10L)).thenReturn(AuthResponse(gender = 1)) Mockito.`when`(authService.authenticate(certificate, 10L)).thenReturn(AuthResponse(gender = 1))
controller.authVerify(request, member) val response = controller.authVerify(request, member)
Mockito.verify(memberContentPreferenceService).markAdultVisibleAfterAuthVerify(10L) assertTrue(response.success)
assertEquals(1, response.data?.gender)
val inOrder = Mockito.inOrder(authService, memberContentPreferenceService)
inOrder.verify(authService).authenticate(certificate, 10L)
inOrder.verify(memberContentPreferenceService).markAdultVisibleAfterAuthVerify(10L, true, ContentType.ALL)
Mockito.verify(userActionService).recordAction( Mockito.verify(userActionService).recordAction(
memberId = 10L, memberId = 10L,
isAuth = true, isAuth = true,
@@ -57,11 +70,78 @@ class AuthControllerTest {
) )
} }
@ParameterizedTest
@CsvSource(
value = [
"{\"receipt_id\":\"receipt-json\",\"version\":\"v1\"}|true|ALL",
"{\"receipt_id\":\"receipt-json\",\"version\":null}|true|ALL",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":null,\"contentType\":null}|true|ALL",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":false}|false|ALL",
"{\"receipt_id\":\"receipt-json\",\"contentType\":\"ALL\"}|true|ALL",
"{\"receipt_id\":\"receipt-json\",\"contentType\":\"MALE\"}|true|MALE",
"{\"receipt_id\":\"receipt-json\",\"contentType\":\"FEMALE\"}|true|FEMALE",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":null,\"contentType\":\"MALE\"}|true|MALE",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":false,\"contentType\":null}|false|ALL",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":false,\"contentType\":\"ALL\"}|false|ALL",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":false,\"contentType\":\"MALE\"}|false|MALE",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":false,\"contentType\":\"FEMALE\"}|false|FEMALE",
"{\"receipt_id\":\"receipt-json\",\"isAdultContentVisible\":true,\"contentType\":\"FEMALE\"}|true|FEMALE"
],
delimiter = '|'
)
@DisplayName("인증 JSON의 camelCase 선호값을 전달하고 생략과 null은 필드별 true/ALL로 해석한다")
fun shouldApplyJsonPreferenceInputsAfterAuthentication(
json: String,
expectedAdultContentVisible: Boolean,
expectedContentType: ContentType
) {
val member = createMember(id = 11L)
val request = jacksonObjectMapper().readValue<AuthVerifyRequest>(json)
val certificate = AuthVerifyCertificate("홍길동", "19900101", "unique-json", "di-json", 1)
Mockito.`when`(authService.certificate(request, 11L)).thenReturn(certificate)
Mockito.`when`(authService.authenticate(certificate, 11L)).thenReturn(AuthResponse(gender = 1))
val response = controller.authVerify(request, member)
assertTrue(response.success)
assertEquals(1, response.data?.gender)
val inOrder = Mockito.inOrder(authService, memberContentPreferenceService)
inOrder.verify(authService).authenticate(certificate, 11L)
inOrder.verify(memberContentPreferenceService).markAdultVisibleAfterAuthVerify(
11L,
expectedAdultContentVisible,
expectedContentType
)
}
@ParameterizedTest
@ValueSource(strings = ["certificate", "authenticate"])
@DisplayName("인증서 검증 또는 authenticate 실패 시 선호 설정을 쓰지 않는다")
fun shouldNotWritePreferenceWhenAuthenticationFails(failingStep: String) {
val member = createMember(id = 12L)
val request = AuthVerifyRequest("receipt-failure", null, false, ContentType.FEMALE)
val certificate = AuthVerifyCertificate("홍길동", "19900101", "unique-failure", "di-failure", 1)
val failure = SodaException(messageKey = "member.auth.certificate_invalid_retry")
if (failingStep == "certificate") {
Mockito.`when`(authService.certificate(request, 12L)).thenThrow(failure)
} else {
Mockito.`when`(authService.certificate(request, 12L)).thenReturn(certificate)
Mockito.`when`(authService.authenticate(certificate, 12L)).thenThrow(failure)
}
val exception = assertThrows(SodaException::class.java) {
controller.authVerify(request, member)
}
assertSame(failure, exception)
Mockito.verifyNoInteractions(memberContentPreferenceService, userActionService)
}
@Test @Test
@DisplayName("차단 정책으로 authVerify가 실패하면 저장을 호출하지 않는다") @DisplayName("차단 정책으로 authVerify가 실패하면 저장을 호출하지 않는다")
fun shouldNotSaveAdultPreferenceWhenAuthIsBlocked() { fun shouldNotSaveAdultPreferenceWhenAuthIsBlocked() {
val member = createMember(id = 20L) val member = createMember(id = 20L)
val request = AuthVerifyRequest(receiptId = "receipt-2", version = null) val request = AuthVerifyRequest("receipt-2", null, false, ContentType.MALE)
val certificate = AuthVerifyCertificate( val certificate = AuthVerifyCertificate(
name = "홍길동", name = "홍길동",
birth = "19900101", birth = "19900101",
@@ -73,12 +153,14 @@ class AuthControllerTest {
Mockito.`when`(authService.certificate(request, memberId = 20L)).thenReturn(certificate) Mockito.`when`(authService.certificate(request, memberId = 20L)).thenReturn(certificate)
Mockito.`when`(authService.isBlockAuth(certificate)).thenReturn(true) Mockito.`when`(authService.isBlockAuth(certificate)).thenReturn(true)
assertThrows(SodaException::class.java) { val exception = assertThrows(SodaException::class.java) {
controller.authVerify(request, member) controller.authVerify(request, member)
} }
assertEquals("member.auth.blocked_policy", exception.messageKey)
Mockito.verify(authService).signOut(20L) Mockito.verify(authService).signOut(20L)
Mockito.verify(memberContentPreferenceService, Mockito.never()).markAdultVisibleAfterAuthVerify(Mockito.anyLong()) Mockito.verify(authService, Mockito.never()).authenticate(certificate, 20L)
Mockito.verifyNoInteractions(memberContentPreferenceService, userActionService)
} }
@Test @Test
@@ -86,11 +168,12 @@ class AuthControllerTest {
fun shouldThrowWhenMemberIsNull() { fun shouldThrowWhenMemberIsNull() {
val request = AuthVerifyRequest(receiptId = "receipt-3", version = null) val request = AuthVerifyRequest(receiptId = "receipt-3", version = null)
assertThrows(SodaException::class.java) { val exception = assertThrows(SodaException::class.java) {
controller.authVerify(request, null) controller.authVerify(request, null)
} }
Mockito.verifyNoInteractions(memberContentPreferenceService) assertEquals("common.error.bad_credentials", exception.messageKey)
Mockito.verifyNoInteractions(authService, memberContentPreferenceService, userActionService)
} }
private fun createMember(id: Long): Member { private fun createMember(id: Long): Member {