test: CourseService 단위 테스트 추가 - #213
Conversation
createCourse/getCourseByUser/getPrivateCourseByUser/getCourseDetail/updateCourse/ deleteCourses 전체 메서드에 대해 정상 케이스 + 예외 케이스 + 경계값을 검증. 테스트 작성 중 실제 프로덕션 코드에서 3가지 의심되는 부분을 발견해 별도로 표시해둠: - createCourse: 출발지 주소가 3토큰 미만이면 DepartureConverter가 null을 반환하고 이후 NPE로 이어짐 (요청값 검증 부재) - updateCourse: courseId로만 조회하고 userId로 소유자 검증을 하지 않아, 다른 사람의 코스 제목도 수정 가능 (IDOR 의심) - getCourseDetail: RunnectUser가 equals/hashCode를 오버라이드하지 않아 isNowUser 판정이 참조 동일성에 의존함 (같은 id라도 인스턴스가 다르면 다른 사람으로 판정될 수 있음)
📝 WalkthroughWalkthroughAdded comprehensive Mockito and JUnit coverage for ChangesCourseService test coverage
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/test/java/org/runnect/server/course/service/CourseServiceTest.java (3)
93-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSet
isPrivateexplicitly inbuildCourse.The helper only writes
isPrivatewhen the argument isfalse. The private case depends on theCourseentity default. If that default changes, the tests keep passing while the fixture state is wrong.♻️ Proposed change
ReflectionTestUtils.setField(course, "id", id); - if (!isPrivate) { - ReflectionTestUtils.setField(course, "isPrivate", false); - } + ReflectionTestUtils.setField(course, "isPrivate", isPrivate); return course;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/org/runnect/server/course/service/CourseServiceTest.java` around lines 93 - 111, Update the buildCourse helper to explicitly set the Course isPrivate field from the isPrivate argument for both private and public fixtures, removing reliance on the entity default while preserving the existing course construction.
175-184: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTwo tests assert suspected production defects as expected behavior. Both tests pass because the production code is defective. After a fix, both fail and look like regressions instead of confirmations. At each site, either mark the test
@Disabledwith a tracking reference, or assert the intended behavior so the failure marks the open defect.
src/test/java/org/runnect/server/course/service/CourseServiceTest.java#L175-L184: replace theNullPointerExceptionassertion withBadRequestException, or disable the test and link thedepartureAddressvalidation defect.src/test/java/org/runnect/server/course/service/CourseServiceTest.java#L294-L307: replaceisFalse()withisTrue()for the same-id uploader, or disable the test and link the id-comparison defect inCourseService.getCourseDetail.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/org/runnect/server/course/service/CourseServiceTest.java` around lines 175 - 184, The tests currently encode suspected production defects as expected behavior. In src/test/java/org/runnect/server/course/service/CourseServiceTest.java:175-184, update 출발지_주소가_불완전하면_NPE() to expect BadRequestException for invalid departureAddress, or disable it with a tracking reference; in the same file:294-307, change the same-id uploader assertion from isFalse() to isTrue() for CourseService.getCourseDetail, or disable it with a tracking reference for the id-comparison defect.
453-466: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winClarify the partial-delete assertion scope.
CourseService.deleteCoursesis@Transactional, so the mocked in-memorycourse1.setDeletedAt()state may not persist with a real database. Add a comment that this assertion covers only failed mocked-entity state, and cover the rollback behavior with an integration test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/org/runnect/server/course/service/CourseServiceTest.java` around lines 453 - 466, The test method 중간에_실패하면_이후_항목은_처리되지_않는다의 course1.getDeletedAt() 검증이 mocked in-memory 엔티티 상태만 확인한다는 주석을 추가하고, 실제 트랜잭션 롤백 여부는 CourseService.deleteCourses를 사용하는 통합 테스트에서 검증하도록 보완한다.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/test/java/org/runnect/server/course/service/CourseServiceTest.java`:
- Around line 294-307: Update 같은_id여도_인스턴스가_다르면_다른_사람으로_판정된다() to assert
isNowUser is true for distinct RunnectUser instances sharing the same id, so the
test captures the intended identity-by-id rule and fails until CourseService is
corrected.
---
Nitpick comments:
In `@src/test/java/org/runnect/server/course/service/CourseServiceTest.java`:
- Around line 93-111: Update the buildCourse helper to explicitly set the Course
isPrivate field from the isPrivate argument for both private and public
fixtures, removing reliance on the entity default while preserving the existing
course construction.
- Around line 175-184: The tests currently encode suspected production defects
as expected behavior. In
src/test/java/org/runnect/server/course/service/CourseServiceTest.java:175-184,
update 출발지_주소가_불완전하면_NPE() to expect BadRequestException for invalid
departureAddress, or disable it with a tracking reference; in the same
file:294-307, change the same-id uploader assertion from isFalse() to isTrue()
for CourseService.getCourseDetail, or disable it with a tracking reference for
the id-comparison defect.
- Around line 453-466: The test method 중간에_실패하면_이후_항목은_처리되지_않는다의
course1.getDeletedAt() 검증이 mocked in-memory 엔티티 상태만 확인한다는 주석을 추가하고, 실제 트랜잭션 롤백
여부는 CourseService.deleteCourses를 사용하는 통합 테스트에서 검증하도록 보완한다.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0336c746-7efc-46cc-83b4-631b3fa9e164
📒 Files selected for processing (1)
src/test/java/org/runnect/server/course/service/CourseServiceTest.java
1. DepartureConverter: 출발지 주소가 3토큰 미만이면 null 대신 BadRequestException(VALIDATION_DEPARTURE_ADDRESS_EXCEPTION)을 던지도록 변경 (기존엔 CourseService에서 바로 NPE로 이어짐) 2. CourseService.updateCourse: findById → findByCourseIdAndUserId로 변경해 본인 소유 코스만 수정 가능하도록 수정 (IDOR 방지, deleteCourses와 동일 패턴) 3. RunnectUser: equals/hashCode를 id 기준으로 구현. 기존엔 참조 동일성에 의존해 같은 유저라도 인스턴스가 다르면(Course.isMatchedUser, RecordService, PublicCourseService 등에서) 다른 사람으로 오판정될 수 있었음 CourseServiceTest의 관련 3개 테스트를 수정된 동작에 맞게 갱신하고, RunnectUserTest를 새로 추가해 equals/hashCode 자체를 검증.
배경
테스트 커버리지 확장 2단계. 앱 핵심 도메인인
CourseService(코스 생성/조회/수정/삭제) 전체 메서드에 대해 단위 테스트 작성. 테스트 작성 중 발견한 실제 버그 3건도 함께 수정.테스트
CourseServiceTest(24개) +RunnectUserTest(7개, 신규 equals/hashCode 검증)createCourse: 정상 생성, 유저 없음, 좌표 부족, 출발지 주소 불완전getCourseByUser/getPrivateCourseByUser: 정상 매핑, 빈 목록, 유저 없음getCourseDetail: 본인/타인 코스, 업로더 없는 코스, 유저/코스 없음updateCourse: 정상 수정, 코스 없음, 소유자 아니면 거부deleteCourses: 비공개/공개 코스 삭제, 다건 삭제, 빈 목록, 존재하지 않는 코스, 중간 실패 시 이후 미처리 확인발견해서 수정한 버그 3건
DepartureConverter: 출발지 주소가 공백 기준 3토큰 미만이면null을 반환해CourseService에서 그대로 NPE로 이어졌음 →BadRequestException(VALIDATION_DEPARTURE_ADDRESS_EXCEPTION)을 던지도록 변경CourseService.updateCourse(IDOR):courseId로만 조회하고userId로 소유권 검증을 하지 않아 다른 사람 코스의 제목도 수정 가능했음 →findByCourseIdAndUserId로 변경 (deleteCourses와 동일 패턴)RunnectUserequals/hashCode 부재: 참조 동일성에 의존해, 같은 유저라도 조회 경로가 다르면(로그인 유저 vs 코스에 매핑된 유저 등) 다른 사람으로 오판정될 수 있었음 → id 기준equals/hashCode추가 (Course.isMatchedUser, RecordService, PublicCourseService의 동일 패턴 전부 개선됨)검증
./gradlew build전체(기존 ServerApplicationTests 포함) 통과 확인