CockpitFacilityGeometryServiceTest.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package com.zksy.web.controller.cockpit;
  2. import com.zksy.base.domain.GasPipePoint;
  3. import com.zksy.base.domain.ManholeBase;
  4. import com.zksy.base.domain.PipeNetworkBase;
  5. import com.zksy.base.service.GasPipePointService;
  6. import com.zksy.base.service.ManholeBaseService;
  7. import com.zksy.base.service.PipeNetworkBaseService;
  8. import com.zksy.cockpit.foundation.CockpitReadException;
  9. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse;
  10. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Coverage;
  11. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Feature;
  12. import com.zksy.cockpit.geometry.CockpitFacilityGeometryService;
  13. import org.junit.jupiter.api.BeforeEach;
  14. import org.junit.jupiter.api.Test;
  15. import org.junit.jupiter.params.ParameterizedTest;
  16. import org.junit.jupiter.params.provider.CsvSource;
  17. import java.math.BigDecimal;
  18. import java.time.Clock;
  19. import java.time.Instant;
  20. import java.time.ZoneOffset;
  21. import java.util.Arrays;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.stream.Collectors;
  25. import static org.junit.jupiter.api.Assertions.*;
  26. import static org.mockito.Mockito.*;
  27. class CockpitFacilityGeometryServiceTest {
  28. private PipeNetworkBaseService networks;
  29. private GasPipePointService points;
  30. private ManholeBaseService manholes;
  31. private CockpitFacilityGeometryService service;
  32. @BeforeEach
  33. void setUp() {
  34. networks = mock(PipeNetworkBaseService.class);
  35. points = mock(GasPipePointService.class);
  36. manholes = mock(ManholeBaseService.class);
  37. when(networks.list()).thenReturn(Collections.emptyList());
  38. when(points.list()).thenReturn(Collections.emptyList());
  39. when(manholes.list()).thenReturn(Collections.emptyList());
  40. service = new CockpitFacilityGeometryService(networks, points, manholes,
  41. Clock.fixed(Instant.parse("2026-10-12T02:00:00Z"), ZoneOffset.UTC));
  42. }
  43. @Test
  44. void readsAllThreeIndustriesAsActualTwoEndpointLinesWithoutInventingTopology() {
  45. when(networks.list()).thenReturn(Arrays.asList(network("same-id", "0"), network("water", "2"),
  46. network("drain", "1"), network("unknown", "3")));
  47. GasPipePoint point = point(1L, "三通");
  48. point.setNetworkId("same-id");
  49. when(points.list()).thenReturn(Collections.singletonList(point));
  50. ManholeBase manhole = new ManholeBase();
  51. manhole.setManholeId("same-id");
  52. manhole.setLongitude(new BigDecimal("110.5"));
  53. manhole.setLatitude(new BigDecimal("28.5"));
  54. when(manholes.list()).thenReturn(Collections.singletonList(manhole));
  55. CockpitFacilityGeometryResponse response = service.load();
  56. assertEquals("FeatureCollection", response.getType());
  57. assertEquals("2026-10-12T10:00:00+08:00", response.getGeneratedAt());
  58. assertEquals(6, response.getFeatures().size());
  59. assertEquals(6, response.getFeatures().stream().map(Feature::getId).distinct().count());
  60. assertEquals(Arrays.asList("GAS", "WATER_SUPPLY", "DRAINAGE", "UNKNOWN", "GAS", "MANHOLE"),
  61. response.getFeatures().stream().map(item -> item.getProperties().getIndustry()).collect(Collectors.toList()));
  62. Feature gas = response.getFeatures().get(0);
  63. assertEquals("LineString", gas.getGeometry().getType());
  64. assertEquals(Arrays.asList(Arrays.asList(new BigDecimal("110.4"), new BigDecimal("28.4")),
  65. Arrays.asList(new BigDecimal("110.6"), new BigDecimal("28.6"))), gas.getGeometry().getCoordinates());
  66. assertEquals("GAS_PIPE", gas.getProperties().getTypeCode());
  67. assertEquals("PIPE_UNCLASSIFIED", response.getFeatures().get(3).getProperties().getTypeCode());
  68. assertEquals("3", response.getFeatures().get(3).getProperties().getNetworkType());
  69. Feature component = response.getFeatures().get(4);
  70. assertEquals("Point", component.getGeometry().getType());
  71. assertEquals("三通", component.getProperties().getPointType());
  72. assertEquals(gas.getProperties().getNetworkId(), component.getProperties().getNetworkId());
  73. assertEquals(1, coverage(response, "UNKNOWN", "LineString").getRecordCount());
  74. verify(networks).list();
  75. verify(points).list();
  76. verify(manholes).list();
  77. verifyNoMoreInteractions(networks, points, manholes);
  78. }
  79. @Test
  80. void preservesEveryComponentTypeIncludingUnknownAndMissingTypesWithoutFabricatingNodes() {
  81. List<String> types = Arrays.asList("阀门", "调压", "计量", "阴极保护", "弯头", "三通", "其它", "未知原始值", null);
  82. List<GasPipePoint> records = new java.util.ArrayList<>();
  83. for (int i = 0; i < types.size(); i++) {
  84. records.add(point((long) i, types.get(i)));
  85. }
  86. when(points.list()).thenReturn(records);
  87. CockpitFacilityGeometryResponse response = service.load();
  88. assertEquals(types, response.getFeatures().stream()
  89. .map(item -> item.getProperties().getPointType()).collect(Collectors.toList()));
  90. assertEquals(types.size(), response.getFeatures().size());
  91. assertTrue(response.getFeatures().stream().allMatch(item -> item.getProperties().getNetworkId() == null));
  92. }
  93. @ParameterizedTest
  94. @CsvSource({",28.4,MISSING_LONGITUDE", "110.4,,MISSING_LATITUDE", "181,28.4,LONGITUDE_OUT_OF_RANGE",
  95. "-181,28.4,LONGITUDE_OUT_OF_RANGE", "110.4,91,LATITUDE_OUT_OF_RANGE",
  96. "110.4,-91,LATITUDE_OUT_OF_RANGE", "0,0,ZERO_COORDINATE"})
  97. void invalidPointsStayInTheCollectionWithNullGeometryAndSpecificReason(String longitude, String latitude,
  98. String reason) {
  99. GasPipePoint point = point(1L, "阀门");
  100. point.setLongitude(decimal(longitude));
  101. point.setLatitude(decimal(latitude));
  102. when(points.list()).thenReturn(Collections.singletonList(point));
  103. Feature feature = service.load().getFeatures().get(0);
  104. assertNull(feature.getGeometry());
  105. assertEquals("INVALID", feature.getProperties().getCoordinateValidation().getState());
  106. assertEquals(reason, feature.getProperties().getCoordinateValidation().getReason());
  107. assertFalse(feature.getProperties().getCoordinateValidation().isRenderable());
  108. assertEquals(Arrays.asList(decimal(longitude), decimal(latitude)),
  109. feature.getProperties().getSourceCoordinates().get(0));
  110. }
  111. @Test
  112. void invalidEndIsNotMaskedByAnUnverifiedStartAndEntireLineHasNullGeometry() {
  113. PipeNetworkBase line = network("bad-end", "1");
  114. line.setEndLatitude(null);
  115. when(networks.list()).thenReturn(Collections.singletonList(line));
  116. CockpitFacilityGeometryResponse response = service.load();
  117. Feature feature = response.getFeatures().get(0);
  118. assertNull(feature.getGeometry());
  119. assertEquals("MISSING_LATITUDE", feature.getProperties().getCoordinateValidation().getReason());
  120. assertEquals("UNVERIFIED", feature.getProperties().getCoordinateValidations().get(0).getState());
  121. assertEquals("INVALID", feature.getProperties().getCoordinateValidations().get(1).getState());
  122. assertEquals(1, coverage(response, "DRAINAGE", "LineString").getRecordCount());
  123. assertEquals(0, coverage(response, "DRAINAGE", "LineString").getGeometryCount());
  124. }
  125. @Test
  126. void numericBoundsDoNotGrantCoordinateVerificationForAnySource() {
  127. when(networks.list()).thenReturn(Collections.singletonList(network("line", "0")));
  128. when(points.list()).thenReturn(Collections.singletonList(point(1L, "阀门")));
  129. ManholeBase manhole = new ManholeBase();
  130. manhole.setManholeId("well");
  131. manhole.setLongitude(new BigDecimal("-180"));
  132. manhole.setLatitude(new BigDecimal("90"));
  133. when(manholes.list()).thenReturn(Collections.singletonList(manhole));
  134. CockpitFacilityGeometryResponse response = service.load();
  135. for (Feature feature : response.getFeatures()) {
  136. assertNotNull(feature.getGeometry());
  137. assertEquals("UNVERIFIED", feature.getProperties().getCoordinateValidation().getState());
  138. assertEquals("UNVERIFIED_COORDINATE", feature.getProperties().getCoordinateValidation().getReason());
  139. assertEquals("WGS84", feature.getProperties().getCoordinateValidation().getCrs());
  140. assertFalse(feature.getProperties().getCoordinateValidation().isRenderable());
  141. }
  142. assertTrue(response.getCoverage().stream().allMatch(item -> item.getRenderableCount() == 0));
  143. }
  144. @Test
  145. void emptySourcesAreDistinguishedFromUnsupportedIndustryGeometry() {
  146. CockpitFacilityGeometryResponse response = service.load();
  147. assertTrue(response.getFeatures().isEmpty());
  148. assertEquals("EMPTY", coverage(response, "GAS", "Point").getState());
  149. assertEquals("EMPTY", coverage(response, "WATER_SUPPLY", "LineString").getState());
  150. assertEquals("TWO_ENDPOINTS_ONLY", coverage(response, "WATER_SUPPLY", "LineString").getReason());
  151. assertEquals("UNAVAILABLE", coverage(response, "WATER_SUPPLY", "Point").getState());
  152. assertEquals("NO_COMPONENT_SOURCE", coverage(response, "WATER_SUPPLY", "Point").getReason());
  153. assertEquals("NO_TYPED_COMPONENT_SOURCE", coverage(response, "DRAINAGE", "Point").getReason());
  154. assertEquals("NO_COMPONENT_TYPE", coverage(response, "MANHOLE", "Point").getReason());
  155. assertEquals("NO_PIPELINE_SOURCE", coverage(response, "MANHOLE", "LineString").getReason());
  156. }
  157. @Test
  158. void aFailedSourceNeverProducesAnEmptyOrPartialSuccess() {
  159. when(networks.list()).thenReturn(Collections.singletonList(network("line", "0")));
  160. when(points.list()).thenThrow(new IllegalStateException("private database detail"));
  161. CockpitReadException failure = assertThrows(CockpitReadException.class, service::load);
  162. assertEquals("facility geometry source unavailable", failure.getMessage());
  163. verifyNoInteractions(manholes);
  164. }
  165. @Test
  166. void aNullSourceIsAReadFailureRatherThanASuccessfulEmptyCollection() {
  167. when(manholes.list()).thenReturn(null);
  168. assertThrows(CockpitReadException.class, service::load);
  169. }
  170. private static Coverage coverage(CockpitFacilityGeometryResponse response, String industry, String geometry) {
  171. return response.getCoverage().stream().filter(item -> industry.equals(item.getIndustry())
  172. && geometry.equals(item.getGeometryType())).findFirst().orElseThrow();
  173. }
  174. private static PipeNetworkBase network(String id, String type) {
  175. PipeNetworkBase network = new PipeNetworkBase();
  176. network.setNetworkId(id);
  177. network.setNetworkCode("code-" + id);
  178. network.setNetworkType(type);
  179. network.setStartLongitude(new BigDecimal("110.4"));
  180. network.setStartLatitude(new BigDecimal("28.4"));
  181. network.setEndLongitude(new BigDecimal("110.6"));
  182. network.setEndLatitude(new BigDecimal("28.6"));
  183. return network;
  184. }
  185. private static GasPipePoint point(Long id, String type) {
  186. GasPipePoint point = new GasPipePoint();
  187. point.setPointId(id);
  188. point.setPointType(type);
  189. point.setLongitude(new BigDecimal("110.4"));
  190. point.setLatitude(new BigDecimal("28.4"));
  191. return point;
  192. }
  193. private static BigDecimal decimal(String value) {
  194. return value == null ? null : new BigDecimal(value);
  195. }
  196. }