diff --git a/src/main/java/com/quespot/domain/mission/repository/CourseAttemptRepository.java b/src/main/java/com/quespot/domain/mission/repository/CourseAttemptRepository.java index 8271e7f..25709e6 100644 --- a/src/main/java/com/quespot/domain/mission/repository/CourseAttemptRepository.java +++ b/src/main/java/com/quespot/domain/mission/repository/CourseAttemptRepository.java @@ -2,6 +2,7 @@ import com.quespot.domain.mission.entity.CourseAttempt; import com.quespot.domain.mission.enums.CourseAttemptStatus; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -13,6 +14,9 @@ Optional findByUserIdAndCourseIdAndStatus( Long userId, Long courseId, CourseAttemptStatus status ); + // 진행 중 코스 목록 API용(#67). 컨버터가 트랜잭션 밖에서 attempt.getCourse()를 + // 타므로 course를 같이 로딩한다(MissionAttemptRepository.findByUserIdAndStatus와 동일). + @EntityGraph(attributePaths = "course") List findByUserIdAndStatus(Long userId, CourseAttemptStatus status); List findByCourseIdIn(List courseIds); diff --git a/src/main/java/com/quespot/domain/mission/repository/MissionAttemptRepository.java b/src/main/java/com/quespot/domain/mission/repository/MissionAttemptRepository.java index 1d0cb17..211e5ad 100644 --- a/src/main/java/com/quespot/domain/mission/repository/MissionAttemptRepository.java +++ b/src/main/java/com/quespot/domain/mission/repository/MissionAttemptRepository.java @@ -22,6 +22,12 @@ Optional findByUserIdAndMissionIdAndStatus( @EntityGraph(attributePaths = "mission") List findByUserIdAndStatus(Long userId, MissionAttemptStatus status); + // 단건 조회·인증 가이드·완료 결과 API용(#67). open-in-view: false라 컨트롤러의 + // 컨버터가 attempt.getMission()을 타는 시점엔 세션이 닫혀 있다 — 서비스에서 + // mission을 같이 로딩해 둬야 LazyInitializationException이 안 난다. + @EntityGraph(attributePaths = "mission") + Optional findWithMissionById(Long id); + // 목록 조회 화면에서 미션마다 시도를 따로 조회하지 않기 위한 벌크 조회. // 재시작 차단 정책 덕분에 (user, mission) 당 COMPLETED는 최대 1건, // active_key 덕분에 IN_PROGRESS도 최대 1건이라 결과가 작다. diff --git a/src/main/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepository.java b/src/main/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepository.java index 2c94477..666261d 100644 --- a/src/main/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepository.java +++ b/src/main/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepository.java @@ -12,7 +12,7 @@ // 코스 생성 알고리즘 전용 최근접 후보 검색(#39 재설계). MissionRepository(허건우 // 소유, 미션 목록/상세 조회)는 건드리지 않는다 — Mission 엔티티에 대한 두 번째 // JpaRepository는 별도 Spring 빈으로 문제없이 공존한다(#37 GeoDistanceCalculator와 -// 동일한 이유). +// 동일한 이유). 카테고리 조건은 #67에서 뺐다 — 반경·미수행·제외 ID만 본다. public interface MissionCourseCandidateRepository extends JpaRepository { @Query(value = """ @@ -22,7 +22,6 @@ public interface MissionCourseCandidateRepository extends JpaRepository findNearestCandidates( @Param("refLat") BigDecimal refLat, @Param("refLng") BigDecimal refLng, @Param("radiusMeters") int radiusMeters, - @Param("categories") List categories, @Param("excludeIds") List excludeIds, @Param("userId") Long userId, @Param("limit") int limit diff --git a/src/main/java/com/quespot/domain/mission/service/MissionAttemptService.java b/src/main/java/com/quespot/domain/mission/service/MissionAttemptService.java index a6a8819..653a59d 100644 --- a/src/main/java/com/quespot/domain/mission/service/MissionAttemptService.java +++ b/src/main/java/com/quespot/domain/mission/service/MissionAttemptService.java @@ -121,7 +121,9 @@ public void writeReflection(Long userId, Long attemptId, String content) { } private MissionAttempt findOwnedAttempt(Long userId, Long attemptId) { - return missionAttemptRepository.findById(attemptId) + // mission을 같이 로딩한다(#67) — getAttempt()가 돌려준 엔티티는 트랜잭션 밖에서 + // 컨버터가 attempt.getMission()을 타므로 지연 로딩이면 500이 난다. + return missionAttemptRepository.findWithMissionById(attemptId) .filter(a -> a.getUserId().equals(userId)) .orElseThrow(() -> new MissionException(MissionErrorCode.ATTEMPT_NOT_FOUND)); } diff --git a/src/main/java/com/quespot/domain/mission/service/MissionCourseGenerationService.java b/src/main/java/com/quespot/domain/mission/service/MissionCourseGenerationService.java index 4071da1..1aa2454 100644 --- a/src/main/java/com/quespot/domain/mission/service/MissionCourseGenerationService.java +++ b/src/main/java/com/quespot/domain/mission/service/MissionCourseGenerationService.java @@ -7,7 +7,6 @@ import com.quespot.domain.mission.entity.MissionCourse; import com.quespot.domain.mission.enums.CourseAttemptStatus; import com.quespot.domain.mission.enums.MissionAttemptStatus; -import com.quespot.domain.mission.enums.MissionCategory; import com.quespot.domain.mission.enums.MissionStatus; import com.quespot.domain.mission.exception.MissionException; import com.quespot.domain.mission.exception.code.MissionErrorCode; @@ -28,7 +27,10 @@ // 사용자가 anchor 미션 하나를 고르면 규칙 기반으로 코스를 생성하고 즉시 시작한다 // (#39 재설계). 반경 500m→1km 순회, 페어 단위 재시도로 기존 코스와의 중복을 -// 피한다(스펙 2번 섹션). +// 피한다(스펙 2번 섹션). 2·3번 미션의 카테고리 조건(NATURE/CULTURE, +// CULTURE/NIGHT_VIEW)은 #67에서 뺐다 — 수집 데이터가 FOOD·ETC 위주라 주변 미션 +// 10개 중 2개꼴만 통과해 코스가 거의 안 만들어졌다. 지금은 카테고리 무관하게 +// 반경 안 미수행 미션을 전부 후보로 본다. @Service @RequiredArgsConstructor public class MissionCourseGenerationService { @@ -37,10 +39,6 @@ public class MissionCourseGenerationService { private static final int RADIUS_STEP2_METERS = 1000; private static final int CANDIDATE_FETCH_LIMIT = 20; private static final float BONUS_RATE = 0.30f; - private static final List SECOND_CATEGORIES = - List.of(MissionCategory.NATURE.name(), MissionCategory.CULTURE.name()); - private static final List THIRD_CATEGORIES = - List.of(MissionCategory.CULTURE.name(), MissionCategory.NIGHT_VIEW.name()); private final MissionCourseCandidateRepository candidateRepository; private final MissionCourseRepository missionCourseRepository; @@ -120,12 +118,12 @@ public MissionCourseDetailResultDTO generateAndStart(Long userId, Long anchorMis private CandidatePair tryFindPair(Long userId, Mission anchor, int radiusMeters, Set rejectedPairs) { List m2Candidates = candidateRepository.findNearestCandidates( anchor.getSnapshotLatitude(), anchor.getSnapshotLongitude(), radiusMeters, - SECOND_CATEGORIES, List.of(anchor.getId()), userId, CANDIDATE_FETCH_LIMIT + List.of(anchor.getId()), userId, CANDIDATE_FETCH_LIMIT ); for (CourseCandidateMissionProjection m2 : m2Candidates) { List m3Candidates = candidateRepository.findNearestCandidates( m2.getLatitude(), m2.getLongitude(), radiusMeters, - THIRD_CATEGORIES, List.of(anchor.getId(), m2.getMissionId()), userId, CANDIDATE_FETCH_LIMIT + List.of(anchor.getId(), m2.getMissionId()), userId, CANDIDATE_FETCH_LIMIT ); for (CourseCandidateMissionProjection m3 : m3Candidates) { String key = m2.getMissionId() + ":" + m3.getMissionId(); diff --git a/src/main/java/com/quespot/global/apiPayload/handler/GeneralExceptionAdvice.java b/src/main/java/com/quespot/global/apiPayload/handler/GeneralExceptionAdvice.java index 3aacfff..77eff4e 100644 --- a/src/main/java/com/quespot/global/apiPayload/handler/GeneralExceptionAdvice.java +++ b/src/main/java/com/quespot/global/apiPayload/handler/GeneralExceptionAdvice.java @@ -6,6 +6,7 @@ import com.quespot.global.apiPayload.code.GeneralErrorCode; import com.quespot.global.apiPayload.exception.GeneralException; import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataAccessException; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; @@ -18,6 +19,7 @@ import java.util.LinkedHashMap; import java.util.Map; +@Slf4j @RestControllerAdvice public class GeneralExceptionAdvice { @@ -69,8 +71,12 @@ protected ResponseEntity> handleDataAccessException() { return handleExceptionInternal(GeneralErrorCode.COMMON_503_001); } + // 처리되지 않은 예외는 응답이 COMMON_500_001로만 나가서 원인을 알 수 없다(#67에서 + // LazyInitializationException이 서버 로그에도 안 남아 코드 추적으로만 찾았다). + // 스택트레이스를 반드시 남긴다. @ExceptionHandler(Exception.class) - protected ResponseEntity> handleException() { + protected ResponseEntity> handleException(Exception exception) { + log.error("처리되지 않은 예외", exception); return handleExceptionInternal(GeneralErrorCode.COMMON_500_001); } diff --git a/src/test/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepositoryTest.java b/src/test/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepositoryTest.java index 4807dcf..c755420 100644 --- a/src/test/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepositoryTest.java +++ b/src/test/java/com/quespot/domain/mission/repository/MissionCourseCandidateRepositoryTest.java @@ -93,21 +93,23 @@ private Mission mission(String suffix, MissionTemplate template, BigDecimal lat, return missionRepository.save(Mission.publish(candidate)); } + // 카테고리 조건은 #67에서 뺐다 — FOOD/ETC/HISTORY도 반경 안이면 후보다. @Test - void findsOnlyMissionsWithinRadiusMatchingCategoryOrderedByDistance() { + void findsAllMissionsWithinRadiusRegardlessOfCategoryOrderedByDistance() { + Mission nearest = mission("food", MissionTemplate.FOOD_LOCATION, new BigDecimal("37.5670"), ANCHOR_LNG); // ~55m Mission near = mission("near", MissionTemplate.CULTURE_LOCATION, new BigDecimal("37.5701"), ANCHOR_LNG); // ~400m Mission mid = mission("mid", MissionTemplate.NATURE_LOCATION, new BigDecimal("37.5728"), ANCHOR_LNG); // ~700m mission("far", MissionTemplate.CULTURE_LOCATION, new BigDecimal("37.5773"), ANCHOR_LNG); // ~1200m, 반경 밖 - mission("wrong-category", MissionTemplate.FOOD_LOCATION, new BigDecimal("37.5670"), ANCHOR_LNG); // 카테고리 불일치 List result = candidateRepository.findNearestCandidates( ANCHOR_LAT, ANCHOR_LNG, 1000, - List.of("NATURE", "CULTURE"), List.of(-1L), 9999L, 20 + List.of(-1L), 9999L, 20 ); - assertThat(result).hasSize(2); - assertThat(result.get(0).getMissionId()).isEqualTo(near.getId()); - assertThat(result.get(1).getMissionId()).isEqualTo(mid.getId()); + assertThat(result).hasSize(3); + assertThat(result.get(0).getMissionId()).isEqualTo(nearest.getId()); + assertThat(result.get(1).getMissionId()).isEqualTo(near.getId()); + assertThat(result.get(2).getMissionId()).isEqualTo(mid.getId()); } @Test @@ -117,7 +119,7 @@ void excludesMissionsAlreadyAttemptedByUser() { List result = candidateRepository.findNearestCandidates( ANCHOR_LAT, ANCHOR_LNG, 1000, - List.of("CULTURE"), List.of(-1L), 7001L, 20 + List.of(-1L), 7001L, 20 ); assertThat(result).noneMatch(row -> row.getMissionId().equals(attempted.getId())); @@ -129,7 +131,7 @@ void excludesGivenExcludeIds() { List result = candidateRepository.findNearestCandidates( ANCHOR_LAT, ANCHOR_LNG, 1000, - List.of("CULTURE"), List.of(mission.getId()), 9999L, 20 + List.of(mission.getId()), 9999L, 20 ); assertThat(result).noneMatch(row -> row.getMissionId().equals(mission.getId())); @@ -141,7 +143,7 @@ void returnsEmptyWhenNoCandidateWithinRadius() { List result = candidateRepository.findNearestCandidates( ANCHOR_LAT, ANCHOR_LNG, 500, - List.of("CULTURE"), List.of(-1L), 9999L, 20 + List.of(-1L), 9999L, 20 ); assertThat(result).isEmpty(); diff --git a/src/test/java/com/quespot/domain/mission/service/AttemptDetailConversionIntegrationTest.java b/src/test/java/com/quespot/domain/mission/service/AttemptDetailConversionIntegrationTest.java new file mode 100644 index 0000000..7d4dd10 --- /dev/null +++ b/src/test/java/com/quespot/domain/mission/service/AttemptDetailConversionIntegrationTest.java @@ -0,0 +1,164 @@ +package com.quespot.domain.mission.service; + +import com.quespot.domain.mission.converter.MissionConverter; +import com.quespot.domain.mission.dto.res.CourseAttemptListResponseDTO; +import com.quespot.domain.mission.dto.res.MissionAttemptResponseDTO; +import com.quespot.domain.mission.dto.res.MissionAttemptResultResponseDTO; +import com.quespot.domain.mission.dto.res.VerificationGuideResponseDTO; +import com.quespot.domain.mission.entity.CourseAttempt; +import com.quespot.domain.mission.entity.Mission; +import com.quespot.domain.mission.entity.MissionAttempt; +import com.quespot.domain.mission.entity.MissionCandidate; +import com.quespot.domain.mission.entity.MissionCourse; +import com.quespot.domain.mission.enums.MissionTemplate; +import com.quespot.domain.mission.repository.CourseAttemptRepository; +import com.quespot.domain.mission.repository.MissionAttemptRepository; +import com.quespot.domain.mission.repository.MissionCandidateRepository; +import com.quespot.domain.mission.repository.MissionCourseRepository; +import com.quespot.domain.mission.repository.MissionRepository; +import com.quespot.domain.spot.entity.Spot; +import com.quespot.domain.spot.enums.AppCategory; +import com.quespot.domain.spot.enums.SpotSource; +import com.quespot.domain.spot.repository.SpotRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import javax.sql.DataSource; +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +// #67 회귀 테스트. open-in-view: false라 서비스의 readOnly 트랜잭션이 끝난 뒤 +// 컨트롤러가 MissionConverter로 지연 로딩 연관관계(attempt.getMission(), +// attempt.getCourse())를 타면 LazyInitializationException이 났다. 이 테스트는 +// 컨트롤러와 똑같이 트랜잭션 밖에서 서비스 → 컨버터를 호출한다 — 테스트 메서드에 +// @Transactional을 붙이면 세션이 열려 있어 버그가 재현되지 않으니 붙이지 말 것. +@SpringBootTest +@Testcontainers +class AttemptDetailConversionIntegrationTest { + + @Container + private static final MySQLContainer MYSQL = new MySQLContainer<>("mysql:8.4") + .withDatabaseName("quespot_test") + .withUsername("test") + .withPassword("test"); + + @DynamicPropertySource + static void registerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", MYSQL::getJdbcUrl); + registry.add("spring.datasource.username", MYSQL::getUsername); + registry.add("spring.datasource.password", MYSQL::getPassword); + registry.add("spring.jpa.hibernate.ddl-auto", () -> "create"); + registry.add("app.jwt.secret", () -> "dGVzdC1qd3Qtc2VjcmV0LWtleS1tdXN0LWJlLWF0LWxlYXN0LTMyLWJ5dGVz"); + registry.add("app.mail.verification-code-secret", () -> "integration-test-mail-secret"); + } + + @Autowired + private MissionAttemptService missionAttemptService; + + @Autowired + private CourseAttemptService courseAttemptService; + + @Autowired + private MissionAttemptRepository missionAttemptRepository; + + @Autowired + private CourseAttemptRepository courseAttemptRepository; + + @Autowired + private MissionCourseRepository missionCourseRepository; + + @Autowired + private MissionRepository missionRepository; + + @Autowired + private MissionCandidateRepository missionCandidateRepository; + + @Autowired + private SpotRepository spotRepository; + + @Autowired + private DataSource dataSource; + + // active_key 생성 컬럼 — MissionAttempt/CourseAttempt 상단 주석 참고. + @BeforeEach + void applyGeneratedColumns() throws SQLException { + applyGeneratedColumn("mission_attempts", "mission_id"); + applyGeneratedColumn("course_attempts", "course_id"); + } + + private void applyGeneratedColumn(String table, String refColumn) throws SQLException { + try (var connection = dataSource.getConnection(); var statement = connection.createStatement()) { + statement.execute(""" + ALTER TABLE %s + MODIFY COLUMN active_key VARCHAR(64) GENERATED ALWAYS AS ( + CASE WHEN status = 'IN_PROGRESS' THEN CONCAT(user_id, ':', %s) END + ) STORED + """.formatted(table, refColumn)); + } catch (SQLException ignored) { + // 같은 컨테이너를 재사용하는 다른 테스트가 이미 적용한 경우 + } + } + + private Mission createPublishedMission(String suffix) { + Spot spot = spotRepository.save(Spot.builder() + .source(SpotSource.TOUR_API).sourceContentId("ad" + suffix + (System.nanoTime() % 100_000_000_000L)) + .name("변환 테스트 스팟 " + suffix) + .latitude(new BigDecimal("37.5665")).longitude(new BigDecimal("126.9780")) + .appCategory(AppCategory.CULTURE).categoryMappingVersion(1).showFlag(true) + .build()); + MissionCandidate candidate = missionCandidateRepository.save( + MissionCandidate.generate(spot, MissionTemplate.CULTURE_LOCATION, 1) + ); + return missionRepository.save(Mission.publish(candidate)); + } + + @Test + void attemptFetchedOutsideTransactionCanBeConvertedToVerificationGuide() { + Long userId = 9301L; + Mission mission = createPublishedMission("guide"); + MissionAttempt saved = missionAttemptRepository.save(MissionAttempt.start(userId, mission)); + + MissionAttempt attempt = missionAttemptService.getAttempt(userId, saved.getId()); + + VerificationGuideResponseDTO guide = MissionConverter.toVerificationGuideResponse(attempt); + assertThat(guide.attemptId()).isEqualTo(saved.getId()); + assertThat(guide.targetLatitude()).isEqualByComparingTo(new BigDecimal("37.5665")); + assertThat(guide.targetLongitude()).isEqualByComparingTo(new BigDecimal("126.9780")); + assertThat(guide.radiusMeters()).isEqualTo(MissionArrivalService.RADIUS_METERS); + + // 같은 getAttempt를 쓰는 단건 조회·완료 결과 컨버터도 함께 확인한다. + MissionAttemptResponseDTO single = MissionConverter.toAttemptResponse(attempt); + assertThat(single.missionId()).isEqualTo(mission.getId()); + assertThat(single.missionTitle()).isEqualTo(mission.getTitle()); + MissionAttemptResultResponseDTO result = MissionConverter.toAttemptResultResponse(attempt, null); + assertThat(result.missionTitle()).isEqualTo(mission.getTitle()); + } + + @Test + void inProgressCourseAttemptsFetchedOutsideTransactionCanBeConvertedToList() { + Long userId = 9302L; + Mission anchor = createPublishedMission("course"); + MissionCourse course = missionCourseRepository.save(MissionCourse.generate( + anchor, userId, 3, "변환 테스트 코스", "설명", null, "11110", 100, 30, 60 + )); + CourseAttempt saved = courseAttemptRepository.save(CourseAttempt.start(userId, course)); + + List attempts = courseAttemptService.getInProgressList(userId); + + CourseAttemptListResponseDTO response = MissionConverter.toCourseAttemptListResponse(attempts); + assertThat(response.attempts()).hasSize(1); + assertThat(response.attempts().get(0).courseAttemptId()).isEqualTo(saved.getId()); + assertThat(response.attempts().get(0).courseId()).isEqualTo(course.getId()); + assertThat(response.attempts().get(0).courseName()).isEqualTo("변환 테스트 코스"); + } +} diff --git a/src/test/java/com/quespot/domain/mission/service/MissionAttemptServiceTest.java b/src/test/java/com/quespot/domain/mission/service/MissionAttemptServiceTest.java index ae07bee..4f4ff36 100644 --- a/src/test/java/com/quespot/domain/mission/service/MissionAttemptServiceTest.java +++ b/src/test/java/com/quespot/domain/mission/service/MissionAttemptServiceTest.java @@ -240,7 +240,7 @@ void startThrowsWhenMissionNotActive() { void quitSetsStatusToQuitWhenInProgress() { Mission mission = activeMission(); MissionAttempt attempt = MissionAttempt.start(1L, mission); - when(missionAttemptRepository.findById(100L)).thenReturn(Optional.of(attempt)); + when(missionAttemptRepository.findWithMissionById(100L)).thenReturn(Optional.of(attempt)); missionAttemptService.quit(1L, 100L); @@ -252,7 +252,7 @@ void quitThrowsWhenNotInProgress() { Mission mission = activeMission(); MissionAttempt attempt = MissionAttempt.start(1L, mission); attempt.quit(); - when(missionAttemptRepository.findById(100L)).thenReturn(Optional.of(attempt)); + when(missionAttemptRepository.findWithMissionById(100L)).thenReturn(Optional.of(attempt)); assertThatThrownBy(() -> missionAttemptService.quit(1L, 100L)) .isInstanceOf(MissionException.class) @@ -264,7 +264,7 @@ void quitThrowsWhenNotInProgress() { void getAttemptThrowsWhenNotOwnedByUser() { Mission mission = activeMission(); MissionAttempt attempt = MissionAttempt.start(2L, mission); - when(missionAttemptRepository.findById(100L)).thenReturn(Optional.of(attempt)); + when(missionAttemptRepository.findWithMissionById(100L)).thenReturn(Optional.of(attempt)); assertThatThrownBy(() -> missionAttemptService.getAttempt(1L, 100L)) .isInstanceOf(MissionException.class) @@ -276,7 +276,7 @@ void getAttemptThrowsWhenNotOwnedByUser() { void writeReflectionSetsReflectionOnOwnedAttempt() { Mission mission = activeMission(); MissionAttempt attempt = MissionAttempt.start(1L, mission); - when(missionAttemptRepository.findById(100L)).thenReturn(Optional.of(attempt)); + when(missionAttemptRepository.findWithMissionById(100L)).thenReturn(Optional.of(attempt)); missionAttemptService.writeReflection(1L, 100L, "좋았어요"); diff --git a/src/test/java/com/quespot/domain/mission/service/MissionCourseGenerationServiceTest.java b/src/test/java/com/quespot/domain/mission/service/MissionCourseGenerationServiceTest.java index 8daab36..2b1d250 100644 --- a/src/test/java/com/quespot/domain/mission/service/MissionCourseGenerationServiceTest.java +++ b/src/test/java/com/quespot/domain/mission/service/MissionCourseGenerationServiceTest.java @@ -142,11 +142,11 @@ void generateAndStartBuildsCourseFromNearestValidPairAt500m() { CourseCandidateMissionProjection candidateM3 = candidate(3L, "37.5715", "126.9850"); when(candidateRepository.findNearestCandidates( eq(anchor.getSnapshotLatitude()), eq(anchor.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of(candidateM2)); when(candidateRepository.findNearestCandidates( eq(m2.getSnapshotLatitude()), eq(m2.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of(candidateM3)); when(missionCourseRepository.save(any(MissionCourse.class))).thenAnswer(inv -> { MissionCourse c = inv.getArgument(0); @@ -178,11 +178,11 @@ void generateAndStartWidensTo1kmWhenNoCandidateWithin500m() { when(courseMissionRepository.findExistingPairs(9L, 1L)).thenReturn(List.of()); when(candidateRepository.findNearestCandidates( eq(anchor.getSnapshotLatitude()), eq(anchor.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of()); when(candidateRepository.findNearestCandidates( eq(anchor.getSnapshotLatitude()), eq(anchor.getSnapshotLongitude()), eq(1000), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of()); assertThatThrownBy(() -> service.generateAndStart(9L, 1L)) @@ -192,11 +192,11 @@ void generateAndStartWidensTo1kmWhenNoCandidateWithin500m() { verify(candidateRepository).findNearestCandidates( eq(anchor.getSnapshotLatitude()), eq(anchor.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() ); verify(candidateRepository).findNearestCandidates( eq(anchor.getSnapshotLatitude()), eq(anchor.getSnapshotLongitude()), eq(1000), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() ); } @@ -222,15 +222,15 @@ void generateAndStartSkipsPairThatMatchesExistingCourseAndTriesNextCandidate() { CourseCandidateMissionProjection candidateM3ForSecond = candidate(3L, "37.5715", "126.9850"); when(candidateRepository.findNearestCandidates( eq(anchor.getSnapshotLatitude()), eq(anchor.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of(candidateM2First, candidateM2Second)); when(candidateRepository.findNearestCandidates( eq(m2First.getSnapshotLatitude()), eq(m2First.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of(candidateM3ForFirst)); when(candidateRepository.findNearestCandidates( eq(m2Second.getSnapshotLatitude()), eq(m2Second.getSnapshotLongitude()), eq(500), - any(), any(), eq(9L), anyInt() + any(), eq(9L), anyInt() )).thenReturn(List.of(candidateM3ForSecond)); when(missionCourseRepository.save(any(MissionCourse.class))).thenAnswer(inv -> { MissionCourse c = inv.getArgument(0);