diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt index a3c7c44a..d800954a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.member.auth import kr.co.vividnext.sodalive.common.ApiResponse 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.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.useraction.ActionType @@ -34,7 +35,11 @@ class AuthController( } 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 { userActionService.recordAction( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthVerifyRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthVerifyRequest.kt index ea9590ee..b9fa437b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthVerifyRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthVerifyRequest.kt @@ -1,12 +1,17 @@ package kr.co.vividnext.sodalive.member.auth import com.fasterxml.jackson.annotation.JsonProperty +import kr.co.vividnext.sodalive.content.ContentType data class AuthVerifyRequest( @JsonProperty("receipt_id") val receiptId: String, @JsonProperty("version") - val version: String? + val version: String?, + @JsonProperty("isAdultContentVisible") + val isAdultContentVisible: Boolean? = null, + @JsonProperty("contentType") + val contentType: ContentType? = null ) data class AuthVerifyCertificate( diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/member/auth/AuthControllerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/member/auth/AuthControllerTest.kt index bf9d79de..f599542a 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/member/auth/AuthControllerTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/member/auth/AuthControllerTest.kt @@ -1,14 +1,23 @@ 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.content.ContentType import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.useraction.ActionType 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.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName 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 class AuthControllerTest { @@ -31,7 +40,7 @@ class AuthControllerTest { } @Test - @DisplayName("authVerify 성공 시 성인노출 true 저장을 호출한다") + @DisplayName("authVerify 성공 시 입력이 없으면 true/ALL 저장을 호출한다") fun shouldSaveAdultPreferenceWhenAuthVerifySucceeds() { val member = createMember(id = 10L) val request = AuthVerifyRequest(receiptId = "receipt-1", version = "v1") @@ -47,9 +56,13 @@ class AuthControllerTest { Mockito.`when`(authService.isBlockAuth(certificate)).thenReturn(false) 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( memberId = 10L, 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(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 @DisplayName("차단 정책으로 authVerify가 실패하면 저장을 호출하지 않는다") fun shouldNotSaveAdultPreferenceWhenAuthIsBlocked() { 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( name = "홍길동", birth = "19900101", @@ -73,12 +153,14 @@ class AuthControllerTest { Mockito.`when`(authService.certificate(request, memberId = 20L)).thenReturn(certificate) Mockito.`when`(authService.isBlockAuth(certificate)).thenReturn(true) - assertThrows(SodaException::class.java) { + val exception = assertThrows(SodaException::class.java) { controller.authVerify(request, member) } + assertEquals("member.auth.blocked_policy", exception.messageKey) 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 @@ -86,11 +168,12 @@ class AuthControllerTest { fun shouldThrowWhenMemberIsNull() { val request = AuthVerifyRequest(receiptId = "receipt-3", version = null) - assertThrows(SodaException::class.java) { + val exception = assertThrows(SodaException::class.java) { 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 {