CockpitFacilityGeometryControllerTest.java 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package com.zksy.web.controller.cockpit;
  2. import com.zksy.base.service.GasPipePointService;
  3. import com.zksy.base.service.ManholeBaseService;
  4. import com.zksy.base.service.PipeNetworkBaseService;
  5. import com.zksy.cockpit.foundation.CockpitReadException;
  6. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse;
  7. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Feature;
  8. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Properties;
  9. import com.zksy.cockpit.geometry.CockpitFacilityGeometryService;
  10. import com.zksy.cockpit.foundation.CockpitCoordinateValidator;
  11. import org.junit.jupiter.api.BeforeEach;
  12. import org.junit.jupiter.api.Test;
  13. import org.springframework.test.web.servlet.MockMvc;
  14. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  15. import java.math.BigDecimal;
  16. import java.time.Clock;
  17. import java.time.Instant;
  18. import java.time.ZoneOffset;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import static org.hamcrest.Matchers.hasSize;
  22. import static org.hamcrest.Matchers.nullValue;
  23. import static org.mockito.Mockito.*;
  24. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  25. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  26. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
  27. /** Standalone MVC tests lock JSON/routing, not the deployed authentication chain. */
  28. class CockpitFacilityGeometryControllerTest {
  29. private static final String URL = "/api/cockpit/v1/infrastructure/geometry";
  30. private CockpitFacilityGeometryService service;
  31. private MockMvc mvc;
  32. @BeforeEach
  33. void setUp() {
  34. service = mock(CockpitFacilityGeometryService.class);
  35. mvc = MockMvcBuilders.standaloneSetup(new CockpitFacilityGeometryController(service,
  36. Clock.fixed(Instant.parse("2026-10-12T02:00:00Z"), ZoneOffset.UTC))).build();
  37. }
  38. @Test
  39. void returnsGeoJsonInsideTheExistingAjaxEnvelopeWithoutRequiringAnAuthorizationHeader() throws Exception {
  40. PipeNetworkBaseService networks = mock(PipeNetworkBaseService.class);
  41. GasPipePointService points = mock(GasPipePointService.class);
  42. ManholeBaseService manholes = mock(ManholeBaseService.class);
  43. when(networks.list()).thenReturn(Collections.emptyList());
  44. when(points.list()).thenReturn(Collections.emptyList());
  45. when(manholes.list()).thenReturn(Collections.emptyList());
  46. CockpitFacilityGeometryResponse response = new CockpitFacilityGeometryService(networks, points, manholes,
  47. Clock.fixed(Instant.parse("2026-10-12T02:00:00Z"), ZoneOffset.UTC)).load();
  48. when(service.load()).thenReturn(response);
  49. mvc.perform(get(URL))
  50. .andExpect(status().isOk())
  51. .andExpect(jsonPath("$.code").value(200))
  52. .andExpect(jsonPath("$.data.type").value("FeatureCollection"))
  53. .andExpect(jsonPath("$.data.features", hasSize(0)))
  54. .andExpect(jsonPath("$.data.coverage", hasSize(9)))
  55. .andExpect(jsonPath("$.data.coverage[4].state").value("UNAVAILABLE"));
  56. }
  57. @Test
  58. void invalidGeometryIsExplicitJsonNullWithRawCoordinatesAndSameValidationShape() throws Exception {
  59. Properties properties = new Properties("gas_pipe_point", "1", "G1", "阀门", "GAS", "GAS_PIPE_POINT",
  60. null, "阀门", null, Collections.singletonList(Arrays.asList(null, new BigDecimal("28.4"))),
  61. CockpitCoordinateValidator.validate(null, new BigDecimal("28.4"), true),
  62. Collections.singletonList(CockpitCoordinateValidator.validate(null, new BigDecimal("28.4"), true)));
  63. when(service.load()).thenReturn(new CockpitFacilityGeometryResponse("2026-10-12T10:00:00+08:00",
  64. Collections.singletonList(new Feature("gas_pipe_point:1", null, properties)), Collections.emptyList()));
  65. mvc.perform(get(URL))
  66. .andExpect(status().isOk())
  67. .andExpect(jsonPath("$.data.features[0].type").value("Feature"))
  68. .andExpect(jsonPath("$.data.features[0].geometry").value(nullValue()))
  69. .andExpect(jsonPath("$.data.features[0].properties.networkId").value(nullValue()))
  70. .andExpect(jsonPath("$.data.features[0].properties.pointType").value("阀门"))
  71. .andExpect(jsonPath("$.data.features[0].properties.sourceCoordinates[0][0]").value(nullValue()))
  72. .andExpect(jsonPath("$.data.features[0].properties.coordinateValidation.state").value("INVALID"))
  73. .andExpect(jsonPath("$.data.features[0].properties.coordinateValidation.reason").value("MISSING_LONGITUDE"))
  74. .andExpect(jsonPath("$.data.features[0].properties.coordinateValidation.renderable").value(false))
  75. .andExpect(jsonPath("$.data.features[0].properties.coordinateValidation.crs").value("WGS84"));
  76. }
  77. @Test
  78. void sourceFailureReturns500AndStableCategoryRatherThanSuccess() throws Exception {
  79. when(service.load()).thenThrow(new CockpitReadException("private database details"));
  80. mvc.perform(get(URL).header("X-Request-Id", "geometry-failure"))
  81. .andExpect(status().isInternalServerError())
  82. .andExpect(jsonPath("$.code").value(500))
  83. .andExpect(jsonPath("$.data.category").value("SERVER_ERROR"))
  84. .andExpect(jsonPath("$.data.message").value("facility geometry source unavailable"))
  85. .andExpect(jsonPath("$.data.requestId").value("geometry-failure"));
  86. }
  87. @Test
  88. void serializesActualServicePointAndLineCoordinatesInLongitudeLatitudeOrder() throws Exception {
  89. PipeNetworkBaseService networks = mock(PipeNetworkBaseService.class);
  90. GasPipePointService points = mock(GasPipePointService.class);
  91. ManholeBaseService manholes = mock(ManholeBaseService.class);
  92. com.zksy.base.domain.PipeNetworkBase line = new com.zksy.base.domain.PipeNetworkBase();
  93. line.setNetworkId("gas-line");
  94. line.setNetworkType("0");
  95. line.setStartLongitude(new BigDecimal("110.4"));
  96. line.setStartLatitude(new BigDecimal("28.4"));
  97. line.setEndLongitude(new BigDecimal("110.6"));
  98. line.setEndLatitude(new BigDecimal("28.6"));
  99. com.zksy.base.domain.GasPipePoint point = new com.zksy.base.domain.GasPipePoint();
  100. point.setPointId(1L);
  101. point.setPointType("三通");
  102. point.setNetworkId("gas-line");
  103. point.setLongitude(new BigDecimal("110.5"));
  104. point.setLatitude(new BigDecimal("28.5"));
  105. when(networks.list()).thenReturn(Collections.singletonList(line));
  106. when(points.list()).thenReturn(Collections.singletonList(point));
  107. when(manholes.list()).thenReturn(Collections.emptyList());
  108. CockpitFacilityGeometryService actual = new CockpitFacilityGeometryService(networks, points, manholes,
  109. Clock.fixed(Instant.parse("2026-10-12T02:00:00Z"), ZoneOffset.UTC));
  110. mvc = MockMvcBuilders.standaloneSetup(new CockpitFacilityGeometryController(actual,
  111. Clock.fixed(Instant.parse("2026-10-12T02:00:00Z"), ZoneOffset.UTC))).build();
  112. mvc.perform(get(URL))
  113. .andExpect(status().isOk())
  114. .andExpect(jsonPath("$.data.features", hasSize(2)))
  115. .andExpect(jsonPath("$.data.features[0].geometry.type").value("LineString"))
  116. .andExpect(jsonPath("$.data.features[0].geometry.coordinates", hasSize(2)))
  117. .andExpect(jsonPath("$.data.features[0].geometry.coordinates[0][0]").value(110.4))
  118. .andExpect(jsonPath("$.data.features[0].geometry.coordinates[0][1]").value(28.4))
  119. .andExpect(jsonPath("$.data.features[0].geometry.coordinates[1][0]").value(110.6))
  120. .andExpect(jsonPath("$.data.features[0].geometry.coordinates[1][1]").value(28.6))
  121. .andExpect(jsonPath("$.data.features[1].geometry.type").value("Point"))
  122. .andExpect(jsonPath("$.data.features[1].geometry.coordinates", hasSize(2)))
  123. .andExpect(jsonPath("$.data.features[1].geometry.coordinates[0]").value(110.5))
  124. .andExpect(jsonPath("$.data.features[1].geometry.coordinates[1]").value(28.5))
  125. .andExpect(jsonPath("$.data.features[1].properties.pointType").value("三通"))
  126. .andExpect(jsonPath("$.data.features[1].properties.networkId").value("gas-line"))
  127. .andExpect(jsonPath("$.data.features[1].properties.coordinateValidation.state").value("UNVERIFIED"))
  128. .andExpect(jsonPath("$.data.features[1].properties.coordinateValidation.renderable").value(false));
  129. }
  130. @Test
  131. void transactionProxyFailureIsAlsoMappedToSanitized500() throws Exception {
  132. when(service.load()).thenThrow(new org.springframework.transaction.CannotCreateTransactionException(
  133. "private connection detail"));
  134. mvc.perform(get(URL).header("X-Request-Id", "transaction-failure"))
  135. .andExpect(status().isInternalServerError())
  136. .andExpect(jsonPath("$.code").value(500))
  137. .andExpect(jsonPath("$.data.category").value("SERVER_ERROR"))
  138. .andExpect(jsonPath("$.data.message").value("facility geometry source unavailable"))
  139. .andExpect(jsonPath("$.data.requestId").value("transaction-failure"));
  140. }
  141. @Test
  142. void postIsNotAWriteEntryPoint() throws Exception {
  143. mvc.perform(post(URL)).andExpect(status().isMethodNotAllowed());
  144. verifyNoInteractions(service);
  145. }
  146. }