feat(content): 콘텐츠 조회 이력 실패 로그를 남긴다

This commit is contained in:
2026-06-01 17:55:53 +09:00
parent 1d7f55bbe7
commit 7ad514dcc0
2 changed files with 104 additions and 0 deletions

View File

@@ -40,6 +40,7 @@ import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.utils.generateFileName import kr.co.vividnext.sodalive.utils.generateFileName
import kr.co.vividnext.sodalive.v2.recommend.application.CreatorContentViewHistoryService import kr.co.vividnext.sodalive.v2.recommend.application.CreatorContentViewHistoryService
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable import org.springframework.cache.annotation.Cacheable
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
@@ -91,6 +92,8 @@ class AudioContentService(
@Value("\${cloud.aws.cloud-front.host}") @Value("\${cloud.aws.cloud-front.host}")
private val coverImageHost: String private val coverImageHost: String
) { ) {
private val log = LoggerFactory.getLogger(javaClass)
@Transactional @Transactional
fun audioContentLike(request: PutAudioContentLikeRequest, member: Member): PutAudioContentLikeResponse { fun audioContentLike(request: PutAudioContentLikeRequest, member: Member): PutAudioContentLikeResponse {
var audioContentLike = audioContentLikeRepository.findByMemberIdAndContentId( var audioContentLike = audioContentLikeRepository.findByMemberIdAndContentId(
@@ -820,6 +823,14 @@ class AudioContentService(
memberId = member.id!!, memberId = member.id!!,
contentId = audioContent.id!! contentId = audioContent.id!!
) )
}.onFailure { ex ->
log.warn(
"event=creator_content_view_history_record_failure memberId={} contentId={} error={}",
member.id,
audioContent.id,
ex.message,
ex
)
} }
return GetAudioContentDetailResponse( return GetAudioContentDetailResponse(

View File

@@ -29,10 +29,15 @@ 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.api.extension.ExtendWith
import org.mockito.Mockito import org.mockito.Mockito
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
import java.time.LocalDateTime
import java.util.Optional import java.util.Optional
@ExtendWith(OutputCaptureExtension::class)
class AudioContentServiceTest { class AudioContentServiceTest {
private lateinit var repository: AudioContentRepository private lateinit var repository: AudioContentRepository
private lateinit var explorerQueryRepository: ExplorerQueryRepository private lateinit var explorerQueryRepository: ExplorerQueryRepository
@@ -240,6 +245,34 @@ class AudioContentServiceTest {
assertEquals(audioContent.id!!, recordViewInvocation.arguments[1]) assertEquals(audioContent.id!!, recordViewInvocation.arguments[1])
} }
@Test
@DisplayName("콘텐츠 조회 이력 기록 실패는 상세 응답을 실패시키지 않고 로그로 남긴다")
fun shouldLogViewHistoryFailureWithoutFailingDetail(output: CapturedOutput) {
val viewer = createMember(id = 1003L, nickname = "history-failure-viewer")
val creator = createMember(id = 2003L, nickname = "history-failure-creator")
val audioContent = createAudioContent(creator)
stubSuccessfulDetailDependencies(viewer, creator, audioContent)
Mockito.doThrow(IllegalStateException("history failed"))
.`when`(creatorContentViewHistoryService)
.recordView(
memberId = Mockito.eq(viewer.id!!),
contentId = Mockito.eq(audioContent.id!!),
viewedAt = anyLocalDateTime()
)
val response = service.getDetail(
id = audioContent.id!!,
member = viewer,
isAdultContentVisible = false,
timezone = "Asia/Seoul"
)
assertEquals(audioContent.id!!, response.contentId)
assertTrue(output.out.contains("event=creator_content_view_history_record_failure"))
assertTrue(output.out.contains("memberId=${viewer.id}"))
assertTrue(output.out.contains("contentId=${audioContent.id}"))
}
private fun createMember(id: Long, nickname: String): Member { private fun createMember(id: Long, nickname: String): Member {
val member = Member( val member = Member(
email = "$nickname@test.com", email = "$nickname@test.com",
@@ -277,4 +310,64 @@ class AudioContentServiceTest {
return audioContent return audioContent
} }
private fun stubSuccessfulDetailDependencies(viewer: Member, creator: Member, audioContent: AudioContent) {
Mockito.`when`(repository.findById(audioContent.id!!)).thenReturn(Optional.of(audioContent))
Mockito.`when`(explorerQueryRepository.getMember(creator.id!!)).thenReturn(creator)
Mockito.`when`(
orderRepository.isExistOrderedAndOrderType(
memberId = viewer.id!!,
contentId = audioContent.id!!
)
).thenReturn(Pair(true, OrderType.KEEP))
Mockito.`when`(explorerQueryRepository.getCreatorFollowing(creator.id!!, viewer.id!!)).thenReturn(null)
Mockito.`when`(
limitedEditionOrderRepository.getOrderSequence(
contentId = audioContent.id!!,
memberId = viewer.id!!
)
).thenReturn(null)
Mockito.`when`(
audioContentCloudFront.generateSignedURL(
resourcePath = audioContent.content!!,
expirationTime = 7_200_000L
)
).thenReturn("https://signed.test/audio")
Mockito.`when`(
repository.getCreatorOtherContentList(
cloudfrontHost = "https://cdn.test",
contentId = audioContent.id!!,
creatorId = creator.id!!,
isAdult = false
)
).thenReturn(emptyList())
Mockito.`when`(
repository.getSameThemeOtherContentList(
cloudfrontHost = "https://cdn.test",
contentId = audioContent.id!!,
themeId = audioContent.theme!!.id!!,
isAdult = false
)
).thenReturn(emptyList())
Mockito.`when`(audioContentLikeRepository.totalCountAudioContentLike(audioContent.id!!)).thenReturn(0)
Mockito.`when`(audioContentLikeRepository.findByMemberIdAndContentId(viewer.id!!, audioContent.id!!)).thenReturn(null)
Mockito.`when`(
pinContentRepository.findByContentIdAndMemberId(
contentId = audioContent.id!!,
memberId = viewer.id!!,
active = true
)
).thenReturn(null)
Mockito.`when`(pinContentRepository.getPinContentList(memberId = viewer.id!!, active = true)).thenReturn(emptyList())
Mockito.`when`(
contentThemeTranslationRepository.findByContentThemeIdAndLocale(
contentThemeId = audioContent.theme!!.id!!,
locale = "ko"
)
).thenReturn(null)
}
private fun anyLocalDateTime(): LocalDateTime {
return Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.MIN
}
} }