package com.ruoyi.app.flashdelivery.route; import com.alibaba.fastjson2.JSONObject; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; /** 自部署地图路线 Provider 单测:响应解析、单次多站点请求保持用户顺序、越界不降级语义。 */ class SelfHostedRouteDistanceProviderTest { @Test void realRequestMustSelectMotorcycleInsteadOfMapDefaultCar() throws Exception { com.sun.net.httpserver.HttpServer server = com.sun.net.httpserver.HttpServer.create( new java.net.InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/route/multi", exchange -> { boolean motorcycle = exchange.getRequestURI().getQuery().contains("profile=motorcycle"); byte[] body = ("{\"distance_meters\":" + (motorcycle ? 1000 : 2000) + ",\"duration_seconds\":120}").getBytes(java.nio.charset.StandardCharsets.UTF_8); exchange.sendResponseHeaders(200, body.length); try (var output = exchange.getResponseBody()) { output.write(body); } }); server.start(); try { com.ruoyi.app.map.SelfHostedMapClient client = new com.ruoyi.app.map.SelfHostedMapClient(); SelfHostedRouteDistanceProvider provider = new SelfHostedRouteDistanceProvider(client); org.springframework.test.util.ReflectionTestUtils.setField(client, "baseUrl", "http://127.0.0.1:" + server.getAddress().getPort()); assertEquals(1000, provider.route(List.of(point("25", "121"), point("25.1", "121.1")), 1).distanceMeters()); } finally { server.stop(0); } } /** 捕获请求站点并返回固定响应,替代 HTTP。 */ static class StubProvider extends SelfHostedRouteDistanceProvider { StubProvider() { super(org.mockito.Mockito.mock(com.ruoyi.app.map.SelfHostedMapClient.class)); } JSONObject response; RuntimeException error; List requestedPoints; @Override JSONObject fetchRoute(List points, Integer vehicleType) { requestedPoints = points; if (error != null) throw error; return response; } } private static GeoPoint point(String lat, String lng) { return new GeoPoint(new BigDecimal(lat), new BigDecimal(lng)); } @Test void parsesOsmDistanceAndDurationAsRouteSource() { JSONObject response = new JSONObject(); response.put("distance_meters", 1234); response.put("duration_seconds", 567); StubProvider provider = new StubProvider(); provider.response = response; assertEquals(new RouteDistance(1234, 567, "ROUTE"), provider.route(List.of(point("25.0", "121.0"), point("25.1", "121.1")), null)); } @Test void invalidDistanceFailsInsteadOfFabricating() { JSONObject response = new JSONObject(); response.put("distance_meters", -1); assertThrows(IllegalStateException.class, () -> new StubProvider().parseRoute(response)); assertThrows(IllegalStateException.class, () -> new StubProvider().parseRoute(null)); } @Test void multiStopGoesInOneRequestPreservingUserOrder() { JSONObject response = new JSONObject(); response.put("distance_meters", 3500); response.put("duration_seconds", 400); List points = List.of(point("25.0", "121.0"), point("25.1", "121.0"), point("25.1", "121.1")); StubProvider provider = new StubProvider(); provider.response = response; assertEquals(new RouteDistance(3500, 400, "ROUTE"), provider.route(points, null)); // 全部站点一次提交、顺序不变,由地图计算整线道路距离 assertEquals(points, provider.requestedPoints); } @Test void anyMapFailureFailsTheWholeRoute() { StubProvider provider = new StubProvider(); provider.error = new IllegalStateException("self hosted map http status 502"); assertThrows(IllegalStateException.class, () -> provider.route(List.of(point("25.0", "121.0"), point("25.1", "121.0"), point("25.1", "121.1")), null)); } @Test void outsideRegionErrorIsTypedForNonDegradation() { StubProvider provider = new StubProvider(); provider.error = new RouteOutsideRegionException("outside"); assertThrows(RouteOutsideRegionException.class, () -> provider.route(List.of(point("25.0", "121.0"), point("30.0", "124.0")), null)); } }