| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- package com.ruoyi.app.map;
- import com.alibaba.fastjson2.JSON;
- import com.alibaba.fastjson2.JSONObject;
- import com.ruoyi.common.exception.ServiceException;
- import com.ruoyi.common.utils.spring.SpringUtils;
- import org.junit.jupiter.api.AfterAll;
- import org.junit.jupiter.api.BeforeAll;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
- import org.springframework.beans.factory.support.DefaultListableBeanFactory;
- import org.springframework.context.support.StaticMessageSource;
- import org.springframework.test.util.ReflectionTestUtils;
- import java.math.BigDecimal;
- import java.util.List;
- import java.util.Map;
- import static org.junit.jupiter.api.Assertions.*;
- import static org.mockito.ArgumentMatchers.*;
- import static org.mockito.Mockito.*;
- class MapLocationServiceTest {
- private static ConfigurableListableBeanFactory originalBeanFactory;
- private final MapSourceSelector selector = mock(MapSourceSelector.class);
- private final SelfHostedMapClient selfHosted = mock(SelfHostedMapClient.class);
- private final GoogleMapLocationClient google = mock(GoogleMapLocationClient.class);
- private MapLocationService service;
- @BeforeAll
- static void messages() {
- originalBeanFactory = (ConfigurableListableBeanFactory)
- ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
- StaticMessageSource messages = new StaticMessageSource();
- messages.setUseCodeAsDefaultMessage(true);
- DefaultListableBeanFactory beans = new DefaultListableBeanFactory();
- beans.registerSingleton("messageSource", messages);
- new SpringUtils().postProcessBeanFactory(beans);
- }
- @AfterAll
- static void restoreMessages() {
- new SpringUtils().postProcessBeanFactory(originalBeanFactory);
- }
- @BeforeEach
- void setUp() {
- service = new MapLocationService(selector, selfHosted, google);
- when(selector.selfHostedEnabled()).thenReturn(true);
- }
- @Test
- void searchMapsFlatSelfHostedAddressAndProviderNamespace() {
- when(selfHosted.getArray(eq("/search"), anyMap())).thenReturn(JSON.parseArray("""
- [{"id":"osm:123","name":"台北車站","address":"臺北市中正區","lat":25.0478,
- "lng":121.517,"source":"osm","precision":"poi"}]
- """));
- MapLocation result = service.search(" 台北車站 ").get(0);
- assertEquals("self_hosted:osm:123", result.getId());
- assertEquals("台北車站", result.getName());
- assertEquals(new BigDecimal("25.0478"), result.getLatitude());
- assertEquals("SELF_HOSTED", result.getSource());
- assertTrue(result.isRequiresConfirmation());
- verifyNoInteractions(google);
- }
- @Test
- void reverseRetainsExactTapInsteadOfNearestCoordinates() {
- when(selfHosted.getObject(eq("/reverse_geocode"), anyMap())).thenReturn(JSON.parseObject("""
- {"id":"pin:25,121","name":"松智路","address":"松智路","lat":25.03,"lng":121.56,
- "precision":"nearby_place","requires_confirmation":true,
- "nearest":{"id":"osm:8","lat":25.04,"lng":121.57}}
- """));
- MapLocation result = service.reverse(new BigDecimal("25.033000123"), new BigDecimal("121.565400987"));
- assertEquals(new BigDecimal("25.033000123"), result.getLatitude());
- assertEquals(new BigDecimal("121.565400987"), result.getLongitude());
- assertEquals("nearby_place", result.getPrecision());
- assertTrue(result.isRequiresConfirmation());
- }
- @Test
- void googleSearchAndReverseNormalizeGeometryAndStructuredAddress() {
- when(selector.selfHostedEnabled()).thenReturn(false);
- JSONObject place = JSON.parseObject("""
- {"place_id":"ChIJ123","name":"台北車站","formatted_address":"臺北市中正區",
- "geometry":{"location":{"lat":25.0478,"lng":121.517},"location_type":"ROOFTOP"},
- "address_components":[{"long_name":"臺灣","types":["country"]},
- {"long_name":"臺北市","types":["administrative_area_level_1"]}]}
- """);
- JSONObject response = new JSONObject();
- response.put("status", "OK");
- response.put("results", List.of(place));
- when(google.getObject(eq("/maps/api/place/textsearch/json"), anyMap())).thenReturn(response);
- when(google.getObject(eq("/maps/api/geocode/json"), anyMap())).thenReturn(response);
- MapLocation found = service.search("台北").get(0);
- assertEquals("google:ChIJ123", found.getId());
- assertEquals("GOOGLE", found.getSource());
- assertEquals("臺灣", found.getCountry());
- assertEquals("臺北市", found.getProvince());
- MapLocation reverse = service.reverse(new BigDecimal("25.1"), new BigDecimal("121.6"));
- assertEquals(new BigDecimal("25.1"), reverse.getLatitude());
- assertEquals(new BigDecimal("121.6"), reverse.getLongitude());
- assertEquals("google:pin:25.1,121.6", reverse.getId());
- assertEquals("nearby_place", reverse.getPrecision());
- assertTrue(reverse.isRequiresConfirmation());
- verifyNoInteractions(selfHosted);
- }
- @Test
- void namespacedDetailRemainsResolvableAfterSourceSwitch() {
- when(selector.selfHostedEnabled()).thenReturn(false);
- when(selfHosted.getObject("/place_detail", Map.of("id", "osm:9"))).thenReturn(JSON.parseObject("""
- {"id":"osm:9","name":"門店","lat":25,"lng":121,"precision":"poi"}
- """));
- assertEquals("self_hosted:osm:9", service.place("self_hosted:osm:9").getId());
- verifyNoInteractions(google);
- when(selector.selfHostedEnabled()).thenReturn(true);
- when(google.getObject("/maps/api/place/details/json", Map.of("place_id", "abc")))
- .thenReturn(JSON.parseObject("""
- {"status":"OK","result":{"place_id":"abc","name":"店",
- "geometry":{"location":{"lat":25,"lng":121}}}}
- """));
- assertEquals("google:abc", service.place("google:abc").getId());
- }
- @Test
- void nearbyMapsDistanceAndForwardsBoundedRequest() {
- when(selfHosted.getArray(eq("/nearby"), anyMap())).thenReturn(JSON.parseArray("""
- [{"id":"osm:1","name":"道路","lat":25,"lng":121,
- "precision":"street","distance_meters":123.4}]
- """));
- MapLocation result = service.nearby(BigDecimal.valueOf(25), BigDecimal.valueOf(121), 300, 10).get(0);
- assertEquals(new BigDecimal("123.4"), result.getDistanceMeters());
- verify(selfHosted).getArray("/nearby", Map.of("lat", BigDecimal.valueOf(25),
- "lng", BigDecimal.valueOf(121), "radius", 300, "limit", 10));
- }
- @Test
- void malformedResponsesAndFailureNeverFallBackToGoogle() {
- for (String body : List.of("[{\"id\":\"1\",\"name\":\"bad\"}]",
- "[{\"id\":\"1\",\"name\":\"bad\",\"lat\":91,\"lng\":121}]", "[null]")) {
- when(selfHosted.getArray(eq("/search"), anyMap())).thenReturn(JSON.parseArray(body));
- assertEquals("map.location.unavailable", assertThrows(ServiceException.class,
- () -> service.search("台北")).getMessage());
- }
- when(selfHosted.getArray(eq("/search"), anyMap())).thenThrow(new IllegalStateException("offline"));
- assertThrows(ServiceException.class, () -> service.search("台北"));
- verifyNoInteractions(google);
- }
- @Test
- void googleZeroResultsIsEmptyButDeniedOrMalformedResponseFails() {
- when(selector.selfHostedEnabled()).thenReturn(false);
- when(google.getObject(anyString(), anyMap())).thenReturn(JSON.parseObject("{\"status\":\"ZERO_RESULTS\"}"));
- assertTrue(service.search("unknown").isEmpty());
- MapLocation pin = service.reverse(new BigDecimal("25.1"), new BigDecimal("121.6"));
- assertEquals("google:pin:25.1,121.6", pin.getId());
- assertEquals("coordinate", pin.getPrecision());
- assertTrue(pin.isRequiresConfirmation());
- for (String body : List.of("{\"status\":\"REQUEST_DENIED\"}", "{\"status\":\"OK\"}",
- "{\"status\":\"OK\",\"results\":[{\"place_id\":\"bad\"}]}")) {
- when(google.getObject(anyString(), anyMap())).thenReturn(JSON.parseObject(body));
- assertThrows(ServiceException.class, () -> service.search("台北"));
- }
- }
- @Test
- void invalidRequestsAreRejectedBeforeProviderCall() {
- assertThrows(ServiceException.class, () -> service.search(" "));
- assertThrows(ServiceException.class, () -> service.search("a".repeat(201)));
- assertThrows(ServiceException.class, () -> service.reverse(null, BigDecimal.ONE));
- assertThrows(ServiceException.class, () -> service.reverse(BigDecimal.valueOf(91), BigDecimal.ONE));
- assertThrows(ServiceException.class, () -> service.nearby(BigDecimal.ONE, BigDecimal.ONE, 2001, 10));
- assertThrows(ServiceException.class, () -> service.nearby(BigDecimal.ONE, BigDecimal.ONE, 300, 51));
- assertThrows(ServiceException.class, () -> service.place("osm:123"));
- verifyNoInteractions(selfHosted, google);
- }
- }
|