Skip to content

[TEST] #6 통합 테스트 및 이벤트/스케줄러 #138

Merged
nan0silver merged 8 commits intomainfrom
test/137-phase-6
Feb 19, 2026
Merged

[TEST] #6 통합 테스트 및 이벤트/스케줄러 #138
nan0silver merged 8 commits intomainfrom
test/137-phase-6

Conversation

@nan0silver
Copy link
Member

@nan0silver nan0silver commented Feb 19, 2026

⭐️ Issue Number

🚩 Summary

생성된 테스트 파일

파일 테스트 수 설명
listener/CallEventListenerTest.java 5개 통화 시작 이벤트 → 녹음 시작 체인
listener/MatchingEventListenerTest.java 5개 매칭 성공 → WebSocket 알림 + Agora 채널 오케스트레이션
scheduler/GracePeriodSchedulerTest.java 5개 유예 기간 만료 처리 스케줄러
service/RedisMatchingQueueServiceOperationTest.java 15개 enqueue/dequeue/상태조회/차단/제거 동작 검증

각 파일의 주요 검증 포인트

CallEventListenerTest

  • isAutoStart=false / channelName=null → 녹음 스킵
  • 녹음 서비스 예외 발생 → 통화 계속 진행 (예외 비전파)

MatchingEventListenerTest

  • Agora 불가 → 오류 알림 + createChannel 미호출
  • 채널 조인 부분 실패 → 취소 알림 + 재매칭 예약
  • isAutoStart=trueCallStartedEvent 발행 확인 (publishEvent(Object.class))
  • Call 미조회 → 알림 전송 없음

GracePeriodSchedulerTest

  • 활성 통화 없음 → isInGracePeriod 미호출
  • 한쪽만 유예 기간 → callStatusService 미호출
  • 체크 중 예외 발생 → 나머지 통화 계속 처리

RedisMatchingQueueServiceOperationTest

  • Lua 스크립트 반환값별 Result 객체 검증
  • Redis 예외 → 각 메서드별 방어 처리 확인
  • 양방향 차단 검증 (isBlocked)

GitHub Actions CI 파이프라인 및 Dockerfile 추가

.github/
  workflows/
    ci.yml        ← GitHub Actions CI 파이프라인
Dockerfile        ← 나중에 CD(ECR/ECS) 때 사용할 빌드 정의

ci.yml 핵심 구조 설명

push/PR → main 브랜치
        └── test job (ubuntu-latest)
              ├── services: redis:7-alpine  ← 사이드카 컨테이너로 함께 실행
              ├── checkout → JDK 17 설정 → Gradle 캐시
              ├── ./gradlew test
              │     env: SPRING_DATA_REDIS_HOST=localhost  ← 사이드카 Redis 연결
              └── 테스트 결과 artifact 업로드 (항상 실행)

Redis 연동 방식: GitHub Actions의 services 블록으로 Redis 컨테이너를 test job과 동일한 네트워크에서 실행합니다. SPRING_DATA_REDIS_HOST=localhost 환경변수가 application-test.yml의 하드코딩된 localhost를 Spring Boot가 자동으로 오버라이드합니다. yml 파일 수정 없이 동작합니다.


Dockerfile 설계 포인트

단계 이미지 역할
Builder eclipse-temurin:17-jdk-alpine bootJar 빌드 (-x test 로 테스트 제외)
Runner eclipse-temurin:17-jre-alpine JRE만 포함 → 이미지 크기 최소화
  • 멀티스테이지 빌드: JDK는 빌드에만, 최종 이미지엔 JRE만 포함
  • 비루트 사용자: appuser로 실행 → 보안 강화
  • 의존성 레이어 분리: COPY src 전에 dependencies 다운로드 → Docker 캐시 효율 향상

📋 To Do

나중에 CD 구축 시 ci.yml에 아래 job을 추가하면 됩니다:

test 통과 → build-and-push job
              ├── ECR 로그인
              ├── docker build (위 Dockerfile 사용)
              ├── docker push → ECR
              └── ECS 서비스 업데이트

Summary by CodeRabbit

  • Tests

    • 통화·매칭·그레이스 기간·Redis 매칭 큐 등 핵심 도메인에 대한 포괄적 단위 테스트 추가로 안정성 및 회귀 방지 강화
    • 테스트 전용 설정 변경 및 Firebase 모킹으로 테스트 환경 분리
  • Chores

    • CI 파이프라인 추가(푸시/PR 시 자동 테스트, Redis 서비스 포함 및 테스트 리포트 업로드)
    • 다단계 Docker 이미지 도입으로 빌드/런타임 일관성 및 보안 개선
    • 빌드 출력물 이름을 app.jar로 고정

- 통화 시작 이벤트 -> 녹음 시작 체인
- 매칭 성공 -> WebSocket 알림, Agora 채널 오케스트레이션
- 유예 기간 만료 처리 스케줄러
- enqueue, dequeue, 큐 상태 조회, 차단 등
- GitHub Actions CI 워크플로우 추가 (.github/workflows/ci.yml)
- 멀티스테이지 Dockerfile 추가 (CD 단계 대비)
@nan0silver nan0silver linked an issue Feb 19, 2026 that may be closed by this pull request
4 tasks
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Walkthrough

GitHub Actions CI 워크플로우와 멀티스테이지 Dockerfile이 추가되었고, 이벤트 리스너·스케줄러·Redis 매칭 큐 관련의 여러 JUnit 5 단위 테스트 및 테스트 환경 설정 변경이 포함되었습니다.

Changes

Cohort / File(s) Summary
CI/CD 워크플로우
​.github/workflows/ci.yml
메인 브랜치 Push/PR에 트리거되는 "CI" 워크플로우 추가: Redis 7-alpine 서비스(헬스체크 포함), Temurin JDK17 설정, Gradle 캐시, Gradle wrapper 실행 권한 부여, 테스트 실행(스프링 프로파일·Redis env 설정), 테스트 리포트 아티팩트 업로드(7일). 일부 스텝 명이 한국어임.
컨테이너 구성
Dockerfile
멀티스테이지 Dockerfile 추가: eclipse-temurin:17-jdk-alpine 빌드 스테이지(의존성 프리페치, bootJar 생성), eclipse-temurin:17-jre-alpine 런타임 스테이지(비루트 appuser 생성·소유권 설정, app.jar 복사, 포트 8080 노출, 엔트리포인트 지정).
이벤트 리스너 테스트
src/test/java/com/ldsilver/chingoohaja/listener/CallEventListenerTest.java, src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java
CallEventListener 단위 테스트 추가(자동 녹화 시작/비활성/채널 없음/예외 처리 등). MatchingEventListener 단위 테스트 추가(레디스 큐 제거, WebSocket 알림, Agora 건강 상태별 분기, 부분 실패 시 취소·재매칭 로직 검증).
스케줄러 테스트
src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java
GracePeriodScheduler의 만료 유예 검사 로직에 대한 단위 테스트 추가(활성 콜 없음, 한쪽/양쪽 유예 상태, 예외 발생 시 연속 처리 등 시나리오).
Redis 매칭 큐 서비스 테스트
src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java
RedisMatchingQueueService의 광범위한 유닛 테스트 추가: Lua 스크립트 기반 enqueueUser/dequeueUser, getQueueStatus, getWaitingCount, 블록 저장·확인, removeMatchedUsers 등 정상·오류·엣지 케이스 검증.
애플리케이션 테스트 설정
src/test/resources/application-test.yml, src/test/java/com/ldsilver/chingoohaja/ChingooHajaApplicationTests.java
테스트 설정값 변경(Agora app-id/app-certificate 및 Firebase 서비스 계정 경로·storage-bucket 값 업데이트). 테스트 클래스에 @ActiveProfiles("test") 추가 및 FirebaseConfig에 대한 @MockitoBean 모킹 포함된 테스트 스캐폴딩 변경.
빌드 설정
build.gradle
bootJar { archiveFileName = 'app.jar' } 추가로 부트 JAR 출력 파일명을 app.jar로 고정.

Sequence Diagram(s)

(생략)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 깡충 깡충 CI를 건너,
도커 품에 조용히 앉아,
테스트가 방울방울 톡 톡,
Redis·Agora 손을 맞잡고,
작은 변경에 기쁜 당근 한 줌 🥕


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (7)
src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java (1)

225-270: Redis 키 패턴이 하드코딩되어 있습니다.

"user:blocked:1", "user:blocked:2" 등의 키 패턴이 테스트에 직접 작성되어 있습니다. 구현체의 키 형식이 변경되면 테스트가 실패할 수 있습니다. RedisMatchingConstants의 키 빌더를 사용하는 것을 고려하세요.

💡 제안된 수정 예시
+import static com.ldsilver.chingoohaja.infrastructure.redis.RedisMatchingConstants.KeyBuilder.blockedUsersKey;
+
         `@Test`
         `@DisplayName`("차단 목록에 추가 후 isBlocked가 true를 반환한다")
         void givenBlockedUser_whenIsBlocked_thenReturnsTrue() {
             // given
             when(redisTemplate.opsForSet()).thenReturn(setOperations);
             when(redisTemplate.expire(anyString(), any())).thenReturn(true);
-            when(setOperations.isMember("user:blocked:1", "2")).thenReturn(true);
-            when(setOperations.isMember("user:blocked:2", "1")).thenReturn(false);
+            when(setOperations.isMember(blockedUsersKey(1L), "2")).thenReturn(true);
+            when(setOperations.isMember(blockedUsersKey(2L), "1")).thenReturn(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java`
around lines 225 - 270, Tests in RedisMatchingQueueServiceOperationTest hardcode
Redis keys like "user:blocked:1"/"user:blocked:2", making them brittle; replace
hardcoded strings with the same key-building helper used by production (e.g.,
RedisMatchingConstants or the service's key builder) so the tests follow
implementation changes. Update the test setup in methods testing isBlocked and
saveBlockedUsers to call the key builder (referencing RedisMatchingConstants or
redisMatchingQueueService's key generation method) to produce keys for
setOperations.isMember and any expire stubbing, and use those generated keys
when stubbing redisTemplate.opsForSet()/setOperations behavior.
src/test/java/com/ldsilver/chingoohaja/listener/CallEventListenerTest.java (1)

77-87: 예외 비전파 테스트에 명시적 검증 추가를 고려하세요.

현재 테스트는 예외가 전파되지 않음을 암묵적으로 확인합니다. assertDoesNotThrow를 사용하면 테스트 의도가 더 명확해집니다.

💡 제안된 수정
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
         `@Test`
         `@DisplayName`("녹음 서비스에서 예외가 발생해도 통화는 계속 진행된다")
         void givenRecordingServiceThrows_whenCallStarted_thenDoesNotPropagate() {
             // given
             when(recordingProperties.isAutoStart()).thenReturn(true);
             doThrow(new RuntimeException("Agora 오류")).when(agoraRecordingService).startRecording(any());
             CallStartedEvent event = new CallStartedEvent(1L, "channel-abc");

-            // when & then (예외가 전파되지 않아야 함)
-            callEventListener.handleCallStarted(event);
+            // when & then
+            assertDoesNotThrow(() -> callEventListener.handleCallStarted(event));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/com/ldsilver/chingoohaja/listener/CallEventListenerTest.java`
around lines 77 - 87, Update the test
givenRecordingServiceThrows_whenCallStarted_thenDoesNotPropagate to explicitly
assert that no exception is thrown: wrap the call to
callEventListener.handleCallStarted(event) with JUnit's assertDoesNotThrow
(import from org.junit.jupiter.api.Assertions) so the test intent is clear;
leave the existing stubbing of recordingProperties.isAutoStart() and
doThrow(...) on agoraRecordingService unchanged and only change the assertion
around handleCallStarted(event).
src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java (1)

122-137: 테스트 설명과 검증 내용이 일치하지 않습니다.

@DisplayName이 "상태를 로그에 남긴다"라고 명시하지만, 실제로 로깅 동작을 검증하지 않습니다. 테스트 설명을 수정하거나 로그 검증을 추가하는 것을 고려하세요.

💡 제안된 수정 - 테스트 설명 변경
         `@Test`
-        `@DisplayName`("두 사용자 모두 유예 기간이면 상태를 로그에 남긴다")
-        void givenBothUsersInGrace_whenCheck_thenLogsStatus() {
+        `@DisplayName`("두 사용자 모두 유예 기간이면 유예 기간 체크를 수행한다")
+        void givenBothUsersInGrace_whenCheck_thenChecksGracePeriod() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java`
around lines 122 - 137, The test method
givenBothUsersInGrace_whenCheck_thenLogsStatus in GracePeriodSchedulerTest has a
DisplayName claiming it verifies logging but only asserts calls to
gracePeriodService; either update the DisplayName to reflect current behavior
(e.g., "...thenChecksGracePeriodForBothUsers") or add verification of the
logging side-effect: mock or capture the logger used by GracePeriodScheduler (or
attach a test appender) and after calling
gracePeriodScheduler.checkExpiredGracePeriods() assert that the expected log
message was emitted; modify the test accordingly to reference the method under
test (gracePeriodScheduler.checkExpiredGracePeriods()) and the mocks
(callRepository, gracePeriodService) so the intent matches implementation.
src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java (2)

146-169: 이벤트 발행 검증이 너무 일반적입니다.

any(Object.class)로 검증하면 어떤 타입의 이벤트든 발행되면 통과합니다. CallStartedEvent 타입을 명시적으로 검증하면 테스트가 더 강력해집니다.

💡 제안된 수정
+import com.ldsilver.chingoohaja.event.CallStartedEvent;
+import org.mockito.ArgumentCaptor;
+
             // then
-            verify(eventPublisher).publishEvent(any(Object.class));
+            ArgumentCaptor<CallStartedEvent> eventCaptor = ArgumentCaptor.forClass(CallStartedEvent.class);
+            verify(eventPublisher).publishEvent(eventCaptor.capture());
+            assertThat(eventCaptor.getValue().callId()).isEqualTo(100L);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java`
around lines 146 - 169, The test MatchingEventListenerTest currently verifies
eventPublication with verify(eventPublisher).publishEvent(any(Object.class)),
which is too loose; change the assertion in the given test (method
givenAutoRecordingEnabled_whenMatchingSuccess_thenPublishesCallStartedEvent) to
assert the specific event type published—i.e., replace the any(Object.class)
matcher with a type-specific matcher like isA(CallStartedEvent.class) or
argThat(e -> e instanceof CallStartedEvent && /* optional checks */) when
verifying eventPublisher.publishEvent after calling
matchingEventListener.handleMatchingSuccess(event); this ensures the test
verifies a CallStartedEvent is published rather than any object.

42-53: Mock 의존성이 많습니다 (10개 이상).

MatchingEventListener가 많은 의존성을 가지고 있어 단일 책임 원칙(SRP) 관점에서 향후 리팩토링을 고려해볼 수 있습니다. 현재 테스트 작성에는 문제가 없으나, 프로덕션 코드의 복잡도가 높을 수 있음을 시사합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java`
around lines 42 - 53, MatchingEventListener currently depends on many
collaborators (RedisMatchingQueueService, WebSocketEventService, CallRepository,
CallChannelService, AgoraTokenService, AgoraService, MatchingService,
CallSessionRepository, RecordingProperties, ApplicationEventPublisher),
indicating a SRP violation; to fix, analyze MatchingEventListener to group
related responsibilities (e.g., queueing/dispatch, call/session management,
agora/token/recording concerns), extract those groups into cohesive classes or
facades (e.g., MatchingQueueFacade, CallManagementService, AgoraFacade) and
update MatchingEventListener to depend on those fewer abstractions, then update
tests to mock the new facades (reducing the number of `@Mock` fields) and adjust
existing interaction tests to verify facade calls instead of many low-level
collaborators.
Dockerfile (1)

27-27: JAR 파일 glob 패턴이 여러 파일을 복사할 수 있음.

*.jar 패턴은 build/libs/ 디렉토리에 여러 JAR 파일이 존재할 경우 예측 불가능한 동작을 유발할 수 있습니다. 명시적인 파일 이름 패턴 사용을 권장합니다.

💡 제안된 수정
-COPY --from=builder /app/build/libs/*.jar app.jar
+COPY --from=builder /app/build/libs/*-SNAPSHOT.jar app.jar

또는 빌드 시 -Pversion을 사용하여 버전을 고정하고 해당 패턴을 사용하는 것을 고려하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 27, The Dockerfile line using a glob COPY --from=builder
/app/build/libs/*.jar app.jar can match multiple JARs and cause unpredictable
builds; change the build to produce a single predictable artifact (e.g., set
archiveFileName or use -Pversion in Gradle) and then replace the COPY with the
explicit filename from the builder stage (e.g., COPY --from=builder
/app/build/libs/my-app-<version>.jar app.jar) or ensure the build writes a
consistent fixed name so the COPY references that exact JAR rather than *.jar.
.github/workflows/ci.yml (1)

48-53: 테스트 실패 시 디버깅을 위해 --stacktrace 옵션 추가를 고려하세요.

CI에서 테스트 실패 시 상세한 스택 트레이스가 있으면 문제 해결이 더 쉬워집니다.

💡 제안된 수정
       - name: 테스트 실행
-        run: ./gradlew test --no-daemon
+        run: ./gradlew test --no-daemon --stacktrace
         env:
           SPRING_PROFILES_ACTIVE: test
           SPRING_DATA_REDIS_HOST: localhost
           SPRING_DATA_REDIS_PORT: 6379
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 48 - 53, The CI test step named "테스트
실행" currently runs "./gradlew test --no-daemon"; update that run command to
include the Gradle "--stacktrace" flag (e.g., "./gradlew test --no-daemon
--stacktrace") so failing tests produce full stack traces for debugging; modify
the workflow step where the "테스트 실행" name and the run command are defined to
include this flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 48-53: The CI test step named "테스트 실행" currently runs "./gradlew
test --no-daemon"; update that run command to include the Gradle "--stacktrace"
flag (e.g., "./gradlew test --no-daemon --stacktrace") so failing tests produce
full stack traces for debugging; modify the workflow step where the "테스트 실행"
name and the run command are defined to include this flag.

In `@Dockerfile`:
- Line 27: The Dockerfile line using a glob COPY --from=builder
/app/build/libs/*.jar app.jar can match multiple JARs and cause unpredictable
builds; change the build to produce a single predictable artifact (e.g., set
archiveFileName or use -Pversion in Gradle) and then replace the COPY with the
explicit filename from the builder stage (e.g., COPY --from=builder
/app/build/libs/my-app-<version>.jar app.jar) or ensure the build writes a
consistent fixed name so the COPY references that exact JAR rather than *.jar.

In `@src/test/java/com/ldsilver/chingoohaja/listener/CallEventListenerTest.java`:
- Around line 77-87: Update the test
givenRecordingServiceThrows_whenCallStarted_thenDoesNotPropagate to explicitly
assert that no exception is thrown: wrap the call to
callEventListener.handleCallStarted(event) with JUnit's assertDoesNotThrow
(import from org.junit.jupiter.api.Assertions) so the test intent is clear;
leave the existing stubbing of recordingProperties.isAutoStart() and
doThrow(...) on agoraRecordingService unchanged and only change the assertion
around handleCallStarted(event).

In
`@src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java`:
- Around line 146-169: The test MatchingEventListenerTest currently verifies
eventPublication with verify(eventPublisher).publishEvent(any(Object.class)),
which is too loose; change the assertion in the given test (method
givenAutoRecordingEnabled_whenMatchingSuccess_thenPublishesCallStartedEvent) to
assert the specific event type published—i.e., replace the any(Object.class)
matcher with a type-specific matcher like isA(CallStartedEvent.class) or
argThat(e -> e instanceof CallStartedEvent && /* optional checks */) when
verifying eventPublisher.publishEvent after calling
matchingEventListener.handleMatchingSuccess(event); this ensures the test
verifies a CallStartedEvent is published rather than any object.
- Around line 42-53: MatchingEventListener currently depends on many
collaborators (RedisMatchingQueueService, WebSocketEventService, CallRepository,
CallChannelService, AgoraTokenService, AgoraService, MatchingService,
CallSessionRepository, RecordingProperties, ApplicationEventPublisher),
indicating a SRP violation; to fix, analyze MatchingEventListener to group
related responsibilities (e.g., queueing/dispatch, call/session management,
agora/token/recording concerns), extract those groups into cohesive classes or
facades (e.g., MatchingQueueFacade, CallManagementService, AgoraFacade) and
update MatchingEventListener to depend on those fewer abstractions, then update
tests to mock the new facades (reducing the number of `@Mock` fields) and adjust
existing interaction tests to verify facade calls instead of many low-level
collaborators.

In
`@src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java`:
- Around line 122-137: The test method
givenBothUsersInGrace_whenCheck_thenLogsStatus in GracePeriodSchedulerTest has a
DisplayName claiming it verifies logging but only asserts calls to
gracePeriodService; either update the DisplayName to reflect current behavior
(e.g., "...thenChecksGracePeriodForBothUsers") or add verification of the
logging side-effect: mock or capture the logger used by GracePeriodScheduler (or
attach a test appender) and after calling
gracePeriodScheduler.checkExpiredGracePeriods() assert that the expected log
message was emitted; modify the test accordingly to reference the method under
test (gracePeriodScheduler.checkExpiredGracePeriods()) and the mocks
(callRepository, gracePeriodService) so the intent matches implementation.

In
`@src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java`:
- Around line 225-270: Tests in RedisMatchingQueueServiceOperationTest hardcode
Redis keys like "user:blocked:1"/"user:blocked:2", making them brittle; replace
hardcoded strings with the same key-building helper used by production (e.g.,
RedisMatchingConstants or the service's key builder) so the tests follow
implementation changes. Update the test setup in methods testing isBlocked and
saveBlockedUsers to call the key builder (referencing RedisMatchingConstants or
redisMatchingQueueService's key generation method) to produce keys for
setOperations.isMember and any expire stubbing, and use those generated keys
when stubbing redisTemplate.opsForSet()/setOperations behavior.

- ci.yml: ./gradlew test에 --stacktrace 플래그 추가
- Dockerfile: 와일드카드 *.jar → app.jar로 명시 (build.gradle에 bootJar 블록 추가)

- CallEventListenerTest: 예외 전파 차단 검증에 assertDoesNotThrow 추가

- MatchingEventListenerTest: publishEvent 검증을 ArgumentCaptor로 교체하여 publishEvent(Object) 오버로드와의 타입 불일치 해소
- GracePeriodSchedulerTest: givenBothUsersInGrace 테스트 DisplayName 명확화

- RedisMatchingQueueServiceOperationTest: 하드코딩된 Redis 키 문자열을 blockedKey(Long) 헬퍼 메서드로 추출하여 중복 제거
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
.github/workflows/ci.yml (1)

55-60: 테스트 결과 업로드에 JUnit XML도 포함하는 것을 고려해 주세요.
현재 HTML 리포트만 업로드됩니다. build/test-results/test/(JUnit XML)을 함께 업로드하면 다른 CI 도구와 연동·가공이 쉬워집니다.

💡 제안 변경
       with:
         name: test-results
-        path: build/reports/tests/test/
+        path: |
+          build/reports/tests/test/
+          build/test-results/test/
         retention-days: 7
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 55 - 60, Update the "테스트 결과 업로드"
upload-artifact step that uses actions/upload-artifact@v4 to include the JUnit
XML output path in addition to the HTML report; include both
build/reports/tests/test/ and build/test-results/test/ (the JUnit XML directory)
in the "path" so the artifact contains HTML and JUnit XML results for downstream
CI tools and reporting.
src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java (1)

60-68: setId 헬퍼 메서드가 여러 테스트 파일에 중복됩니다.

이 헬퍼 메서드가 MatchingEventListenerTest와 동일하게 존재합니다. 공통 테스트 유틸리티 클래스로 추출하면 중복을 줄일 수 있습니다.

♻️ 공통 유틸리티 추출 제안
// src/test/java/com/ldsilver/chingoohaja/support/TestEntityUtils.java
public final class TestEntityUtils {
    private TestEntityUtils() {}
    
    public static void setId(Object entity, Long id) {
        try {
            Field field = entity.getClass().getDeclaredField("id");
            field.setAccessible(true);
            field.set(entity, id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java`
around lines 60 - 68, Extract the duplicated private helper setId method into a
shared test utility: create a final utility class TestEntityUtils with a public
static setId(Object entity, Long id) method (matching the implementation in
GracePeriodSchedulerTest and MatchingEventListenerTest), remove the private
setId from both test classes, and update both GracePeriodSchedulerTest and
MatchingEventListenerTest to call TestEntityUtils.setId(...) so the reflection
logic is centralized and duplication is eliminated.
src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java (1)

94-127: dequeueUser에 Redis 예외 처리 테스트가 누락되었습니다.

EnqueueUser 테스트에서는 Redis 예외 발생 시 REDIS_ERROR 메시지와 함께 실패를 반환하는지 검증하지만, DequeueUser에는 동일한 테스트가 없습니다. 일관성을 위해 추가를 고려해 주세요.

♻️ 추가 테스트 제안
`@Test`
`@DisplayName`("Redis 오류 발생 시 DequeueResult(success=false)를 반환한다")
void givenRedisException_whenDequeue_thenReturnsFailure() {
    // given
    when(redisTemplate.execute(any(RedisScript.class), anyList(), any(Object[].class)))
            .thenThrow(new RuntimeException("Redis 연결 실패"));

    // when
    RedisMatchingQueueService.DequeueResult result =
            redisMatchingQueueService.dequeueUser(100L, 1L);

    // then
    assertThat(result.success()).isFalse();
    assertThat(result.message()).isEqualTo(RedisMatchingConstants.ResponseMessage.REDIS_ERROR);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java`
around lines 94 - 127, Add a test that verifies Redis exceptions during dequeue
are handled like enqueue: stub redisTemplate.execute to throw (e.g., new
RuntimeException("Redis 연결 실패")) and call
RedisMatchingQueueService.dequeueUser(100L, 1L); assert the returned
DequeueResult has success() == false and message() equals
RedisMatchingConstants.ResponseMessage.REDIS_ERROR; place the test alongside the
other DequeueUser tests and use the same mocking pattern
(when(redisTemplate.execute(...)).thenThrow(...)) so dequeueUser properly maps
Redis failures to a failure result.
src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java (1)

171-173: ArgumentCaptor 타입을 더 구체적으로 지정할 수 있습니다.

Object.class 대신 CallStartedEvent.class를 사용하면 타입 안전성이 향상됩니다. 단, publishEvent의 오버로드된 메서드 때문에 현재 방식도 동작합니다.

♻️ 타입 안전성 개선 제안
 // then
-ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
-verify(eventPublisher).publishEvent(captor.capture());
-assertThat(captor.getValue()).isInstanceOf(CallStartedEvent.class);
+ArgumentCaptor<CallStartedEvent> captor = ArgumentCaptor.forClass(CallStartedEvent.class);
+verify(eventPublisher).publishEvent(captor.capture());
+CallStartedEvent capturedEvent = captor.getValue();
+assertThat(capturedEvent.callId()).isEqualTo(100L);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java`
around lines 171 - 173, In MatchingEventListenerTest replace the generic
ArgumentCaptor<Object> with a type-specific ArgumentCaptor<CallStartedEvent> and
call ArgumentCaptor.forClass(CallStartedEvent.class) so the captured value from
eventPublisher.publishEvent(...) is type-safe; update the captor variable
declaration and the forClass argument to CallStartedEvent.class while keeping
the verify(eventPublisher).publishEvent(captor.capture()) and the
assertThat(captor.getValue()).isInstanceOf(CallStartedEvent.class).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java`:
- Around line 191-214: The test MatchingEventListenerTest has a DisplayName
claiming it "schedules a rematch" but it never verifies any interaction with
matchingService; either add an assertion that matchingService was invoked (for
example verify(matchingService, times(1)).scheduleRematch(...) or
verify(matchingService).enqueueRematch(anyLong(), anyList()) matching the actual
method used by matchingEventListener.handleMatchingSuccess), or change the
DisplayName to remove the rematch claim; update the test to reference the exact
matchingService method name and expected arguments (e.g., call id or user list)
so the rematch scheduling behavior is explicitly verified.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 55-60: Update the "테스트 결과 업로드" upload-artifact step that uses
actions/upload-artifact@v4 to include the JUnit XML output path in addition to
the HTML report; include both build/reports/tests/test/ and
build/test-results/test/ (the JUnit XML directory) in the "path" so the artifact
contains HTML and JUnit XML results for downstream CI tools and reporting.

In
`@src/test/java/com/ldsilver/chingoohaja/listener/MatchingEventListenerTest.java`:
- Around line 171-173: In MatchingEventListenerTest replace the generic
ArgumentCaptor<Object> with a type-specific ArgumentCaptor<CallStartedEvent> and
call ArgumentCaptor.forClass(CallStartedEvent.class) so the captured value from
eventPublisher.publishEvent(...) is type-safe; update the captor variable
declaration and the forClass argument to CallStartedEvent.class while keeping
the verify(eventPublisher).publishEvent(captor.capture()) and the
assertThat(captor.getValue()).isInstanceOf(CallStartedEvent.class).

In
`@src/test/java/com/ldsilver/chingoohaja/scheduler/GracePeriodSchedulerTest.java`:
- Around line 60-68: Extract the duplicated private helper setId method into a
shared test utility: create a final utility class TestEntityUtils with a public
static setId(Object entity, Long id) method (matching the implementation in
GracePeriodSchedulerTest and MatchingEventListenerTest), remove the private
setId from both test classes, and update both GracePeriodSchedulerTest and
MatchingEventListenerTest to call TestEntityUtils.setId(...) so the reflection
logic is centralized and duplication is eliminated.

In
`@src/test/java/com/ldsilver/chingoohaja/service/RedisMatchingQueueServiceOperationTest.java`:
- Around line 94-127: Add a test that verifies Redis exceptions during dequeue
are handled like enqueue: stub redisTemplate.execute to throw (e.g., new
RuntimeException("Redis 연결 실패")) and call
RedisMatchingQueueService.dequeueUser(100L, 1L); assert the returned
DequeueResult has success() == false and message() equals
RedisMatchingConstants.ResponseMessage.REDIS_ERROR; place the test alongside the
other DequeueUser tests and use the same mocking pattern
(when(redisTemplate.execute(...)).thenThrow(...)) so dequeueUser properly maps
Redis failures to a failure result.

@nan0silver nan0silver changed the title [TEST] #6 [TEST] #6 통합 테스트 및 이벤트/스케줄러 Feb 19, 2026
@nan0silver nan0silver merged commit a6d363b into main Feb 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TEST] #6 통합 테스트 및 이벤트/스케줄러

1 participant