Skip to content

feat : 랭킹, 공부시간 측정 단위테스트 및 통합테스트 설정#63

Merged
Juhye0k merged 38 commits intodevfrom
testcode
Jan 8, 2026
Merged

feat : 랭킹, 공부시간 측정 단위테스트 및 통합테스트 설정#63
Juhye0k merged 38 commits intodevfrom
testcode

Conversation

@Juhye0k
Copy link
Copy Markdown
Contributor

@Juhye0k Juhye0k commented Jan 5, 2026

🚀 1. 개요

  • Testcontainers 기반 MySQL + Redis 통합 테스트 환경 구축
  • 단위 테스트 / 통합 테스트 역할 분리 및 안정성 강화
  • 하트비트 로직 제거에 따른 불필요 코드 정리

📝 2. 주요 변경 사항

✅ 테스트 인프라 개선

  • Testcontainers 도입
    • MySQL 8.0, Redis 7.0 컨테이너 기반 통합 테스트 환경 구성
    • BaseIntegrationTest 공통 베이스 클래스 추가
    • 테스트 종료 시 DB TRUNCATE + Redis FLUSH ALL 자동 수행
  • 테스트 프로필 분리
    • test : 통합 테스트 (MySQL + Redis)
    • unit-test : 단위 테스트 (H2 + Redis 비활성화)

✅ 단위 테스트 추가

  • StudySessionServiceTest
    • 공부 세션 시작 성공/실패 시나리오
    • Wi-Fi 검증 결과별 예외 처리
    • 공부 시간 계산 로직 (짧은 세션, 장시간, 자정 초과 등)
  • DepartmentRankServiceTest
  • PersonalRankServiceTest
  • CampusWiFiValidationServiceTest
    • VALID / INVALID / ERROR 시나리오별 검증

✅ 통합 테스트 추가

  • StudySessionIntegrationTest
    • 실제 Wi-Fi 설정 기반 세션 시작 검증
    • Redis 캐시 히트 / 미스 동작 검증
    • IP 대역 경계값 검증
  • DepartmentRankServiceIntegrationTest
    • 학과 랭킹 집계 및 fallback 랭킹 생성 검증

Summary by CodeRabbit

  • 버그 수정

    • 부서 랭킹에서 총 학습시간이 0 이하인 항목 제외 및 기간 집계의 종료 시간 처리 개선
  • 새로운 기능

    • 개인 랭킹에 부서명 한글 변환 기능 추가
    • 날짜 파라미터의 형식 처리 강화(엄격한 바인딩)
  • 제거된 기능

    • 하트비트(heartbeat) 관련 API/DTO/도메인 상태 전면 제거
    • 좀비 세션 탐지용 공개 메서드 제거
  • 테스트·설정

    • 통합/단위 테스트 베이스 및 다수의 테스트 추가, TestContainers 기반 테스트 환경 및 CI 테스트 흐름 개선
  • 리팩터

    • Redis 설정·직렬화 정리 및 소프트삭제 쿼리 인용 방식 수정

✏️ Tip: You can customize this high-level summary in your review settings.

@Juhye0k Juhye0k requested a review from kon28289 January 5, 2026 13:06
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 5, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Heartbeat 관련 API·도메인·DTO 제거, TestContainers 기반 통합 테스트 인프라 및 단위 테스트 베이스/모킹 추가, Redis 설정 리팩토링 및 기간 집계 쿼리 일부 캡핑/순위 로직 조정. (50자 이내)

Changes

코호트 / 파일(s) 변경 요약
빌드 & 의존성
build.gradle
테스트용 H2 제거, TestContainers(MySQL/Redis/JUnit) 의존 추가, Java 21 동적 에이전트 JVM 인수 추가
Redis 설정 리팩토링
src/main/java/.../global/config/redis/RedisConfig.java
RedisProperties 생성자 주입(@RequiredArgsConstructor), redisTemplate(RedisConnectionFactory) 시그니처 변경, String 직렬화 통일
Heartbeat 제거 — API/컨트롤러/서비스/도메인/DTO
src/main/java/.../study/api/StudySessionApi.java, src/main/java/.../study/controller/StudySessionController.java, src/main/java/.../study/service/StudySessionService.java, src/main/java/.../study/domain/StudySession.java, src/main/java/.../study/dto/request/HeartBeatRequest.java, src/main/java/.../study/dto/response/HeartBeatResponse.java
HeartBeat 관련 엔드포인트·서비스 메서드·도메인 필드(heartBeatAt)·요청/응답 DTO 전부 삭제
리포지토리 및 기간 계산 수정
src/main/java/.../study/repository/StudySessionRepository.java
좀비 세션 조회 메서드 제거, end_time NULL 시 기간 계산에서 LEAST(:now, :periodEnd)로 캡핑하여 기간 집계 변경
순위 로직 변경
src/main/java/.../rank/service/DepartmentRankService.java, src/main/java/.../rank/dto/PersonalRankingTemp.java
topRanks에 totalMillis > 0 필터 적용, getDepartmentKoreanName() 추가, myRanking fallback 계산을 필터된 리스트 기준으로 조정
테스트 인프라 — 통합/유닛 베이스 및 목 설정
src/test/java/.../integration/config/BaseIntegrationTest.java, src/test/java/.../unit/config/BaseUnitTest.java, src/test/java/.../unit/config/TestWiFiMockConfig.java
TestContainers 기반 MySQL/Redis 공유 컨테이너, DB/Redis 정리 로직, Wi‑Fi 검증 서비스 Mock 제공
단위 테스트 추가
src/test/java/.../unit/rank/service/DepartmentRankServiceTest.java, src/test/java/.../unit/rank/service/PersonalRankServiceTest.java, src/test/java/.../unit/study/service/StudySessionServiceTest.java, src/test/java/.../unit/wifi/service/CampusWiFiValidationServiceTest.java
순위 로직, 개인 순위, 공부 세션 시작/종료/시간계산, Wi‑Fi 검증 등 단위 테스트 대규모 추가
통합 테스트 추가/수정
src/test/java/.../GeumpumtaBackendApplicationTests.java, src/test/java/.../integration/rank/controller/DepartmentRankControllerIntegrationTest.java, src/test/java/.../integration/study/controller/StudySessionControllerIntegrationTest.java
BaseIntegrationTest 확장으로 통합 테스트 구성, MockMvc 기반 컨트롤러 통합 테스트 추가
테스트 설정 파일
src/test/resources/application-test.yml, src/test/resources/application-unit-test.yml
MySQL/HikariCP 설정 추가, unit-test 프로파일(H2/Redis 비활성) 추가, JWT/OAuth/Cloudinary 테스트값 보강
CI/CD 워크플로우 업데이트
.github/workflows/dev-ci.yml, .github/workflows/prod-ci.yml
TestContainers 이미지 선다운로드·Docker sanity 체크·실패 덤프 추가, 테스트·도커 단계 분리 및 멀티아치 빌드/푸시 구성
기타 정리
src/main/java/.../statistics/controller/StatisticsController.java
미사용 Lombok SLF4J 임포트 제거
엔티티 SQL 어노테이션 변경
src/main/java/.../user/domain/User.java
@SQLDelete 내부의 테이블명에 백틱 추가 (UPDATE user ...)

Sequence Diagram(s)

(생성 조건에 부합하지 않아 다이어그램을 생략합니다.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • kon28289

Poem

"나는 토끼, 코드 밭을 뛰네 —
하트비트는 사라지고 테스트는 샘솟네.
컨테이너 물결 속 DB가 웃고,
Redis는 단정히 제 자리를 지키네.
순위는 깔끔히 정리되어 춤추네. 🥕🐰"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경 사항을 명확하게 반영하고 있습니다: 랭킹과 공부시간 측정에 대한 단위테스트 및 통합테스트 설정이 정확히 설명되어 있습니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 충실히 따르고 있으며, 개요, 주요 변경 사항이 상세하게 기술되어 있습니다. 테스트 인프라, 단위테스트, 통합테스트 추가 내용이 명확합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7a55e and 3287637.

📒 Files selected for processing (1)
  • src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Juhye0k Juhye0k changed the title feat : 랭킹, 공부시간 측정 단위테스트 및 통합테스트 개발 feat : 랭킹, 공부시간 측정 단위테스트 및 통합테스트 설정 Jan 5, 2026
Copy link
Copy Markdown
Contributor

@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: 6

Fix all issues with AI Agents 🤖
In @build.gradle:
- Around line 66-69: The TestContainers dependencies in build.gradle are pinned
to the old 1.19.3 version; update the three coordinates
'org.testcontainers:junit-jupiter', 'org.testcontainers:mysql', and
'org.testcontainers:testcontainers' to use version 2.0.2 instead of 1.19.3 so
your testImplementation lines reference '...:2.0.2' to pick up the latest fixes
and features.

In
@src/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java:
- Line 29: 컨트롤러와 코드베이스에 남아있는 HeartBeat 잔여물을 정리하세요: StudySessionController에서
HeartBeatRequest 및 HeartBeatResponse 임포트(현재 미사용)를 제거하고 파일 내에서 관련 참조가 전혀 없는지
확인하세요; HeartBeatRequest.java와 HeartBeatResponse.java DTO 파일은 실제로 사용되지 않으면 삭제하거나
아카이브(또는 주석으로 보관)하고 변경사항을 커밋 메시지에 명시하세요; 엔티티 StudySession에서 heartBeatAt 필드와
updateHeartBeatAt() 메서드를 삭제하고, StudySessionRepository에서 해당 필드를 참조하는 모든 쿼리/메서드를
찾아서 제거 또는 수정하여 컴파일 오류가 나지 않도록 하세요; 변경 후 관련 유닛/통합 테스트와 빌드를 실행해 깨진 참조가 없는지 검증하세요.

In @src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java:
- Around line 9-12: BaseUnitTest is annotated with @ActiveProfiles("unit-test")
but no application-unit-test.* exists and the class comments mention H2 while
build.gradle lacks the H2 dependency; either create
application-unit-test.yml/properties and implement the BaseUnitTest class to
wire mocks and (optionally) H2 test config, or change the annotation to
@ActiveProfiles("test") and reuse application-test.yml while updating the top
comments to reflect the actual configuration; if you choose the new profile
option, also add H2 dependency to build.gradle (or remove H2 mentions) so
comments, profile name, and dependencies remain consistent with the
@ActiveProfiles setting.

In
@src/test/java/com/gpt/geumpumtabackend/unit/rank/service/DepartmentRankServiceTest.java:
- Around line 46-324: The issue is that GetCompletedDailyDepartmentRanking,
GetCurrentWeeklyDepartmentRanking, GetCurrentMonthlyDepartmentRanking,
BuildDepartmentRankingResponse and the helper methods (createTestUser,
createMockDepartmentRankingTemp) are accidentally defined inside the
GetCurrentDailyDepartmentRanking nested test class; fix by moving the closing
brace of GetCurrentDailyDepartmentRanking to immediately after its last @Test
method so those other nested classes and helper methods are declared at the same
level as GetCurrentDailyDepartmentRanking (i.e., as separate nested classes or
class-level methods of DepartmentRankServiceTest), ensuring symbols like
GetCurrentDailyDepartmentRanking, GetCompletedDailyDepartmentRanking,
GetCurrentWeeklyDepartmentRanking, GetCurrentMonthlyDepartmentRanking,
BuildDepartmentRankingResponse, createTestUser and
createMockDepartmentRankingTemp are top-level members of the test class rather
than nested inside another test class.

In
@src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java:
- Around line 272-298: The test 많은_랭킹_데이터에서_마지막_순위_종합 creates only 3
PersonalRankingTemp entries (largeRankings) but the comment and test name claim
"100명의 랭킹 데이터"; update the test to match intent by either changing the
comment/name or generating a larger list programmatically (e.g., fill
largeRankings with 100 createMockPersonalRankingTemp(...) calls), ensure the
expected assertion on response.myRanking().rank() is adjusted accordingly, and
keep references to the existing methods/variables
(createMockPersonalRankingTemp, largeRankings,
personalRankService.getCurrentDaily, notInRankingUserId) so the test reflects
the actual dataset size.

In
@src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java:
- Around line 22-23: 테스트에서 CampusWiFiValidationService
자체(wifiValidationService)를 @Mock 하고 있어 서비스 로직을 검증하지 못합니다; 대신
CampusWiFiValidationService를 @InjectMocks로 선언하고 그 의존성들(예: CampusWiFiSettings,
RedisTemplate<String,Object> 등 현재 서비스가 사용하는 필드들)을 @Mock으로 만들어 주입하도록 변경하세요 (즉,
제거할 대상: @Mock private CampusWiFiValidationService wifiValidationService; 추가/수정
대상: @Mock private CampusWiFiSettings campusWiFiSettings; @Mock private
RedisTemplate<String,Object> redisTemplate; @InjectMocks private
CampusWiFiValidationService wifiValidationService;), 그리고 필요 시 각 의존성에 대한
Mockito.when(...) 스텁을 설정해 실제 서비스 메서드 호출을 검증하도록 테스트를 수정하세요.
🧹 Nitpick comments (14)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (2)

73-88: 테스트 초기화 로직에 대한 개선 제안

  1. System.out.println 대신 SLF4J 로거 사용을 권장합니다.
  2. @BeforeEach@Transactional 조합 시, 트랜잭션이 각 테스트 메서드와 별도로 관리되어 예상치 못한 동작이 발생할 수 있습니다. DDL 확인만 필요하다면 @Transactional 제거를 고려해 주세요.
🔎 로깅 개선 제안
+import lombok.extern.slf4j.Slf4j;

+@Slf4j
 public abstract class BaseIntegrationTest {
     // ...
     
     @BeforeEach
-    @Transactional
     void ensureTablesExist() {
         try {
             entityManager.createNativeQuery("SELECT 1").getSingleResult();
-            System.out.println("EntityManager initialized - Hibernate DDL should be executed");
+            log.debug("EntityManager initialized - Hibernate DDL should be executed");
             
             jdbcTemplate.queryForObject("SELECT COUNT(*) FROM user LIMIT 1", Integer.class);
-            System.out.println("User table exists - DDL executed successfully");
+            log.debug("User table exists - DDL executed successfully");
         } catch (Exception e) {
-            System.out.println("Table check failed: " + e.getMessage());
+            log.error("Table check failed: {}", e.getMessage());
             throw new RuntimeException("Tables not created properly", e);
         }
     }

103-123: H2 분기 코드가 사용되지 않을 수 있습니다.

build.gradle에서 H2 의존성이 제거되었으므로, H2 관련 분기 로직(isH2 체크 및 관련 쿼리)이 실행되지 않습니다. 코드 간결성을 위해 제거하거나, 향후 H2 사용 가능성을 위해 유지할지 결정해 주세요.

src/test/resources/application-test.yml (1)

6-10: BaseIntegrationTest@DynamicPropertySource와 중복 설정이 있습니다.

BaseIntegrationTest에서 DynamicPropertySource를 통해 동일한 속성들을 동적으로 설정하고 있어, 이 YAML 설정은 런타임에 오버라이드됩니다. 혼란을 피하기 위해 둘 중 하나를 제거하거나, 각각의 역할을 명확히 문서화해 주세요.

src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java (1)

174-189: NULL/빈 입력값 테스트 구조

isNull()eq("") 매처를 사용한 stubbing은 정상 동작하지만, 실제 서비스 로직의 null 처리를 검증하지 못합니다. 위의 근본적인 이슈가 해결되면 이 테스트도 실제 예외 처리 동작을 검증할 수 있습니다.

src/test/java/com/gpt/geumpumtabackend/unit/config/TestWiFiMockConfig.java (1)

16-34: LGTM! 테스트용 WiFi Mock 설정

테스트 환경에서 Wi-Fi 검증을 제어하기 위한 적절한 설정입니다. @Primary를 통해 실제 빈을 대체합니다.

기본 fallback 동작을 추가하면 예상치 못한 입력에 대한 테스트 안정성이 향상됩니다:

🔎 기본 fallback 동작 추가 제안
         // 에러 시뮬레이션용 (특정 IP에서 에러 발생)
         when(mock.validateFromCache("error.test.ip", anyString()))
             .thenReturn(WiFiValidationResult.error("Redis 연결 실패 (Mock)"));
+            
+        // 기타 모든 요청에 대한 기본 동작 (invalid 반환)
+        when(mock.validateFromCache(anyString(), anyString()))
+            .thenReturn(WiFiValidationResult.invalid("알 수 없는 네트워크 (Mock)"));
             
         return mock;

주의: Mockito는 가장 구체적인 매처가 우선되므로, 순서에 주의하거나 구체적 매처를 먼저 설정해야 합니다.

src/test/java/com/gpt/geumpumtabackend/integration/rank/service/DepartmentRankServiceIntegrationTest.java (1)

403-422: 테스트 헬퍼 메서드

createAndSaveUsercreateCompletedStudySession 헬퍼가 잘 정의되어 있습니다. StudySessionIntegrationTest.java의 헬퍼와 유사한 패턴을 따르고 있어 일관성이 있습니다.

여러 통합 테스트에서 동일한 헬퍼 메서드를 사용하고 있다면, BaseIntegrationTest나 별도의 테스트 유틸리티 클래스로 추출하여 코드 중복을 줄일 수 있습니다.

src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java (2)

96-96: 사용되지 않는 변수

startTime 변수가 선언되었지만 사용되지 않습니다. 불필요한 코드이므로 제거하는 것이 좋습니다.

🔎 사용되지 않는 변수 제거
         Long userId = 1L;
         String gatewayIp = "192.168.10.1";
         String clientIp = "192.168.10.100";
-        LocalDateTime startTime = LocalDateTime.now();
         
         StudyStartRequest request = new StudyStartRequest(gatewayIp, clientIp);

Lines 120, 140에도 동일하게 적용됩니다.

Also applies to: 120-120, 140-140


281-287: 사용되지 않는 헬퍼 메서드

createTestStudySession 메서드가 정의되어 있지만 테스트에서 사용되지 않습니다. 향후 사용 계획이 없다면 제거하는 것이 좋습니다.

src/test/java/com/gpt/geumpumtabackend/integration/study/service/StudySessionIntegrationTest.java (2)

74-77: 예외 검증을 더 구체적으로 하면 좋겠습니다.

BusinessExceptionexceptionType 필드도 함께 검증하면 어떤 종류의 실패인지 명확히 확인할 수 있습니다. PersonalRankServiceTest에서처럼 hasFieldOrPropertyWithValue를 사용하는 것을 권장합니다.

🔎 제안하는 수정
         // When & Then - WiFi 검증 실패로 예외 발생
         assertThatThrownBy(() -> studySessionService.startStudySession(request, testUser.getId()))
-            .isInstanceOf(BusinessException.class);
+            .isInstanceOf(BusinessException.class)
+            .hasFieldOrPropertyWithValue("exceptionType", ExceptionType.INVALID_CAMPUS_NETWORK); // 실제 예외 타입에 맞게 수정

120-123: IP 범위 경계값 테스트의 정확성을 확인해주세요.

172.30.64.0/18 범위에서 네트워크 주소는 172.30.64.0이고 브로드캐스트 주소는 172.30.127.255입니다. 주석에서 "범위 내 첫 번째 IP"라고 한 172.30.64.1은 실제로 두 번째 사용 가능한 호스트 IP입니다. 네트워크 주소(172.30.64.0)와 브로드캐스트 주소(172.30.127.255)도 경계값 테스트에 포함하면 더 완전한 테스트가 됩니다.

src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java (4)

131-162: 완료된 일간 랭킹 테스트의 확장을 고려해보세요.

현재 정상 조회 케이스만 있습니다. 사용자가 랭킹에 없는 경우의 fallback 처리나 데이터가 없는 날짜 조회 등의 테스트도 추가하면 커버리지가 향상됩니다.


168-189: 주간 기간 계산 검증이 누락되어 있습니다.

테스트 이름은 "월요일부터 일요일까지의 기간으로 계산된다"고 하지만, 실제로 전달되는 날짜 범위를 검증하지 않습니다. ArgumentCaptor를 사용하여 calculateCurrentPeriodRanking에 전달되는 시작/종료 시간이 올바른지 검증하면 테스트의 신뢰도가 높아집니다.

🔎 ArgumentCaptor 사용 예시
@Test
@DisplayName("현재 주간 랭킹 조회 시 월요일부터 일요일까지의 기간으로 계산된다")
void getCurrentWeekly_정상조회_주간기간계산() {
    // Given
    Long userId = 1L;
    List<PersonalRankingTemp> mockRankingData = List.of(
        createMockPersonalRankingTemp(1L, "김철수", "profile1.jpg", "SOFTWARE", 25200000L, 1L)
    );
    
    ArgumentCaptor<LocalDateTime> startCaptor = ArgumentCaptor.forClass(LocalDateTime.class);
    ArgumentCaptor<LocalDateTime> endCaptor = ArgumentCaptor.forClass(LocalDateTime.class);
    
    given(studySessionRepository.calculateCurrentPeriodRanking(startCaptor.capture(), endCaptor.capture(), any()))
        .willReturn(mockRankingData);

    // When
    PersonalRankingResponse response = personalRankService.getCurrentWeekly(userId);

    // Then
    LocalDateTime capturedStart = startCaptor.getValue();
    LocalDateTime capturedEnd = endCaptor.getValue();
    
    // 시작일이 월요일인지 검증
    assertThat(capturedStart.getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY);
    // 종료일이 일요일인지 검증
    assertThat(capturedEnd.getDayOfWeek()).isEqualTo(DayOfWeek.SUNDAY);
}

309-326: Reflection 코드 중복을 제거하면 좋겠습니다.

createTestUser 헬퍼 메서드와 동일한 Reflection 로직이 중복됩니다. 헬퍼 메서드가 null department를 지원하도록 수정하거나, ID 설정을 위한 별도의 유틸리티 메서드를 추출하면 중복을 줄일 수 있습니다.

🔎 헬퍼 메서드 활용 예시
// 기존 createTestUser를 nullable department로 수정
private User createTestUser(Long id, String name, Department department) {
    User user = User.builder()
        .name(name)
        .email("test@kumoh.ac.kr")
        .department(department)  // null 허용
        // ... 나머지 필드
        .build();
    setIdViaReflection(user, id);
    return user;
}

private void setIdViaReflection(User user, Long id) {
    try {
        java.lang.reflect.Field idField = User.class.getDeclaredField("id");
        idField.setAccessible(true);
        idField.set(user, id);
    } catch (Exception e) {
        throw new RuntimeException("Failed to set test user ID", e);
    }
}

// 테스트에서 사용
User userWithNoDepartment = createTestUser(userId, "무소속사용자", null);

374-394: 동일한 테스트 케이스가 중복되어 있습니다.

이 테스트(랭킹에도_없고_DB에도_없는_사용자에게_USER_NOT_FOUND_예외가_발생한다)는 GetCurrentDaily 클래스의 getCurrentDaily_사용자없음_예외발생 테스트와 동일한 시나리오를 검증합니다. 하나로 통합하거나, ExceptionHandling 클래스에서는 다른 메서드(예: getCurrentWeekly, getCurrentMonthly)에 대한 예외 처리를 테스트하는 것을 고려해보세요.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e502120 and df56697.

📒 Files selected for processing (19)
  • build.gradle
  • src/main/java/com/gpt/geumpumtabackend/global/config/redis/RedisConfig.java
  • src/main/java/com/gpt/geumpumtabackend/rank/dto/PersonalRankingTemp.java
  • src/main/java/com/gpt/geumpumtabackend/rank/service/DepartmentRankService.java
  • src/main/java/com/gpt/geumpumtabackend/statistics/controller/StatisticsController.java
  • src/main/java/com/gpt/geumpumtabackend/study/api/StudySessionApi.java
  • src/main/java/com/gpt/geumpumtabackend/study/controller/StudySessionController.java
  • src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java
  • src/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/integration/rank/service/DepartmentRankServiceIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/integration/study/service/StudySessionIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/config/TestWiFiMockConfig.java
  • src/test/java/com/gpt/geumpumtabackend/unit/rank/service/DepartmentRankServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java
  • src/test/resources/application-test.yml
💤 Files with no reviewable changes (3)
  • src/main/java/com/gpt/geumpumtabackend/study/api/StudySessionApi.java
  • src/main/java/com/gpt/geumpumtabackend/statistics/controller/StatisticsController.java
  • src/main/java/com/gpt/geumpumtabackend/study/controller/StudySessionController.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-11T11:48:29.664Z
Learnt from: kon28289
Repo: Geumpumta/backend PR: 16
File: src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java:264-287
Timestamp: 2025-11-11T11:48:29.664Z
Learning: In the Geumpumta backend project, study session statistics (daily, weekly, monthly, grass) intentionally exclude in-progress sessions where end_time is NULL. Only completed sessions (with non-null end_time) should be counted in the statistics calculations.

Applied to files:

  • src/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java
  • src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java
🧬 Code graph analysis (3)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
  • SpringBootTest (24-146)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
  • SpringBootTest (6-13)
src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java (3)
src/test/java/com/gpt/geumpumtabackend/unit/rank/service/DepartmentRankServiceTest.java (5)
  • Nested (44-324)
  • Nested (109-141)
  • Nested (143-172)
  • Nested (174-203)
  • Nested (205-286)
src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java (6)
  • Nested (46-129)
  • Nested (131-162)
  • Nested (164-190)
  • Nested (192-218)
  • Nested (220-368)
  • Nested (370-395)
src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java (3)
  • Nested (25-87)
  • Nested (89-151)
  • Nested (153-190)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (28)
src/main/java/com/gpt/geumpumtabackend/rank/dto/PersonalRankingTemp.java (1)

24-34: LGTM! 방어적인 예외 처리가 잘 구현되었습니다.

getDepartmentKoreanName() 메서드가 null 체크와 IllegalArgumentException 처리를 통해 안전하게 enum 변환을 수행합니다. 잘못된 값에 대해 원본 문자열을 반환하는 fallback 로직도 적절합니다.

src/main/java/com/gpt/geumpumtabackend/rank/service/DepartmentRankService.java (2)

100-109: LGTM! 0시간 학과 필터링 로직이 적절합니다.

공부 시간이 0인 학과는 topRankings에서 제외하면서, 사용자의 학과는 공부 시간과 관계없이 myRanking으로 찾도록 분리된 로직이 잘 구현되었습니다.


111-118: 폴백 순위 계산이 필터링된 목록 크기를 올바르게 사용합니다.

활성 학과(공부 시간 > 0)만 포함된 topRankings.size() + 1을 사용하여 사용자 학과의 순위를 계산하는 것이 필터링 로직과 일관됩니다.

build.gradle (1)

74-76: Java 21 호환성을 위한 JVM 인자가 적절합니다.

-XX:+EnableDynamicAgentLoading은 Java 21에서 Mockito inline mock maker가 정상 작동하도록 필수적인 설정입니다.

src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (2)

34-62: TestContainers 설정이 잘 구성되었습니다.

MySQL과 Redis 컨테이너 설정, 그리고 @DynamicPropertySource를 통한 동적 속성 주입이 표준 패턴을 따르고 있습니다. WiFi 테스트 설정도 함께 구성되어 통합 테스트 환경이 완성되었습니다.


140-145: Redis 캐시 정리가 적절합니다.

try-with-resources를 사용하여 연결을 자동으로 닫고, flushAll()로 테스트 간 격리를 보장합니다. getConnectionFactory()가 null을 반환할 가능성은 테스트 컨텍스트에서 낮지만, 방어적 null 체크를 추가하면 더 안전합니다.

src/main/java/com/gpt/geumpumtabackend/global/config/redis/RedisConfig.java (2)

16-31: 생성자 주입과 조건부 비밀번호 설정이 잘 구현되었습니다.

@RequiredArgsConstructor를 통한 RedisProperties 주입은 @Value 필드 주입보다 테스트하기 쉽고 명시적입니다. 비밀번호가 비어있을 때 설정하지 않는 방어 로직도 적절합니다.


37-52: RedisTemplate 설정이 개선되었습니다.

RedisConnectionFactory를 파라미터로 주입받아 테스트 용이성이 향상되었습니다. 단일 StringRedisSerializer 인스턴스를 재사용하여 메모리 효율도 개선되었습니다.

src/test/resources/application-test.yml (2)

6-10: TestContainers 데이터소스 설정이 올바릅니다.

jdbc:tc:mysql:8.0:///geumpumta-test URL 형식이 TestContainers JDBC 드라이버에 적합합니다. 빈 비밀번호는 TestContainers MySQL 기본 설정과 호환됩니다.


100-112: WiFi 테스트 설정이 적절합니다.

캠퍼스 WiFi 검증을 위한 테스트 네트워크 설정이 완비되었습니다. CIDR 범위 172.30.64.0/18과 게이트웨이 172.30.64.1이 일관되게 구성되어 있습니다.

src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java (2)

51-51: LGTM! 진행 중인 세션의 시간 계산 경계 조건 수정

end_time IS NULL인 진행 중 세션에 대해 LEAST(:now, :periodEnd)를 사용하여 현재 시간과 기간 종료 시간 중 더 작은 값을 사용하도록 수정되었습니다. 이는 기간 종료 시간을 초과하는 시간이 집계되는 것을 방지합니다.

Also applies to: 61-61, 76-76


156-156: LGTM! 학과 랭킹 쿼리에도 동일한 경계 조건 수정이 일관되게 적용됨

calculateCurrentDepartmentRanking 쿼리에서도 개인 랭킹과 동일하게 LEAST(:now, :periodEnd) 로직이 적용되어 일관성이 유지됩니다.

Also applies to: 168-168

src/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java (1)

77-83: LGTM! WiFi 검증 예외 매핑 로직

mapWiFiValidationException 메서드에서 INVALID, ERROR, default 케이스를 적절히 처리하고 있습니다. Switch expression을 활용한 깔끔한 구현입니다.

src/test/java/com/gpt/geumpumtabackend/integration/rank/service/DepartmentRankServiceIntegrationTest.java (3)

116-117: 세션 종료 시간이 시작 시간보다 이전입니다

oneHourAgo(현재 - 1시간)가 시작 시간이고 thirtyMinAgo(현재 - 30분)가 종료 시간으로 설정되어 있어 종료 시간이 시작 시간보다 이후입니다. 코멘트에는 "30분"이라고 되어 있지만 실제로는 30분 학습이 됩니다.

코멘트와 실제 동작이 일치하지만, 변수명이 혼란스러울 수 있습니다. 의도가 맞는지 확인해 주세요:

  • oneHourAgothirtyMinAgo: 이동 시간 = 30분 (정상)

49-56: LGTM! 테스트 사용자 설정

@BeforeEach에서 여러 학과의 테스트 사용자를 생성하여 다양한 시나리오를 검증할 수 있도록 준비합니다.


299-334: 상위 30명 집계 로직 테스트

Top-30 제한 검증을 위해 35명의 사용자를 생성하고 등차수열 합 공식을 사용한 기대값 계산이 정확합니다:

  • (350 + 60) * 30 / 2 = 6,150분 = 369,000,000ms
src/test/java/com/gpt/geumpumtabackend/unit/rank/service/DepartmentRankServiceTest.java (1)

289-310: Reflection을 사용한 ID 설정 헬퍼

JPA 엔티티의 ID를 테스트에서 설정하기 위해 Reflection을 사용하는 것은 일반적인 패턴입니다. PersonalRankServiceTest와 동일한 패턴을 따르고 있어 일관성이 있습니다.

src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java (3)

37-47: LGTM! 적절한 Mock 구조

@Mock으로 의존성(Repository, Service)을 선언하고 @InjectMocks로 테스트 대상 서비스를 주입받는 올바른 단위 테스트 구조입니다.


163-254: LGTM! 공부시간 계산 로직 테스트

도메인 객체(StudySession)의 시간 계산 로직을 다양한 시나리오(정상, 1초, 12시간, 자정 경과)로 검증합니다. 경계값 테스트가 잘 포함되어 있습니다.


156-157: Heartbeat 관련 주석

Heartbeat 기능 제거에 대한 문서화 주석이 잘 작성되어 있어 코드 이력 이해에 도움이 됩니다.

src/test/java/com/gpt/geumpumtabackend/integration/study/service/StudySessionIntegrationTest.java (4)

21-33: LGTM!

클래스 구조와 의존성 주입이 적절하게 구성되어 있습니다. BaseIntegrationTest를 상속하여 Testcontainers 기반 통합 테스트 인프라를 활용하고 있습니다.


57-63: 동일 사용자가 연속으로 세션을 시작하는 동작을 확인해주세요.

두 번째 startStudySession 호출이 첫 번째 세션을 종료하지 않고 새 세션을 생성하는 것이 의도된 동작인지 확인이 필요합니다. 일반적으로 사용자당 하나의 활성 세션만 허용하거나, 기존 세션을 자동 종료하는 로직이 있을 수 있습니다.


92-112: 캐시 동작 테스트가 잘 구성되어 있습니다.

캐시 미스와 히트를 명확히 구분하여 검증하고 있습니다. 다만 메시지 문자열에 의존하는 방식은 메시지 변경 시 테스트가 깨질 수 있으므로, 가능하다면 별도의 캐시 히트 여부를 나타내는 플래그를 응답에 추가하는 것도 고려해볼 수 있습니다.


140-152: LGTM!

헬퍼 메서드가 고유한 providerId를 생성하여 테스트 격리를 보장합니다. DepartmentRankServiceIntegrationTest의 패턴과 일관성 있게 구현되어 있습니다.

src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java (4)

30-44: LGTM!

Mockito 확장을 사용한 단위 테스트 설정이 적절합니다. 필요한 의존성들이 올바르게 모킹되어 있습니다.


50-76: LGTM!

정상 조회 테스트가 잘 구성되어 있습니다. 응답의 주요 필드들을 적절히 검증하고 있으며, repository 호출도 verify하고 있습니다.


420-434: **createMockPersonalRankingTemp에서 잘못된 department 문자열 처리가 필요합니다.**현재 테스트 코드에서는 department 파라미터가 항상 유효한 Department enum 값("SOFTWARE", "COMPUTER_ENGINEERING" 등)으로 전달되지만, IllegalArgumentException은 "메서드에 적절하지 않은 인자가 전달될 때 발생하는 RuntimeException"이므로, 잘못된 문자열이 전달될 경우 Department.valueOf(department)에서 예외가 발생합니다. 테스트 코드이므로 현재는 문제없지만, 방어적 코딩 관점에서 참고하세요.


397-418: LGTM!

createTestUser 헬퍼 메서드가 잘 구현되어 있습니다. Reflection을 통한 ID 설정은 JPA 엔티티 테스트에서 일반적인 패턴입니다.

Copy link
Copy Markdown
Contributor

@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

Fix all issues with AI Agents 🤖
In
@src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java:
- Around line 91-106: In ensureTablesExist(), the raw SQL uses the reserved word
user and prints to stdout; update the jdbcTemplate query to quote the table name
(e.g., wrap with backticks or use the dialect-appropriate quoted identifier)
when calling jdbcTemplate.queryForObject("SELECT COUNT(*) FROM `user` LIMIT 1",
Integer.class) and replace System.out.println calls in ensureTablesExist() with
a logger (e.g., a private static final Logger) and use logger.info/error in the
try/catch and when reporting failures.
🧹 Nitpick comments (4)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (4)

71-72: Redis 포트 타입 확인이 필요합니다.

spring.data.redis.port는 일반적으로 Integer 타입을 기대하지만, 여기서는 toString()으로 String을 반환하고 있습니다. Spring의 타입 변환이 처리해주지만, 명시적으로 Integer를 반환하는 것이 더 안전합니다.

🔎 수정 제안
-        registry.add("spring.data.redis.port", () -> redisContainer.getMappedPort(6379).toString());
+        registry.add("spring.data.redis.port", () -> redisContainer.getMappedPort(6379));

114-156: H2 지원 코드가 포함되어 있지만, 이 클래스는 MySQL 컨테이너만 사용합니다.

truncateAllTables() 메서드에 H2 데이터베이스 지원 로직이 포함되어 있습니다. 그러나 BaseIntegrationTest는 MySQL Testcontainer만 사용하므로 H2 분기 코드는 실행되지 않습니다.

BaseUnitTest에서 H2를 사용한다면 해당 클래스에서 별도로 정리 로직을 구현하거나, 이 코드를 공통 유틸리티로 분리하는 것이 더 명확합니다. 현재 구조에서는 불필요한 복잡성을 추가합니다.


158-163: getConnectionFactory()가 null을 반환할 수 있습니다.

redisTemplate.getConnectionFactory()는 null을 반환할 수 있으며, 이 경우 NullPointerException이 발생합니다. 테스트 환경에서는 발생 가능성이 낮지만, 방어적 코딩을 권장합니다.

🔎 수정 제안
     private void cleanRedisCache() {
         // Redis의 모든 캐시 데이터 삭제 (Connection을 try-with-resources로 자동 close)
-        try (var connection = redisTemplate.getConnectionFactory().getConnection()) {
-            connection.serverCommands().flushAll();
+        var connectionFactory = redisTemplate.getConnectionFactory();
+        if (connectionFactory != null) {
+            try (var connection = connectionFactory.getConnection()) {
+                connection.serverCommands().flushAll();
+            }
         }
     }

43-49: MySQL 8.0에서 mysql_native_password는 deprecated 상태이며, 향후 업그레이드 시 주의가 필요합니다.

mysql_native_password는 MySQL 8.0.34부터 deprecated되었으며, MySQL 8.4에서는 기본적으로 비활성화되고, MySQL 9.0에서는 완전히 제거됩니다. 테스트 컨테이너는 현재 MySQL 8.0을 사용하므로 기능상 문제는 없지만, 향후 MySQL 버전 업그레이드 시를 대비하여 caching_sha2_password로 마이그레이션하는 것이 권장됩니다.

또한 withReuse(true)가 작동하려면 ~/.testcontainers.properties 파일(사용자 홈 디렉토리)에 testcontainers.reuse.enable=true 설정이 필요합니다. 이 설정이 없으면 매번 새로운 컨테이너가 생성됩니다. 단, 컨테이너 재사용은 로컬 개발 환경용이므로 CI 환경에서는 이를 비활성화하는 것이 좋습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4557dc9 and 7e88e91.

📒 Files selected for processing (2)
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/resources/application-test.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/resources/application-test.yml
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
  • SpringBootTest (6-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)

33-41: Testcontainers 기반 통합 테스트 인프라 구성이 잘 되어 있습니다.

정적 컨테이너를 사용하여 테스트 클래스 간 재사용을 활성화하고, @DynamicPropertySource로 동적 속성 바인딩을 구현한 것은 좋은 패턴입니다. create-drop DDL 전략과 @AfterEach 정리 로직을 통해 테스트 격리가 잘 보장됩니다.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (2)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (2)

38-44: MySQL 인증 플러그인 설정 확인 필요

--default-authentication-plugin=mysql_native_password는 MySQL 8.0.34부터 deprecated되었습니다. MySQL 8.0 기본 인증은 caching_sha2_password이며, MySQL Connector/J 8.0.x는 이를 지원합니다.

현재 테스트 환경에서는 동작하지만, 향후 호환성을 위해 해당 옵션 제거를 고려해보세요.

🔎 제안
 @Container
 static final MySQLContainer<?> mysqlContainer = new MySQLContainer<>("mysql:8.0")
         .withDatabaseName("test_geumpumta")
         .withUsername("test")
         .withPassword("test")
-        .withReuse(true)
-        .withCommand("--default-authentication-plugin=mysql_native_password");
+        .withReuse(true);

83-125: H2 분기 로직 필요성 검토

이 클래스는 Testcontainers의 MySQL 컨테이너를 사용하는 통합 테스트 베이스입니다. H2 관련 분기 로직(lines 93-94, 100-104, 117-118)은 실제로 실행되지 않을 것으로 보입니다.

단위 테스트는 별도의 BaseUnitTest를 사용하므로, 코드 간소화를 위해 H2 분기를 제거하는 것을 고려해보세요. 다만, 로컬 개발 환경에서 H2 fallback이 필요한 경우라면 현재 구조를 유지하셔도 됩니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f3d02b and febe868.

📒 Files selected for processing (1)
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
  • SpringBootTest (6-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (4)

28-36: LGTM!

테스트 설정이 적절합니다. spring.test.database.replace=NONE으로 DataSource 자동 교체를 비활성화하고 Testcontainers의 MySQL을 사용하도록 올바르게 구성되었습니다.


51-69: LGTM!

컨테이너 속성을 Spring 환경에 동적으로 바인딩하는 방식이 적절합니다. WiFi 테스트 설정도 함께 포함되어 있어 통합 테스트에서 필요한 모든 속성이 구성됩니다.


77-81: LGTM!

@AfterEach를 통한 테스트 후 정리 로직이 테스트 격리를 보장합니다. 테이블 truncate 후 Redis 캐시를 정리하는 순서도 적절합니다.


127-132: LGTM!

Redis 연결을 try-with-resources로 관리하여 리소스 누수를 방지합니다. flushAll()을 통해 테스트 간 Redis 데이터 격리가 보장됩니다.

Copy link
Copy Markdown
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/prod-ci.yml (1)

18-27: 워크플로우에서 정의된 Redis 서비스 컨테이너를 제거하세요.

18-27번 라인의 Redis 서비스는 실제 테스트에서 사용되지 않습니다. 통합 테스트는 Testcontainers를 통해 자체 Redis 컨테이너를 생성하고 @DynamicPropertySource로 해당 컨테이너의 연결 정보를 주입받으므로, 워크플로우에 정의된 서비스는 불필요한 리소스 낭비입니다. 이 서비스 정의를 제거하면 CI 성능을 개선할 수 있습니다.

.github/workflows/dev-ci.yml (1)

20-28: 워크플로우의 Redis 서비스는 사용되지 않습니다.

BaseIntegrationTest는 Testcontainers를 통해 Redis를 동적으로 프로비저닝하며, @DynamicPropertySource로 Redis 연결 정보를 주입합니다(redisContainer.getMappedPort(6379) 사용). 워크플로우 레벨의 redis 서비스는 빌드 중 실행되는 어떤 테스트에서도 참조되지 않습니다. 단위 테스트(unit-test 프로필)는 Redis를 완전히 비활성화합니다.

이 서비스 정의를 제거하면 CI 빌드 시간을 단축하고 리소스를 절약할 수 있습니다.

🧹 Nitpick comments (2)
src/test/java/com/gpt/geumpumtabackend/GeumpumtaBackendApplicationTests.java (1)

6-10: 단순 컨텍스트 로드 테스트에 통합 테스트 인프라 사용을 재고하세요.

BaseIntegrationTest를 상속하면 MySQL과 Redis Testcontainers가 시작되므로, 단순히 Spring 컨텍스트가 로드되는지 확인하는 이 smoke test의 실행 시간이 크게 증가합니다.

빠른 피드백을 위해 다음 방안을 고려해보세요:

  • 이 테스트는 BaseIntegrationTest 상속을 제거하고 @SpringBootTest(webEnvironment = WebEnvironment.NONE) 같은 가벼운 설정을 사용
  • 또는 실제 통합 테스트 검증이 필요한 경우에만 BaseIntegrationTest 사용
🔎 제안: 가벼운 smoke test로 리팩토링
 package com.gpt.geumpumtabackend;

-import com.gpt.geumpumtabackend.integration.config.BaseIntegrationTest;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

-class GeumpumtaBackendApplicationTests extends BaseIntegrationTest {
+@SpringBootTest(webEnvironment = WebEnvironment.NONE)
+class GeumpumtaBackendApplicationTests {

     @Test
     void contextLoads() {
     }

 }
.github/workflows/dev-ci.yml (1)

54-56: CI 환경에서 Ryuk 비활성화를 고려하세요.

Testcontainers의 Ryuk 컨테이너는 테스트 후 정리 작업을 수행하지만, GitHub Actions와 같은 CI 환경에서는 Docker-in-Docker 권한 문제로 인해 간헐적으로 실패할 수 있습니다. GitHub Actions는 작업 완료 후 자동으로 컨테이너를 정리하므로, CI 환경에서는 TESTCONTAINERS_RYUK_DISABLED: true로 설정하는 것이 일반적인 모범 사례입니다.

🔎 권장 수정사항
        env:
-          TESTCONTAINERS_RYUK_DISABLED: false
+          TESTCONTAINERS_RYUK_DISABLED: true
          TESTCONTAINERS_CHECKS_DISABLE: false
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between febe868 and 124c7ae.

📒 Files selected for processing (4)
  • .github/workflows/dev-ci.yml
  • .github/workflows/prod-ci.yml
  • src/test/java/com/gpt/geumpumtabackend/GeumpumtaBackendApplicationTests.java
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Copy link
Copy Markdown
Contributor

@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

Fix all issues with AI Agents 🤖
In
@src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java:
- Around line 80-85: Remove the @Transactional annotation from the cleanUp()
method in BaseIntegrationTest (the method named cleanUp() annotated with
@AfterEach) because TRUNCATE (DDL) implicitly commits in MySQL and cannot be
rolled back; leave @AfterEach, keep calling truncateAllTables() and
cleanRedisCache(), and ensure any required transaction management for test
setup/teardown is handled separately (e.g., in the methods truncateAllTables or
test utilities) rather than using @Transactional on cleanUp().
🧹 Nitpick comments (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)

87-129: H2 데이터베이스 지원 로직이 불필요할 수 있습니다.

이 클래스는 MySQL TestContainers를 사용하는 통합 테스트 전용입니다. H2 처리 로직(lines 94, 97-98, 104-108, 121-122)은 복잡도를 증가시키지만 실제로 사용되지 않습니다. 단위 테스트는 별도의 BaseUnitTest를 통해 H2를 사용합니다.

또한 디버깅을 위해 로거 추가를 권장합니다.

🔎 리팩토링 제안

1단계: H2 처리 로직 제거

     private void truncateAllTables() {
         try {
-            // Connection을 try-with-resources로 자동 close
-            String dbProductName;
-            try (var connection = jdbcTemplate.getDataSource().getConnection()) {
-                dbProductName = connection.getMetaData().getDatabaseProductName();
-            }
-            boolean isH2 = "H2".equalsIgnoreCase(dbProductName);
-
             // 외래 키 제약 조건 비활성화
-            if (isH2) {
-                jdbcTemplate.execute("SET REFERENTIAL_INTEGRITY FALSE");
-            } else {
-                jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS = 0");
-            }
+            jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS = 0");
 
-            List<String> tableNames;
-            if (isH2) {
-                tableNames = jdbcTemplate.queryForList(
-                    "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'PUBLIC' AND TABLE_TYPE = 'BASE TABLE'",
-                    String.class
-                );
-            } else {
-                tableNames = jdbcTemplate.queryForList(
-                    "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'",
-                    String.class
-                );
-            }
+            List<String> tableNames = jdbcTemplate.queryForList(
+                "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'",
+                String.class
+            );
 
             for (String tableName : tableNames) {
                 jdbcTemplate.execute("TRUNCATE TABLE `" + tableName + "`");
             }
 
             // 외래 키 제약 조건 재활성화
-            if (isH2) {
-                jdbcTemplate.execute("SET REFERENTIAL_INTEGRITY TRUE");
-            } else {
-                jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS = 1");
-            }
+            jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS = 1");
         } catch (Exception e) {
             throw new RuntimeException("Failed to truncate tables", e);
         }
     }

2단계: 로거 추가 (선택사항)

클래스 상단에 로거 추가:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class BaseIntegrationTest {
    private static final Logger log = LoggerFactory.getLogger(BaseIntegrationTest.class);

그런 다음 예외 처리에서 로깅 사용:

         } catch (Exception e) {
+            log.error("Failed to truncate tables", e);
             throw new RuntimeException("Failed to truncate tables", e);
         }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01be812 and 646f4d7.

📒 Files selected for processing (2)
  • build.gradle
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • build.gradle
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
  • SpringBootTest (6-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (2)

41-72: TestContainers 설정이 잘 구성되어 있습니다.

컨테이너 재사용을 위한 static 필드, CI 환경을 고려한 충분한 타임아웃 설정, 그리고 동적 프로퍼티 바인딩이 적절하게 구현되었습니다.


131-136: Redis 캐시 정리 로직이 올바르게 구현되었습니다.

try-with-resources를 사용한 연결 관리와 flushAll() 호출이 적절합니다.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (2)
.github/workflows/dev-ci.yml (2)

1-1: 워크플로우 이름이 실제 동작과 일치하지 않습니다.

이제 테스트가 별도의 job으로 추가되었으므로, 워크플로우 이름을 "Dev - CI (Test, Build & Push)"로 업데이트하는 것이 적절합니다. PR 설명에도 이렇게 명시되어 있습니다.

🔎 제안하는 수정
-name: Dev - CI (Build & Push)
+name: Dev - CI (Test, Build & Push)

15-18: GRADLE_OPTS 설정이 docker job에도 영향을 줍니다.

전역 환경 변수로 workers.max=1을 설정하면 docker job의 bootJar 빌드에도 적용되어 빌드 속도가 저하될 수 있습니다. 테스트 안정성이 필요한 test job에만 적용하는 것이 더 효율적입니다.

🔎 제안하는 수정

전역 env 블록을 제거하고 test job의 Run tests 단계에만 적용:

-env:
-  # CI에서 Testcontainers 안정성 확보(테스트 병렬 워커 제한)
-  GRADLE_OPTS: "-Dorg.gradle.workers.max=1"
-
 jobs:
   test:
       - name: Run tests
-        run: ./gradlew clean test --no-daemon --info
+        run: ./gradlew clean test --no-daemon --info -Dorg.gradle.workers.max=1
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6bcc17 and e32139f.

📒 Files selected for processing (3)
  • .github/workflows/dev-ci.yml
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/resources/application-test.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/resources/application-test.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (3)
.github/workflows/dev-ci.yml (3)

43-72: 테스트 인프라 구성이 잘 되어 있습니다.

Docker sanity check, 이미지 사전 pull, 실패 시 상태 덤프 등 Testcontainers 기반 테스트 환경을 위한 적절한 설정입니다. 특히 실패 시 디버깅을 위한 로그 덤프가 유용합니다.


74-104: Docker job 구성이 적절합니다.

needs: test로 테스트 통과 후에만 빌드가 진행되고, -x test로 테스트 재실행을 방지하는 것이 효율적입니다. PR 이벤트에서 빌드/푸시를 건너뛰는 것도 적절합니다.


127-137: 멀티 아키텍처 빌드 및 캐시 설정이 잘 되어 있습니다.

linux/amd64,linux/arm64 플랫폼 지원과 GHA 캐시(cache-from, cache-to)를 통한 빌드 최적화가 적절하게 구성되어 있습니다.

Copy link
Copy Markdown
Contributor

@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: 2

🤖 Fix all issues with AI agents
In @.github/workflows/dev-ci.yml:
- Around line 20-22: Add a timeout to the GitHub Actions jobs to avoid
long-running CI: set the timeout-minutes field on the test job (the job named
"test") to a sensible limit (e.g., 30–60) and likewise add timeout-minutes to
the Docker-related job (e.g., the job named "docker" or any job running
container setup/testcontainers) so both jobs will be automatically cancelled
after the specified minutes.
- Around line 55-58: Remove the CI step titled "Setup TestContainers
configuration" that creates ~/.testcontainers and writes
"testcontainers.reuse.enable=true"; delete that entire step from the workflow so
CI does not enable Testcontainers reuse (avoid testcontainers.reuse.enable=true)
and ensure no other workflow steps recreate that file or property.
🧹 Nitpick comments (1)
.github/workflows/dev-ci.yml (1)

15-17: Gradle 워커 제한의 필요성 검증 및 성능 영향 평가가 필요합니다.

현재 GRADLE_OPTS: "-Dorg.gradle.workers.max=1"은 의도적으로 Testcontainers 안정성을 위해 추가되었으나, 단일 워커로 제한할 경우 테스트 실행 시간이 크게 증가할 수 있습니다.

다음 항목의 검증을 권장합니다:

  • 실제 CI 환경에서 이 제한이 필수적인지 확인 (과거 안정성 문제가 해결되었을 가능성)
  • --max-workers=2 또는 --max-workers=4로 제한적 병렬화를 시도하여 성능/안정성 균형 평가
  • Testcontainers 1.19.3 버전에서 병렬 실행 관련 알려진 문제가 있는지 확인

참고: src/test/resources/testcontainers.properties의 주석에 "CI environments will ignore this setting"이라고 명시되어 있으나, 워크플로우 스크립트에서 ~/.testcontainers.properties를 다시 생성하므로 이 설정은 무시됩니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1efb329 and 6be60d4.

📒 Files selected for processing (1)
  • .github/workflows/dev-ci.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (2)
.github/workflows/dev-ci.yml (2)

80-88: Docker 작업 구조가 적절합니다.

Docker 빌드 및 푸시 작업이 올바르게 구성되었습니다:

  • 테스트 통과 후에만 실행 (needs: test)
  • PR 이벤트에서는 실행되지 않음 (불필요한 이미지 푸시 방지)
  • 테스트와 빌드/배포의 관심사 분리가 명확함

49-52: 사전 다운로드하는 이미지 버전이 테스트 코드와 일치합니다.

BaseIntegrationTest.java에서 테스트에 사용되는 이미지 버전은 mysql:8.0redis:7.0-alpine으로, CI 워크플로우에서 사전 다운로드하는 버전과 정확히 일치합니다. 이 단계는 적절하고 효과적입니다.

Copy link
Copy Markdown
Contributor

@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

🤖 Fix all issues with AI agents
In
@src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java:
- Around line 124-141: The DepartmentRankController.getDailyRanking method is
missing the @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) annotation on
its LocalDateTime request parameter, causing deserialization failures; update
the method signature in DepartmentRankController.getDailyRanking to add
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) on the date parameter
(matching PersonalRankController.getDailyRanking) so the incoming date query
param parses correctly.
🧹 Nitpick comments (3)
.github/workflows/prod-ci.yml (1)

60-64: TestContainers 설정 파일 경로 불일치

~/.testcontainers 디렉토리를 생성하지만 설정 파일은 ~/.testcontainers.properties로 홈 디렉토리에 직접 생성됩니다. TestContainers의 기본 설정 파일 위치는 ~/.testcontainers.properties가 맞으므로 디렉토리 생성은 불필요합니다.

🔎 수정 제안
      # TestContainers 설정 파일 생성 (컨테이너 재사용 활성화)
      - name: Setup TestContainers configuration
        run: |
-          mkdir -p ~/.testcontainers
          echo "testcontainers.reuse.enable=true" > ~/.testcontainers.properties
src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (1)

39-40: 사용되지 않는 ObjectMapper 필드

objectMapper 필드가 autowired 되어 있지만 이 테스트 클래스에서 사용되지 않습니다. 불필요한 의존성 주입을 제거하세요.

🔎 수정 제안
     @Autowired
     private MockMvc mockMvc;

-    @Autowired
-    private ObjectMapper objectMapper;
-
     @Autowired
     private JwtHandler jwtHandler;
src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (1)

37-37: 클래스 선언에 추가 공백

class StudySessionControllerIntegrationTest에 불필요한 공백이 있습니다.

🔎 수정 제안
-class  StudySessionControllerIntegrationTest extends BaseIntegrationTest {
+class StudySessionControllerIntegrationTest extends BaseIntegrationTest {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6be60d4 and c627e6d.

📒 Files selected for processing (5)
  • .github/workflows/prod-ci.yml
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java
  • src/test/resources/application-test.yml
🧰 Additional context used
🧬 Code graph analysis (3)
src/test/resources/application-test.yml (5)
src/main/java/com/gpt/geumpumtabackend/global/config/redis/RedisConfig.java (1)
  • Configuration (13-52)
src/main/java/com/gpt/geumpumtabackend/wifi/config/CampusWiFiProperties.java (1)
  • ConfigurationProperties (11-61)
src/main/java/com/gpt/geumpumtabackend/global/config/image/CloudinaryProperties.java (1)
  • Getter (7-14)
src/main/java/com/gpt/geumpumtabackend/global/oauth/user/KakaoProperties.java (1)
  • ConfigurationProperties (5-17)
src/main/java/com/gpt/geumpumtabackend/global/config/image/CloudinaryConfig.java (1)
  • Configuration (12-27)
src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (3)
src/main/java/com/gpt/geumpumtabackend/global/jwt/JwtHandler.java (1)
  • JwtHandler (22-110)
src/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java (1)
  • Service (27-122)
src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java (1)
  • Repository (17-537)
src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (1)
src/main/java/com/gpt/geumpumtabackend/global/jwt/JwtHandler.java (1)
  • JwtHandler (22-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (14)
.github/workflows/prod-ci.yml (1)

18-27: Redis 이미지 버전 불일치 확인 필요

CI 서비스에서는 redis:alpine을 사용하고, pre-pull 단계에서는 redis:7.0-alpine을 가져옵니다. TestContainers 테스트에서 redis:7.0-alpine을 사용하므로 pre-pull은 올바르지만, 서비스 컨테이너의 Redis 버전과 일치시키는 것이 좋습니다.

Also applies to: 54-58

src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (2)

96-122: 일간 랭킹 조회 테스트 LGTM

테스트 시나리오가 명확하고, 다양한 학과의 학습 기록을 생성한 후 랭킹 조회 및 내 학과 정보를 잘 검증하고 있습니다.


254-297: 전체 흐름 테스트 LGTM

Controller-Service-Repository 전체 흐름을 검증하는 테스트가 잘 구성되어 있습니다. 다른 사용자 토큰으로 조회하여 내 학과 정보가 다르게 표시되는 것을 검증하는 로직이 적절합니다.

src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (3)

91-133: truncateAllTables 구현 LGTM

H2와 MySQL 양쪽을 지원하는 테이블 truncate 로직이 잘 구현되어 있습니다. 외래 키 제약 조건을 비활성화/활성화하는 로직과 테이블명을 백틱으로 감싸는 처리가 적절합니다.


135-140: Redis 연결 리소스 관리 개선됨

try-with-resources를 사용하여 Redis 연결을 자동으로 close하는 것이 좋습니다.


41-55: TestContainers 컨테이너 설정 LGTM

MySQL 8.0과 Redis 7.0-alpine 컨테이너가 적절한 타임아웃과 재사용 옵션으로 구성되어 있습니다. CI 환경을 고려한 90초/60초 startup timeout 설정이 적절합니다.

src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (4)

77-103: 공부 시작 API 테스트 LGTM

Wi-Fi IP 정보를 포함한 요청, 응답 검증, 그리고 DB 상태 검증까지 포괄적으로 테스트하고 있습니다.


139-164: 공부 종료 테스트 시간 계산 검증

테스트에서 startTime을 2시간 전으로 설정하고 종료 시 totalMillis > 0을 검증합니다. 더 정확한 검증을 위해 대략적인 시간 범위(예: 약 2시간 = 7,200,000ms)를 확인하는 것이 좋을 수 있습니다. 현재 구현도 기능적으로는 충분합니다.


205-235: 다른 사용자 기록 격리 테스트 LGTM

다른 사용자의 공부 기록이 조회되지 않는 것을 검증하는 테스트가 적절합니다. 사용자 격리가 올바르게 동작하는지 확인하는 중요한 보안 테스트입니다.


242-288: 전체 흐름 테스트 LGTM

공부 시작부터 종료까지의 전체 흐름을 검증하며, API 응답과 DB 상태를 모두 확인하는 포괄적인 E2E 테스트입니다.

src/test/resources/application-test.yml (4)

6-17: Datasource 및 HikariCP 설정 LGTM

TestContainers가 URL을 동적으로 설정한다는 주석이 명확하고, HikariCP 풀 설정이 테스트 환경에 적합합니다.


94-98: JWT 시크릿 키 보안 개선됨

기존 짧은 플레이스홀더에서 256비트 이상의 시크릿 키로 변경되어 HS256 알고리즘 요구사항을 충족합니다.


107-110: Cloudinary 더미 설정

테스트용 더미 값으로 설정되어 있습니다. 테스트에서 실제 Cloudinary API 호출이 발생하지 않도록 해당 서비스를 mock 처리하거나, 이 설정으로 인해 테스트 실패가 발생하지 않는지 확인하세요.


112-124: 캠퍼스 WiFi 테스트 설정 LGTM

BaseIntegrationTest@DynamicPropertySource에서도 WiFi 설정을 제공하지만, 이 YAML 설정과 동적 설정이 함께 사용될 때 동적 설정이 우선합니다. 설정 간 일관성을 유지하는 것이 좋습니다.

Copy link
Copy Markdown
Contributor

@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

🤖 Fix all issues with AI agents
In @src/test/resources/application-test.yml:
- Around line 79-83: The Hibernate dialect setting properties.hibernate.dialect
currently uses the deprecated value org.hibernate.dialect.MySQL8Dialect; update
properties.hibernate.dialect to org.hibernate.dialect.MySQLDialect or remove the
properties.hibernate.dialect entry entirely so Hibernate 6.x can auto-detect the
DB version from the JDBC connection.
🧹 Nitpick comments (8)
.github/workflows/dev-ci.yml (4)

15-17: 워커 제한이 필요한지 검토하세요.

org.gradle.workers.max=1 설정은 테스트 병렬성을 완전히 제거하여 CI 실행 시간을 크게 증가시킵니다. Testcontainers는 적절한 격리로 2~4개의 워커를 처리할 수 있는 경우가 많습니다.

안정성 문제가 실제로 발생하지 않는다면, 더 높은 값(예: 2 또는 4)으로 실험해보는 것을 고려하세요.


54-55: --info 플래그가 필요한지 고려하세요.

--info 플래그는 상세한 로그를 생성하여 CI 출력을 복잡하게 만들 수 있습니다. 디버깅 시에만 필요하다면 기본 로그 레벨을 사용하거나, 실패 시에만 재실행하도록 고려하세요.


67-72: 로그 추출 로직을 단순화하는 것을 고려하세요.

복잡한 파이프라인은 일치하는 컨테이너가 없을 때 조용히 실패할 수 있습니다. 더 명확한 오류 처리를 위해 다음과 같이 단순화할 수 있습니다:

🔧 제안하는 개선 사항
-          echo "==== mysql logs (tail) ===="
-          docker ps -a --format "{{.ID}} {{.Image}}" | grep mysql | awk '{print $1}' | xargs -r -n1 docker logs --tail=200
-          echo "==== redis logs (tail) ===="
-          docker ps -a --format "{{.ID}} {{.Image}}" | grep redis | awk '{print $1}' | xargs -r -n1 docker logs --tail=200
+          echo "==== mysql logs (tail) ===="
+          for container in $(docker ps -a --filter "ancestor=mysql" --format "{{.ID}}"); do
+            echo "Container: $container"
+            docker logs --tail=200 "$container" 2>&1 || echo "Failed to get logs for $container"
+          done
+          echo "==== redis logs (tail) ===="
+          for container in $(docker ps -a --filter "ancestor=redis" --format "{{.ID}}"); do
+            echo "Container: $container"
+            docker logs --tail=200 "$container" 2>&1 || echo "Failed to get logs for $container"
+          done

135-135: 멀티 아키텍처 빌드가 필요한지 확인하세요.

linux/amd64,linux/arm64 빌드는 CI 시간을 크게 증가시킵니다. 두 플랫폼 모두 필요하지 않다면, 주요 아키텍처(amd64)만 빌드하거나 태그된 릴리스에서만 멀티 아키텍처를 활성화하는 것을 고려하세요.

src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (3)

11-11: 사용되지 않는 import를 제거하세요.

@Transactional 어노테이션이 제거되었으므로 해당 import 문도 함께 제거해야 합니다.

🧹 제안된 수정
-import org.springframework.transaction.annotation.Transactional;

92-101: System.err.println 대신 SLF4J 로거 사용을 권장합니다.

테스트 코드에서도 적절한 로깅 프레임워크를 사용하면 로그 레벨 제어와 CI 환경에서의 로그 관리가 용이해집니다.

🧹 제안된 수정

클래스 상단에 로거 선언 추가:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BaseIntegrationTest.class);

그리고 System.err.println 호출을 다음과 같이 변경:

-            System.err.println("Cleanup failed, but continuing: " + e.getMessage());
+            log.warn("Cleanup failed, but continuing: {}", e.getMessage());
-            System.err.println("Redis cleanup failed: " + e.getMessage());
+            log.warn("Redis cleanup failed: {}", e.getMessage());

Also applies to: 147-158


46-50: mysql_native_password 인증 플러그인은 MySQL 8.0.34부터 deprecated 상태입니다.

--default-authentication-plugin=mysql_native_password 옵션은 MySQL 8.0.34부터 deprecation 경고를 발생시키며, MySQL 8.4에서는 기본값에서 제외되고, MySQL 9.0에서 완전히 제거될 예정입니다. 향후 MySQL 버전 업그레이드 시 호환성 문제를 피하기 위해 caching_sha2_password로 마이그레이션을 권장합니다.

마이그레이션 시 주의: MySQL Connector/J는 8.0.9 이상에서 caching_sha2_password를 지원하지만, 보안 연결(TLS/SSL)을 사용하거나 RSA 공개 키 교환(AllowPublicKeyRetrieval)을 활성화해야 합니다. 현재 테스트 환경에서는 TLS 없이 사용 중이므로, 전환 시 JDBC 연결 설정 변경이 필요합니다.

src/test/resources/application-test.yml (1)

113-125: WiFi 네트워크 설정이 @DynamicPropertySource에 의해 오버라이드됩니다.

이 YAML 설정은 BaseIntegrationTest@DynamicPropertySource에서 다른 값(KUMOH_TEST)으로 오버라이드됩니다. 혼란을 방지하기 위해 값을 일치시키거나, 오버라이드된다는 주석을 추가하는 것을 고려해 보세요.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad8f2c6 and 6988dbd.

📒 Files selected for processing (3)
  • .github/workflows/dev-ci.yml
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/resources/application-test.yml
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
  • SpringBootTest (6-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (5)
.github/workflows/dev-ci.yml (1)

49-53: CI 워크플로우의 미리 pull한 Docker 이미지 버전이 테스트 설정과 일치합니다.

Testcontainers 설정을 확인한 결과, 다음과 같이 정확히 일치합니다:

  • MySQL: CI에서 mysql:8.0 미리 pull → 테스트에서 mysql:8.0 사용 ✓
  • Redis: CI에서 redis:7.0-alpine 미리 pull → 테스트에서 redis:7.0-alpine 사용 ✓

이 최적화는 예상대로 작동하며 추가 수정이 필요하지 않습니다.

Likely an incorrect or invalid review comment.

src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (2)

103-145: LGTM!

truncateAllTables 메서드가 잘 구현되었습니다. H2와 MySQL 모두를 처리하고, try-with-resources를 사용하여 connection을 안전하게 관리하며, 테이블명에 백틱을 사용하여 예약어 문제를 방지합니다.


41-84: TestContainers 설정이 잘 구성되었습니다.

MySQL과 Redis 컨테이너 설정, 대기 전략, 타임아웃 설정이 적절합니다. @DynamicPropertySource를 통한 프로퍼티 오버라이드도 올바르게 구현되었습니다.

src/test/resources/application-test.yml (2)

6-18: HikariCP 설정이 테스트 환경에 적합합니다.

테스트용으로 적절한 pool size와 timeout 값이 설정되어 있습니다. leak-detection-threshold를 통해 connection leak도 감지할 수 있습니다.


95-99: JWT secret key가 256-bit 요구사항을 충족합니다.

테스트용 secret key가 적절한 길이로 설정되었습니다.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (2)
src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (2)

37-38: @DirtiesContext 사용 및 포맷팅 이슈

  1. class와 클래스명 사이에 공백이 두 개 있습니다.
  2. @DirtiesContext는 테스트 후 Spring 컨텍스트를 재생성하므로 테스트 속도가 느려집니다. BaseIntegrationTest에서 DB TRUNCATE와 Redis FLUSH를 수행한다면 불필요할 수 있습니다. DepartmentRankControllerIntegrationTest에서는 이 어노테이션을 사용하지 않습니다.
♻️ 제안된 수정
-@org.springframework.test.annotation.DirtiesContext(classMode = org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_CLASS)
-class  StudySessionControllerIntegrationTest extends BaseIntegrationTest {
+class StudySessionControllerIntegrationTest extends BaseIntegrationTest {

99-103: findAll().get(0) 패턴은 취약합니다

findAll().get(0)은 테스트 격리가 실패하거나 다른 테스트에서 세션이 생성된 경우 잘못된 세션을 가져올 수 있습니다. 더 명확한 조회 방식을 사용하세요.

♻️ 제안된 수정
-            // DB 검증
-            StudySession savedSession = studySessionRepository.findAll().get(0);
-            assertThat(savedSession.getUser().getId()).isEqualTo(testUser.getId());
+            // DB 검증
+            var sessions = studySessionRepository.findAll();
+            assertThat(sessions).hasSize(1);
+            StudySession savedSession = sessions.get(0);
+            assertThat(savedSession.getUser().getId()).isEqualTo(testUser.getId());
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6988dbd and 199f3bf.

📒 Files selected for processing (1)
  • src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (2)
src/main/java/com/gpt/geumpumtabackend/global/jwt/JwtHandler.java (1)
  • JwtHandler (22-110)
src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (5)
  • DisplayName (32-338)
  • Nested (96-168)
  • Nested (170-210)
  • Nested (212-252)
  • Nested (254-337)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (5)
src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (5)

58-76: LGTM!

설정 메서드가 DepartmentRankControllerIntegrationTest와 동일한 패턴을 따르며, 테스트 사용자 생성 및 JWT 토큰 생성이 올바르게 구현되어 있습니다.


140-165: LGTM!

종료 테스트가 도메인 메서드를 올바르게 사용하고, DB 상태 검증(FINISHED, endTime, totalMillis)이 적절합니다.


172-192: 시간 기반 테스트의 잠재적 flakiness

LocalDateTime.now().withHour(10) 사용 시, 테스트가 오전 10시 이전에 실행되면 미래 시간의 세션이 생성됩니다. 이는 "오늘의 세션" 로직에 따라 예상치 못한 결과를 초래할 수 있습니다. 자정 근처에서 테스트가 실행될 경우 날짜 경계 문제도 발생할 수 있습니다.

현재 getTodayStudySession API가 미래 시간의 세션도 포함하는지 확인이 필요합니다. 만약 현재 시간 이전의 세션만 조회한다면 테스트가 실패할 수 있습니다.


206-236: LGTM!

사용자 데이터 격리 테스트가 잘 구현되어 있습니다. 다른 사용자의 공부 기록이 조회되지 않는 것을 검증하는 것은 중요한 보안 테스트입니다.


243-289: LGTM!

전체 흐름 테스트가 훌륭하게 구현되어 있습니다. 공부 시작부터 종료까지의 전체 라이프사이클을 커버하며, DB 검증도 철저합니다.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (1)
src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (1)

88-94: 타임스탬프 정밀도를 명시적으로 설정하는 것을 고려하세요.

테스트 데이터 생성 시 .withHour(10).withMinute(0) 만 설정하고 초와 나노초는 설정하지 않으면, 호출 시점의 초/나노초 값이 그대로 유지되어 테스트 실행마다 미묘하게 다른 타임스탬프가 생성될 수 있습니다. 테스트의 일관성을 위해 .withSecond(0).withNano(0) 추가를 고려해보세요.

♻️ 제안하는 개선안
 private void createStudySession(User user, LocalDateTime startTime, long durationHours) {
     LocalDateTime endTime = startTime.plusHours(durationHours);
 
     StudySession session = new StudySession();
-    session.startStudySession(startTime, user);
+    session.startStudySession(startTime.withSecond(0).withNano(0), user);
     session.endStudySession(endTime);
     studySessionRepository.save(session);
 }

또는 더 나은 방법으로, 호출하는 쪽에서 타임스탬프를 생성할 때 명시적으로 설정:

LocalDateTime today = LocalDateTime.now()
    .withHour(10).withMinute(0).withSecond(0).withNano(0);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 199f3bf and 70e71d7.

📒 Files selected for processing (1)
  • src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (2)
src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (2)

259-298: 우수한 종단 간 통합 테스트입니다.

학습 기록 생성부터 일간/주간/월간 랭킹 조회까지 전체 흐름을 체계적으로 검증하는 포괄적인 테스트입니다. 특히 여러 학과의 학생 데이터를 생성하고, 서로 다른 사용자 관점에서 topRanksmyDepartmentRanking이 올바르게 반환되는지 확인하는 부분이 훌륭합니다.


300-337: 사용자 간 랭킹 일관성 검증이 탁월합니다.

서로 다른 사용자가 동일한 topRanks 순위를 보지만 myDepartmentRanking은 각자의 학과 정보로 달라지는지 명확하게 검증합니다. 이는 랭킹 알고리즘의 공정성과 개인화가 동시에 올바르게 작동하는지 확인하는 중요한 테스트입니다.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (1)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)

11-11: 로거 사용을 권장합니다.

현재 Transactional import가 사용되지 않고 있으며(Line 11), 에러 로깅에 System.err.println을 사용하고 있습니다(Lines 100, 157). 테스트 환경에서도 구조화된 로깅을 사용하면 디버깅과 CI/CD 파이프라인에서의 추적이 용이합니다.

♻️ 제안하는 개선사항
 package com.gpt.geumpumtabackend.integration.config;
 
 import org.junit.jupiter.api.AfterEach;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.DynamicPropertyRegistry;
 import org.springframework.test.context.DynamicPropertySource;
-import org.springframework.transaction.annotation.Transactional;
 import org.testcontainers.containers.GenericContainer;

클래스 내부에 로거 추가:

 @Testcontainers
 @org.springframework.test.annotation.DirtiesContext(classMode = org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_CLASS)
 public abstract class BaseIntegrationTest {
+
+    private static final Logger log = LoggerFactory.getLogger(BaseIntegrationTest.class);

System.err를 로거로 교체:

         } catch (Exception e) {
             // 테스트 실패 시 cleanup도 실패할 수 있으므로 무시
-            System.err.println("Cleanup failed, but continuing: " + e.getMessage());
+            log.warn("Cleanup failed, but continuing: {}", e.getMessage());
         }
         } catch (Exception e) {
-            System.err.println("Redis cleanup failed: " + e.getMessage());
+            log.warn("Redis cleanup failed: {}", e.getMessage());
         }

Also applies to: 100-100, 157-157

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70e71d7 and 1b653e4.

📒 Files selected for processing (3)
  • src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java
  • src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (1)
src/test/java/com/gpt/geumpumtabackend/unit/config/BaseUnitTest.java (1)
  • SpringBootTest (6-13)
src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (1)
src/main/java/com/gpt/geumpumtabackend/global/jwt/JwtHandler.java (1)
  • JwtHandler (22-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (Gradle + Testcontainers)
🔇 Additional comments (8)
src/test/java/com/gpt/geumpumtabackend/integration/config/BaseIntegrationTest.java (2)

42-64: 컨테이너 설정이 적절합니다.

MySQL과 Redis 컨테이너의 타임아웃 설정(120초, 90초)과 대기 전략(로그 메시지 기반)이 CI 환경을 고려하여 잘 구성되어 있습니다. 컨테이너 재사용 비활성화도 CI 환경에서의 격리성을 보장하기에 적절합니다.


104-146: 테이블 정리 로직이 H2와 MySQL을 모두 지원합니다.

외래 키 제약 조건 처리와 테이블 목록 조회가 H2와 MySQL의 차이를 적절히 고려하고 있으며, try-with-resources를 사용한 리소스 관리도 올바릅니다.

src/test/java/com/gpt/geumpumtabackend/integration/study/controller/StudySessionControllerIntegrationTest.java (3)

57-75: 테스트 설정이 적절합니다.

사용자 생성과 JWT 토큰 생성이 올바르게 구성되어 있으며, @BeforeEach를 사용한 테스트 격리가 잘 되어 있습니다.


77-133: 인증 시나리오 테스트가 포괄적입니다.

정상 케이스뿐만 아니라 인증 누락(403)과 잘못된 토큰(401) 케이스까지 검증하고 있어, 보안 관련 엣지 케이스를 충분히 커버하고 있습니다.


243-288: 전체 흐름 테스트가 가치 있습니다.

시작부터 종료까지의 전체 라이프사이클을 검증하며, 중간 상태 조회와 최종 DB 상태 검증을 모두 포함하고 있어 통합 테스트로서 의미가 큽니다.

src/test/java/com/gpt/geumpumtabackend/integration/rank/controller/DepartmentRankControllerIntegrationTest.java (3)

74-94: 헬퍼 메서드가 테스트 가독성을 향상시킵니다.

createUsercreateStudySession 헬퍼 메서드를 통해 테스트 데이터 생성 로직을 재사용하고 있어, 테스트 코드의 중복을 줄이고 유지보수성을 높였습니다.


152-167: 빈 랭킹 시나리오 테스트가 중요합니다.

학습 기록이 없을 때 빈 topRanks와 기본값(rank=1, totalMillis=0)을 가진 myDepartmentRanking을 반환하는지 검증하여, 엣지 케이스를 적절히 커버하고 있습니다.


299-336: 사용자 간 랭킹 일관성 검증이 탁월합니다.

서로 다른 사용자가 조회했을 때 topRanks는 동일하지만 myDepartmentRanking은 각자의 학과를 반영하는지 검증하여, 비즈니스 로직의 정확성을 효과적으로 테스트하고 있습니다.

Juhye0k and others added 3 commits January 7, 2026 19:07
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@Juhye0k Juhye0k requested a review from kon28289 January 7, 2026 11:36
Copy link
Copy Markdown
Contributor

@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

🤖 Fix all issues with AI agents
In @.github/workflows/dev-ci.yml:
- Around line 75-79: The Docker job ("docker" job with name "Docker Build &
Push") lacks a timeout; add a timeout-minutes setting (e.g., timeout-minutes:
30) to the docker job definition—just like the test job—to prevent indefinite
runs during long multi-arch builds by inserting the timeout-minutes key at the
same level as runs-on/needs/if in the docker job stanza.
🧹 Nitpick comments (8)
src/main/java/com/gpt/geumpumtabackend/user/domain/User.java (1)

16-24: 테이블명 백틱 처리 - 좋은 방어적 코딩 관행입니다.

user는 MySQL 예약어이므로 백틱으로 감싸는 것이 충돌을 방지하는 모범 사례입니다. soft-delete 로직도 적절히 구현되어 있습니다.

다만, 코드베이스 전체의 일관성을 위해 다른 @Entity 클래스들의 @SQLDelete 어노테이션에서도 동일하게 테이블명을 백틱으로 감싸고 있는지 확인하는 것을 권장합니다.

🔍 일관성 검증 스크립트

다른 엔티티들도 동일한 패턴을 사용하는지 확인:

#!/bin/bash
# Description: @SQLDelete 어노테이션에서 테이블명 백틱 사용 여부 확인

# @SQLDelete 어노테이션을 사용하는 모든 파일 검색
rg -n -A 5 '@SQLDelete' --type java
.github/workflows/dev-ci.yml (1)

64-73: 실패 시 디버깅 덤프가 유용하게 구성되었습니다.

xargs -r 플래그로 컨테이너가 없을 때 안전하게 처리됩니다. 다만, 로그 수집 실패가 워크플로우 전체 실패로 이어지지 않도록 각 명령어 끝에 || true를 추가하는 것을 고려해 보세요.

♻️ 권장 수정사항
       - name: Dump docker state on failure
         if: failure()
         run: |
           echo "==== docker ps -a ===="
-          docker ps -a
+          docker ps -a || true
           echo "==== mysql logs (tail) ===="
-          docker ps -a --format "{{.ID}} {{.Image}}" | grep mysql | awk '{print $1}' | xargs -r -n1 docker logs --tail=200
+          docker ps -a --format "{{.ID}} {{.Image}}" | grep mysql | awk '{print $1}' | xargs -r -n1 docker logs --tail=200 || true
           echo "==== redis logs (tail) ===="
-          docker ps -a --format "{{.ID}} {{.Image}}" | grep redis | awk '{print $1}' | xargs -r -n1 docker logs --tail=200
+          docker ps -a --format "{{.ID}} {{.Image}}" | grep redis | awk '{print $1}' | xargs -r -n1 docker logs --tail=200 || true
.github/workflows/prod-ci.yml (1)

48-52: TestContainers 디버깅을 위한 좋은 추가입니다!

Docker 환경 진단 단계는 TestContainers 기반 테스트의 CI 실패를 디버깅할 때 유용합니다.

선택적 제안: 순수 진단 목적이라면 continue-on-error 고려

만약 이 단계가 순수하게 정보 수집 목적이라면 continue-on-error: true를 추가하여 Docker 명령 실패 시에도 빌드를 계속할 수 있습니다. 다만 TestContainers가 Docker를 필수로 요구하므로 현재 방식(실패 시 중단)도 합리적입니다.

 # Docker 환경 확인
 - name: Docker sanity check
+  continue-on-error: true
   run: |
     docker version
     docker info
src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java (3)

44-91: 엣지 케이스 테스트 추가를 검토해주세요.

현재 캐시 히트/미스의 정상 시나리오는 잘 검증되고 있습니다. 다만 이전 리뷰어(kon28289)의 지적처럼, 다음 엣지 케이스에 대한 테스트가 누락되어 있습니다:

  • null 또는 빈 문자열 입력값 처리 (gatewayIp, clientIp)
  • Redis 연결 오류 시 동작 (valueOperations.get()이 예외를 던지는 경우)
  • 캐시에 잘못된 형식의 값이 있는 경우

이러한 시나리오를 추가하면 서비스의 견고성을 더 잘 검증할 수 있습니다.

다음 예외 시나리오 테스트를 추가하시겠습니까?

@Test
@DisplayName("null_입력값은_예외를_발생시킨다")
void null입력값_예외발생() {
    // Given
    String gatewayIp = null;
    String clientIp = "192.168.1.100";
    
    // When & Then
    assertThatThrownBy(() -> wifiValidationService.validateFromCache(gatewayIp, clientIp))
        .isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("Redis_오류_시_실제_검증으로_폴백한다")
void Redis오류_폴백동작() {
    // Given
    String gatewayIp = "192.168.1.1";
    String clientIp = "192.168.1.100";
    
    given(redisTemplate.opsForValue()).willReturn(valueOperations);
    given(valueOperations.get(anyString())).willThrow(new RuntimeException("Redis connection failed"));
    
    // 실제 검증 로직 설정
    CampusWiFiProperties.WiFiNetwork network = mock(CampusWiFiProperties.WiFiNetwork.class);
    given(wifiProperties.networks()).willReturn(List.of(network));
    given(wifiProperties.validation()).willReturn(new CampusWiFiProperties.ValidationConfig(5));
    given(network.active()).willReturn(true);
    given(network.isValidGatewayIP(gatewayIp)).willReturn(true);
    given(network.isValidIP(clientIp)).willReturn(true);
    
    // When
    WiFiValidationResult result = wifiValidationService.validateFromCache(gatewayIp, clientIp);
    
    // Then
    assertThat(result.isValid()).isTrue();
}

79-79: ValidationConfig의 TTL 값 중복을 상수로 추출하는 것을 고려해주세요.

new CampusWiFiProperties.ValidationConfig(5)가 여러 테스트(lines 79, 106, 130)에서 반복됩니다. 테스트 가독성과 유지보수를 위해 클래스 레벨 상수로 추출하는 것을 고려해보세요.

♻️ 제안하는 리팩토링
 @ExtendWith(MockitoExtension.class)
 @ActiveProfiles("unit-test")
 @DisplayName("CampusWiFiValidationService 단위 테스트")
 class CampusWiFiValidationServiceTest {
+
+    private static final int TEST_CACHE_TTL_MINUTES = 5;

     @Mock
     private CampusWiFiProperties wifiProperties;

그리고 각 테스트에서:

-    given(wifiProperties.validation()).willReturn(new CampusWiFiProperties.ValidationConfig(5));
+    given(wifiProperties.validation()).willReturn(new CampusWiFiProperties.ValidationConfig(TEST_CACHE_TTL_MINUTES));

89-89: 캐시 TTL 검증을 구체적으로 수행하는 것을 고려해주세요.

Line 89에서 any()를 사용하여 캐시 TTL 파라미터를 검증하고 있습니다. 더 엄격한 테스트를 위해 실제 ValidationConfig에서 설정한 TTL 값과 일치하는지 확인할 수 있습니다.

♻️ 제안하는 개선
+    import java.time.Duration;
+    import static org.mockito.ArgumentMatchers.eq;

     // Then
     assertThat(result.isValid()).isTrue();
-    verify(valueOperations).set(eq(cacheKey), eq("true"), any());
+    verify(valueOperations).set(eq(cacheKey), eq("true"), eq(Duration.ofMinutes(5)));
src/main/java/com/gpt/geumpumtabackend/rank/controller/DepartmentRankController.java (1)

33-33: 현재 구현은 정상 작동하지만 날짜 타입으로 변경을 권장합니다

통합 테스트에서 .toString()으로 생성된 전체 datetime 문자열(2024-01-15T10:00:00 형식)을 date 파라미터로 전송하고 있어, 현재 ISO.DATE_TIME 형식은 의도대로 작동합니다.

다만, 매개변수명(startDay, weekFirstDay, monthFirstDay)이 날짜 기반 쿼리를 시사하는데 비해 실제로는 전체 datetime을 요구하므로, 다음과 같이 개선하면 API 의도를 더 명확하게 나타낼 수 있습니다:

  • LocalDate + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 사용
  • 서비스 메서드에서 date.atStartOfDay()로 변환
  • 클라이언트는 날짜만 전달 가능 (예: 2024-01-15)

이 변경은 필수가 아니지만, 일간/주간/월간 랭킹 조회의 의도를 더 정확히 반영합니다.

src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java (1)

131-218: 주간/월간 랭킹 테스트의 커버리지를 확장하는 것을 고려해보세요.

GetCompletedDaily, GetCurrentWeekly, GetCurrentMonthly 클래스는 각각 정상 케이스만 테스트하고 있습니다. GetCurrentDaily에서 테스트한 fallback 시나리오나 예외 케이스가 이 메서드들에도 적용된다면, 유사한 테스트를 추가하는 것이 테스트 완결성을 높일 수 있습니다.

다만 구현체에서 동일한 로직을 재사용하고 있다면 현재 수준의 테스트로도 충분할 수 있습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b653e4 and 1f7a55e.

📒 Files selected for processing (9)
  • .github/workflows/dev-ci.yml
  • .github/workflows/prod-ci.yml
  • src/main/java/com/gpt/geumpumtabackend/rank/controller/DepartmentRankController.java
  • src/main/java/com/gpt/geumpumtabackend/user/domain/User.java
  • src/test/java/com/gpt/geumpumtabackend/unit/rank/service/DepartmentRankServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java
  • src/test/resources/application-test.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/com/gpt/geumpumtabackend/unit/study/service/StudySessionServiceTest.java
  • src/test/java/com/gpt/geumpumtabackend/unit/rank/service/DepartmentRankServiceTest.java
🔇 Additional comments (16)
.github/workflows/dev-ci.yml (5)

15-17: Gradle 워커 제한 설정 확인

Testcontainers 안정성을 위해 워커를 1개로 제한하는 것은 리소스 경쟁을 줄이지만, 테스트 실행 시간이 크게 증가할 수 있습니다. 현재 테스트 규모가 작다면 괜찮지만, 테스트가 늘어날 경우 2-4개로 조정을 고려해 보세요.


20-23: LGTM!

테스트 작업에 30분 타임아웃이 적절하게 추가되었습니다. 이전 리뷰 피드백이 반영되었습니다.


44-56: 테스트 인프라 설정이 잘 구성되었습니다.

  • Docker sanity check로 환경 검증
  • 이미지 미리 pull로 네트워크 간헐적 이슈 감소
  • --no-daemon --info 옵션으로 디버깅 용이

103-105: LGTM!

테스트를 건너뛰고 bootJar만 빌드하는 것은 올바른 접근입니다. 이전 test 작업에서 이미 테스트를 완료했으므로 중복 실행을 방지합니다.


128-138: 멀티 아키텍처 빌드 및 캐싱 설정이 적절합니다.

  • linux/amd64,linux/arm64 플랫폼 지원
  • GHA 캐시 활용으로 빌드 시간 단축
  • 메타데이터 기반 태깅
.github/workflows/prod-ci.yml (1)

54-58: DockerImage 버전이 TestContainers 설정과 정확히 일치합니다.

검증 결과:

  • MySQL: Pre-pull의 mysql:8.0과 BaseIntegrationTest의 MySQLContainer 설정이 일치 ✓
  • Redis: Pre-pull의 redis:7.0-alpine과 BaseIntegrationTest의 GenericContainer 설정이 일치 ✓
  • Redis 버전 차이는 의도된 설계입니다: 서비스의 redis:alpine(일반 테스트용)과 TestContainers의 redis:7.0-alpine(통합 테스트용)이 서로 다른 목적으로 분리되어 있습니다.

TestContainers 이미지를 미리 pull하여 테스트 실행 시간을 단축하는 최적화 방식이 올바르게 구현되었습니다.

src/test/java/com/gpt/geumpumtabackend/unit/wifi/service/CampusWiFiValidationServiceTest.java (1)

28-38: Mock 설정이 올바르게 수정되었습니다.

이전 리뷰에서 지적된 문제(서비스 자체를 Mock하던 것)가 수정되어, 이제 서비스의 의존성(CampusWiFiProperties, RedisTemplate, ValueOperations)을 Mock하고 @InjectMocks로 실제 서비스 로직을 테스트하는 구조로 변경되었습니다.

src/test/resources/application-test.yml (3)

106-106: Apple 인증 테스트 시 private-key 형식을 확인해주세요.

현재 private-key: dummy-private-key는 단일 라인 문자열로 설정되어 있습니다. Apple 인증이 실제로 동작해야 하는 통합 테스트에서는 이 형식이 유효하지 않을 수 있습니다. 만약 Apple 인증 로직을 테스트할 계획이라면, 유효한 형식의 개인키(PEM 형식 등)를 사용하거나 해당 테스트를 Mock으로 처리하는 것을 고려해주세요.

단순히 더미 설정으로 충분하다면 현재 상태로 문제없습니다.


113-125: 캠퍼스 WiFi 테스트 설정이 잘 구성되었습니다.

WiFi 네트워크 설정이 명확하게 구조화되어 있습니다:

  • CIDR 표기법(172.30.64.0/18)이 올바르게 사용되었습니다
  • 테스트용으로 적절한 짧은 캐시 TTL(5분) 설정
  • 네트워크 활성화 및 설명이 명시되어 있습니다

이 설정은 CampusWiFiValidationServiceTest의 단위 테스트 및 통합 테스트와 잘 연동될 것입니다.


76-84: JPA 설정이 이전 리뷰를 반영하여 올바르게 수정되었습니다.

  • Line 81: MySQLDialect로 변경되어 Hibernate 6.x의 deprecation 경고가 해결되었습니다
  • Line 83: 테스트 환경에서 show-sql: false는 로그 노이즈를 줄이는 적절한 선택입니다
src/main/java/com/gpt/geumpumtabackend/rank/controller/DepartmentRankController.java (1)

10-10: LGTM!

@DateTimeFormat 어노테이션 사용을 위해 필요한 import입니다.

src/test/java/com/gpt/geumpumtabackend/unit/rank/service/PersonalRankServiceTest.java (5)

1-44: 테스트 설정이 잘 구성되어 있습니다.

Mockito를 사용한 단위 테스트 설정이 적절하며, 필요한 의존성들이 올바르게 모킹되어 있습니다.


46-129: 현재 일간 랭킹 조회 테스트가 포괄적으로 작성되었습니다.

정상 케이스, fallback 로직, 예외 처리를 모두 테스트하고 있으며, given-when-then 구조가 명확합니다.


220-368: Fallback 로직 테스트가 매우 포괄적이고 잘 작성되었습니다.

다양한 edge case(빈 랭킹, 부서 없는 사용자, 랭킹 외 사용자 등)를 꼼꼼히 검증하고 있습니다. Line 278의 주석도 이전 리뷰 피드백을 반영하여 "3명의 랭킹 데이터 생성"으로 수정되어 실제 데이터와 일치합니다.


370-395: 예외 처리 테스트가 적절합니다.

사용자가 랭킹과 DB 모두에 존재하지 않을 때 USER_NOT_FOUND 예외가 정확히 발생하는지 검증하고 있습니다.


397-434: 헬퍼 메서드가 잘 구성되어 있습니다.

테스트 데이터 생성 로직이 재사용 가능하도록 잘 분리되어 있습니다. Reflection을 사용한 ID 설정(lines 409-416)은 JPA 엔티티 테스트에서 흔히 사용되는 패턴이며 현재 방식으로 충분합니다. Line 425의 한글 주석도 stubbing 제거 이유를 명확히 설명하고 있어 좋습니다.

}

@Test
@DisplayName("긴 세션(12시간)도 올바르게 계산된다")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

서비스에서 정한 한 번에 측정할 수 있는 최대 시간은 3시간인데 12시간을 측정하는 테스트는 적절하지 않은 것 같아요.

Copy link
Copy Markdown
Contributor

@kon28289 kon28289 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다! 커멘트 달아둔거 읽어봐주세요

@Juhye0k Juhye0k merged commit 9909d3b into dev Jan 8, 2026
1 of 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.

2 participants