From 3a19fc8fa0ac6d93641e3646f2372e4e07034521 Mon Sep 17 00:00:00 2001 From: shawnatak926 Date: Thu, 17 Sep 2026 23:09:03 +0900 Subject: [PATCH] =?UTF-8?q?test=20:=20NAVER=20=EC=A7=80=EB=8F=84=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=20=EC=9D=B4=EC=8A=88=20=ED=9A=8C=EA=B7=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MapLinkConversionRequestTest.java | 46 +++++++++++ .../PlaceAdministrativeRegionServiceTest.java | 54 +++++++++++++ .../PlaceLocalHotQueryServiceTest.java | 19 +++++ ...PlaceAdministrativeRegionResolverTest.java | 40 ++++++++++ ...dministrativeRegionBackfillRunnerTest.java | 76 +++++++++++++++++++ 5 files changed, 235 insertions(+) create mode 100644 src/test/java/com/typenull/pingdom/place/api/dto/conversion/MapLinkConversionRequestTest.java create mode 100644 src/test/java/com/typenull/pingdom/place/infrastructure/localhot/PlaceAdministrativeRegionBackfillRunnerTest.java diff --git a/src/test/java/com/typenull/pingdom/place/api/dto/conversion/MapLinkConversionRequestTest.java b/src/test/java/com/typenull/pingdom/place/api/dto/conversion/MapLinkConversionRequestTest.java new file mode 100644 index 000000000..f28610677 --- /dev/null +++ b/src/test/java/com/typenull/pingdom/place/api/dto/conversion/MapLinkConversionRequestTest.java @@ -0,0 +1,46 @@ +package com.typenull.pingdom.place.api.dto.conversion; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.typenull.pingdom.place.domain.conversion.MapLinkConversionType; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class MapLinkConversionRequestTest { + + private static Validator validator; + + @BeforeAll + static void setUpValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + @Test + void NAVER_provider와_필수_입력이_있으면_검증을_통과한다() { + Set invalidFields = invalidFields(new MapLinkConversionRequest( + MapLinkConversionType.DIRECTIONS, "NAVER", "request-1")); + + assertThat(invalidFields).isEmpty(); + } + + @Test + void provider와_linkType과_requestId의_필수값과_길이를_검증한다() { + Set invalidFields = invalidFields(new MapLinkConversionRequest( + null, " ", " ")); + Set longProviderInvalidFields = invalidFields(new MapLinkConversionRequest( + MapLinkConversionType.DIRECTIONS, "N".repeat(31), "request-1")); + + assertThat(invalidFields).containsExactlyInAnyOrder("linkType", "provider", "requestId"); + assertThat(longProviderInvalidFields).containsExactly("provider"); + } + + private Set invalidFields(MapLinkConversionRequest request) { + return validator.validate(request).stream() + .map(violation -> violation.getPropertyPath().toString()) + .collect(Collectors.toSet()); + } +} diff --git a/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceAdministrativeRegionServiceTest.java b/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceAdministrativeRegionServiceTest.java index ed64c0b2d..408e037b6 100644 --- a/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceAdministrativeRegionServiceTest.java +++ b/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceAdministrativeRegionServiceTest.java @@ -1,9 +1,11 @@ package com.typenull.pingdom.place.application.service.localhot; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.typenull.pingdom.place.domain.place.core.MapPlace; @@ -12,6 +14,8 @@ import com.typenull.pingdom.place.domain.place.region.ResolvedPlaceAdministrativeRegion; import com.typenull.pingdom.place.infrastructure.persistence.place.MapPlaceRepository; import com.typenull.pingdom.place.infrastructure.persistence.place.PlaceAdministrativeRegionRepository; +import com.typenull.pingdom.shared.exception.MapErrorCode; +import com.typenull.pingdom.shared.exception.MapException; import java.time.Clock; import java.time.Instant; import java.time.ZoneOffset; @@ -73,6 +77,56 @@ class PlaceAdministrativeRegionServiceTest { verify(place).updateAdministrativeRegion("11680"); } + @Test + void Resolver가_비활성화되면_지역과_장소를_변경하지_않는다() { + PlaceAdministrativeRegionResolver resolver = mock(PlaceAdministrativeRegionResolver.class); + PlaceAdministrativeRegionRepository regionRepository = mock(PlaceAdministrativeRegionRepository.class); + MapPlaceRepository placeRepository = mock(MapPlaceRepository.class); + MapPlace place = mock(MapPlace.class); + when(resolver.isConfigured()).thenReturn(false); + + boolean synchronizedRegion = service(resolver, regionRepository, placeRepository).synchronizeIfConfigured(place); + + assertThat(synchronizedRegion).isFalse(); + verifyNoInteractions(regionRepository, placeRepository, place); + } + + @Test + void Resolver_조회_실패시_지역과_장소를_부분_저장하지_않는다() { + PlaceAdministrativeRegionResolver resolver = mock(PlaceAdministrativeRegionResolver.class); + PlaceAdministrativeRegionRepository regionRepository = mock(PlaceAdministrativeRegionRepository.class); + MapPlaceRepository placeRepository = mock(MapPlaceRepository.class); + MapPlace place = mock(MapPlace.class); + when(place.getLatitude()).thenReturn(37.5172d); + when(place.getLongitude()).thenReturn(127.0473d); + MapException resolutionFailure = new MapException(MapErrorCode.LOCAL_HOT_REGION_RESOLUTION_FAILED); + when(resolver.resolve(37.5172d, 127.0473d)).thenThrow(resolutionFailure); + + assertThatThrownBy(() -> service(resolver, regionRepository, placeRepository).synchronize(place)) + .isSameAs(resolutionFailure); + + verifyNoInteractions(regionRepository, placeRepository); + verify(place, org.mockito.Mockito.never()).updateAdministrativeRegion(org.mockito.ArgumentMatchers.anyString()); + } + + @Test + void 지역을_갱신할_장소가_없으면_Resolver를_호출하지_않는다() { + PlaceAdministrativeRegionResolver resolver = mock(PlaceAdministrativeRegionResolver.class); + PlaceAdministrativeRegionRepository regionRepository = mock(PlaceAdministrativeRegionRepository.class); + MapPlaceRepository placeRepository = mock(MapPlaceRepository.class); + when(resolver.isConfigured()).thenReturn(true); + when(placeRepository.findByIdForUpdate(10L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> service(resolver, regionRepository, placeRepository).synchronizeByIdIfConfigured(10L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("placeId=10"); + + verifyNoInteractions(regionRepository); + verify(resolver).isConfigured(); + verify(resolver, org.mockito.Mockito.never()).resolve(org.mockito.ArgumentMatchers.anyDouble(), + org.mockito.ArgumentMatchers.anyDouble()); + } + private PlaceAdministrativeRegionService service( PlaceAdministrativeRegionResolver resolver, PlaceAdministrativeRegionRepository regionRepository, diff --git a/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceLocalHotQueryServiceTest.java b/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceLocalHotQueryServiceTest.java index 589b58d01..80e79f5f9 100644 --- a/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceLocalHotQueryServiceTest.java +++ b/src/test/java/com/typenull/pingdom/place/application/service/localhot/PlaceLocalHotQueryServiceTest.java @@ -1,6 +1,7 @@ package com.typenull.pingdom.place.application.service.localhot; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -15,6 +16,8 @@ import com.typenull.pingdom.place.domain.place.region.ResolvedPlaceAdministrativeRegion; import com.typenull.pingdom.place.infrastructure.persistence.place.PlaceAdministrativeRegionRepository; import com.typenull.pingdom.place.infrastructure.persistence.place.PlaceLocalHotQueryRepository; +import com.typenull.pingdom.shared.exception.MapErrorCode; +import com.typenull.pingdom.shared.exception.MapException; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -104,6 +107,22 @@ class PlaceLocalHotQueryServiceTest { verifyNoInteractions(regionResolver); } + @Test + void 좌표_조회에서_Resolver_실패는_조회_저장소를_호출하지_않고_전파한다() { + PlaceAdministrativeRegionResolver regionResolver = mock(PlaceAdministrativeRegionResolver.class); + PlaceAdministrativeRegionRepository regionRepository = mock(PlaceAdministrativeRegionRepository.class); + PlaceLocalHotQueryRepository queryRepository = mock(PlaceLocalHotQueryRepository.class); + MapException resolutionFailure = new MapException(MapErrorCode.LOCAL_HOT_REGION_RESOLUTION_FAILED); + when(regionResolver.resolve(37.5172d, 127.0473d)).thenThrow(resolutionFailure); + PlaceLocalHotQueryService service = new PlaceLocalHotQueryService( + regionResolver, regionRepository, queryRepository); + + assertThatThrownBy(() -> service.find(new PlaceLocalHotQuery(37.5172d, 127.0473d, null, 1, 20), 7L)) + .isSameAs(resolutionFailure); + + verifyNoInteractions(regionRepository, queryRepository); + } + private PlaceLocalHotQueryRepository.PlaceLocalHotProjection projection( Long placeId, long bookmarkCount, diff --git a/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/NaverPlaceAdministrativeRegionResolverTest.java b/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/NaverPlaceAdministrativeRegionResolverTest.java index 5305ebf1e..e34cb4c53 100644 --- a/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/NaverPlaceAdministrativeRegionResolverTest.java +++ b/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/NaverPlaceAdministrativeRegionResolverTest.java @@ -59,6 +59,46 @@ void setUp() { server.verify(); } + @Test + void 세종이_아닌_빈_area2_응답은_지역없음으로_처리한다() { + server.expect(requestTo(REQUEST_URL)) + .andRespond(withSuccess(successResponse("1168010100", "서울특별시", ""), MediaType.APPLICATION_JSON)); + + assertFailure(resolver(true, "test-client-id", "test-client-secret"), + MapErrorCode.LOCAL_HOT_REGION_NOT_FOUND, HttpStatus.NOT_FOUND); + server.verify(); + } + + @Test + void legalcode_코드가_불완전하면_지역없음으로_처리한다() { + server.expect(requestTo(REQUEST_URL)) + .andRespond(withSuccess(""" + {"status":{"code":0},"results":[{ + "name":"legalcode", + "code":{"id":"361101070"}, + "region":{"area1":{"name":"세종특별자치시"},"area2":{"name":""}} + }]}""", MediaType.APPLICATION_JSON)); + + assertFailure(resolver(true, "test-client-id", "test-client-secret"), + MapErrorCode.LOCAL_HOT_REGION_NOT_FOUND, HttpStatus.NOT_FOUND); + server.verify(); + } + + @Test + void legalcode_결과가_없으면_지역없음으로_처리한다() { + server.expect(requestTo(REQUEST_URL)) + .andRespond(withSuccess(""" + {"status":{"code":0},"results":[{ + "name":"addr", + "code":{"id":"1168010100"}, + "region":{"area1":{"name":"서울특별시"},"area2":{"name":"강남구"}} + }]}""", MediaType.APPLICATION_JSON)); + + assertFailure(resolver(true, "test-client-id", "test-client-secret"), + MapErrorCode.LOCAL_HOT_REGION_NOT_FOUND, HttpStatus.NOT_FOUND); + server.verify(); + } + @Test void 네이버_내부_상태가_결과없음이면_지역없음으로_처리한다() { server.expect(requestTo(REQUEST_URL)) diff --git a/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/PlaceAdministrativeRegionBackfillRunnerTest.java b/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/PlaceAdministrativeRegionBackfillRunnerTest.java new file mode 100644 index 000000000..117218964 --- /dev/null +++ b/src/test/java/com/typenull/pingdom/place/infrastructure/localhot/PlaceAdministrativeRegionBackfillRunnerTest.java @@ -0,0 +1,76 @@ +package com.typenull.pingdom.place.infrastructure.localhot; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.typenull.pingdom.place.application.service.localhot.PlaceAdministrativeRegionService; +import com.typenull.pingdom.place.domain.place.core.MapPlace; +import com.typenull.pingdom.place.infrastructure.persistence.place.MapPlaceRepository; +import com.typenull.pingdom.shared.exception.MapErrorCode; +import com.typenull.pingdom.shared.exception.MapException; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.boot.ApplicationArguments; +import org.springframework.data.domain.Pageable; + +class PlaceAdministrativeRegionBackfillRunnerTest { + + @Test + void regionCode가_없는_장소만_설정된_batchSize만큼_조회한다() { + MapPlaceRepository placeRepository = mock(MapPlaceRepository.class); + PlaceAdministrativeRegionService regionService = mock(PlaceAdministrativeRegionService.class); + MapPlace place = place(10L); + when(placeRepository.findByRegionCodeIsNullOrderByIdAsc(org.mockito.ArgumentMatchers.any(Pageable.class))) + .thenReturn(List.of(place)); + when(regionService.synchronizeByIdIfConfigured(10L)).thenReturn(false); + PlaceAdministrativeRegionBackfillRunner runner = runner(2, placeRepository, regionService); + + runner.run(mock(ApplicationArguments.class)); + + ArgumentCaptor pageableCaptor = ArgumentCaptor.forClass(Pageable.class); + verify(placeRepository).findByRegionCodeIsNullOrderByIdAsc(pageableCaptor.capture()); + assertThat(pageableCaptor.getValue().getPageNumber()).isZero(); + assertThat(pageableCaptor.getValue().getPageSize()).isEqualTo(2); + verify(regionService).synchronizeByIdIfConfigured(10L); + } + + @Test + void 한_장소의_지역_조회가_실패해도_다음_장소를_계속_처리한다() { + MapPlaceRepository placeRepository = mock(MapPlaceRepository.class); + PlaceAdministrativeRegionService regionService = mock(PlaceAdministrativeRegionService.class); + MapPlace failedPlace = place(10L); + MapPlace succeededPlace = place(11L); + when(placeRepository.findByRegionCodeIsNullOrderByIdAsc(org.mockito.ArgumentMatchers.any(Pageable.class))) + .thenReturn(List.of(failedPlace, succeededPlace)); + doThrow(new MapException(MapErrorCode.LOCAL_HOT_REGION_NOT_FOUND)) + .when(regionService).synchronizeByIdIfConfigured(10L); + when(regionService.synchronizeByIdIfConfigured(11L)).thenReturn(true); + + runner(10, placeRepository, regionService).run(mock(ApplicationArguments.class)); + + verify(regionService).synchronizeByIdIfConfigured(10L); + verify(regionService).synchronizeByIdIfConfigured(11L); + } + + private PlaceAdministrativeRegionBackfillRunner runner( + int batchSize, + MapPlaceRepository placeRepository, + PlaceAdministrativeRegionService regionService + ) { + return new PlaceAdministrativeRegionBackfillRunner( + new PlaceAdministrativeRegionBackfillProperties(true, batchSize), + placeRepository, + regionService + ); + } + + private MapPlace place(long id) { + MapPlace place = mock(MapPlace.class); + when(place.getId()).thenReturn(id); + return place; + } +}