Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
testImplementation("io.rest-assured:rest-assured:5.3.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// jwt
Expand Down
14 changes: 11 additions & 3 deletions src/main/kotlin/codel/recommendation/business/CodeTimeService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ class CodeTimeService(
* 제외 대상:
* - 차단한 사용자
* - 나를 차단한 사용자
* - 최근 시그널 보낸 사용자
* - WITHDRAWN 상태의 사용자 (회원 탈퇴)
*
* 주의: 시그널 관계는 실시간 필터링에서 제외하지 않음
* → 추천 세션 일관성 유지를 위해 새로운 추천 생성 시에만 제외
*
* @param user 기준 사용자
* @param memberIds 필터링할 사용자 ID 목록
* @return 필터링된 사용자 ID 목록
Expand All @@ -156,15 +158,21 @@ class CodeTimeService(
return emptyList()
}

// 실시간 제외 대상 조회 (차단만)
val excludeIds = mutableSetOf<Long>()

// 1. 차단 관계만 확인 (즉시 반영)
excludeIds.addAll(exclusionService.getBlockedMemberIds(user))
excludeIds.addAll(exclusionService.getRecentSignalMemberIds(user))

// WITHDRAWN 상태의 회원 필터링
// 2. 시그널 관계는 확인하지 않음 (추천 세션 일관성 유지)
// → 새로운 추천 생성 시에만 제외됨

// 3. WITHDRAWN 상태의 회원 필터링
// getMembersByIds를 통해 조회하면 자동으로 WITHDRAWN이 제외됨
val validMembers = bucketService.getMembersByIds(memberIds)
val validIds = validMembers.map { it.getIdOrThrow() }

// 4. 최종 필터링
val filteredIds = validIds.filter { it !in excludeIds }

log.debug {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,22 @@ interface RecommendationHistoryJpaRepository : JpaRepository<RecommendationHisto
/**
* 시간 범위 내 특정 시간대 코드타임 추천 조회
* 날짜가 바뀌는 경우를 처리하기 위한 범위 조회
*
*
* @param user 사용자
* @param timeSlot 시간대 ("10:00" 또는 "22:00")
* @param startDateTime 시작 시간 (포함)
* @param endDateTime 종료 시간 (미포함)
* @return 해당 시간 범위 내 추천된 사용자 ID 목록
* @return 해당 시간 범위 내 추천된 사용자 ID 목록 (생성 순서대로)
*/
@Query("""
SELECT rh.recommendedUser.id
FROM RecommendationHistory rh
WHERE rh.user = :user
SELECT rh.recommendedUser.id
FROM RecommendationHistory rh
WHERE rh.user = :user
AND rh.recommendationType = 'CODE_TIME'
AND rh.recommendationTimeSlot = :timeSlot
AND rh.recommendedAt >= :startDateTime
AND rh.recommendedAt < :endDateTime
ORDER BY rh.createdAt DESC
ORDER BY rh.createdAt ASC
""")
fun findCodeTimeIdsByTimeRange(
@Param("user") user: Member,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.*
import org.springframework.http.HttpStatus
import org.springframework.mock.web.MockMultipartFile
import java.time.LocalDate

class PreVerificationStrategyTest {

private lateinit var signupService: SignupService
private lateinit var memberJpaRepository: MemberJpaRepository
private lateinit var strategy: PreVerificationStrategy
private lateinit var asyncNotificationService: IAsyncNotificationService
private lateinit var strategy: PreVerificationStrategy

@BeforeEach
fun setUp() {
Expand All @@ -30,31 +32,36 @@ class PreVerificationStrategyTest {
strategy = PreVerificationStrategy(signupService, memberJpaRepository, asyncNotificationService)
}

@DisplayName("PERSONALITY_COMPLETED 상태에서는 히든 이미지 등록 후 HIDDEN_COMPLETED 상태로 변경한다")
@DisplayName("PERSONALITY_COMPLETED 상태에서는 히든 이미지 등록 후 PENDING 상태로 변경한다")
@Test
fun handleHiddenImages_personalityCompleted_changeToHiddenCompleted() {
fun handleHiddenImages_personalityCompleted_changeToPending() {
// given
val profile = Profile(
codeName = "테스트유저"
id = 1L,
codeName = "테스트유저",
bigCity = "서울",
smallCity = "강남구",
birthDate = LocalDate.of(1990, 1, 1)
)

val member = Member(
id = 1L,
oauthId = "test-oauth-id",
oauthType = OauthType.KAKAO,
memberStatus = MemberStatus.PERSONALITY_COMPLETED,
email = "test@test.com"
email = "test@test.com",
profile = profile
)

// 양방향 연관관계 설정
member.updateProfile(profile)
profile.member = member

val images = listOf(
MockMultipartFile("image1", "test1.jpg", "image/jpeg", "test1".toByteArray()),
MockMultipartFile("image2", "test2.jpg", "image/jpeg", "test2".toByteArray()),
MockMultipartFile("image3", "test3.jpg", "image/jpeg", "test3".toByteArray())
)

// memberJpaRepository.findByMemberId가 member를 반환하도록 mock 설정
`when`(memberJpaRepository.findByMemberId(1L)).thenReturn(member)

// when
Expand All @@ -64,7 +71,6 @@ class PreVerificationStrategyTest {
verify(signupService, times(1)).registerHiddenImages(member, images)
verify(memberJpaRepository, times(1)).findByMemberId(1L)

// member 상태가 PENDING으로 변경되었는지 확인
assertEquals(MemberStatus.PENDING, member.memberStatus)
assertEquals(HttpStatus.OK, response.statusCode)
}
Expand Down
Loading