FlashDeliveryControllerContractTest.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package com.ruoyi.app.flashdelivery.controller;
  2. import com.ruoyi.app.flashdelivery.dto.FlashDeliveryCreateRequest;
  3. import com.ruoyi.app.flashdelivery.dto.FlashDeliveryOrderDetailView;
  4. import com.ruoyi.app.flashdelivery.dto.FlashDeliveryProofRequest;
  5. import com.ruoyi.app.flashdelivery.dto.FlashDeliveryDeliverRequest;
  6. import com.ruoyi.app.flashdelivery.dto.FlashDeliveryStopDeliverRequest;
  7. import com.ruoyi.app.flashdelivery.dto.FlashDeliveryQuoteView;
  8. import com.ruoyi.app.flashdelivery.exception.FlashDeliveryQuoteChangedException;
  9. import com.ruoyi.app.flashdelivery.service.FlashDeliveryApplicationService;
  10. import com.ruoyi.common.core.domain.AjaxResult;
  11. import com.ruoyi.common.utils.spring.SpringUtils;
  12. import org.junit.jupiter.api.AfterAll;
  13. import org.junit.jupiter.api.BeforeAll;
  14. import org.junit.jupiter.api.Test;
  15. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  16. import org.springframework.beans.factory.support.DefaultListableBeanFactory;
  17. import org.springframework.context.support.StaticMessageSource;
  18. import org.springframework.security.access.prepost.PreAuthorize;
  19. import org.springframework.test.util.ReflectionTestUtils;
  20. import org.springframework.web.bind.annotation.RequestBody;
  21. import org.springframework.web.bind.annotation.RequestHeader;
  22. import org.springframework.web.bind.annotation.RequestParam;
  23. import org.springframework.web.bind.annotation.GetMapping;
  24. import com.ruoyi.common.annotation.Log;
  25. import com.ruoyi.system.utils.Auth;
  26. import com.ruoyi.system.utils.JwtUtil;
  27. import java.lang.reflect.Method;
  28. import java.math.BigDecimal;
  29. import static org.junit.jupiter.api.Assertions.assertArrayEquals;
  30. import static org.junit.jupiter.api.Assertions.assertNotNull;
  31. import static org.junit.jupiter.api.Assertions.assertSame;
  32. import static org.junit.jupiter.api.Assertions.assertThrows;
  33. import static org.junit.jupiter.api.Assertions.assertTrue;
  34. import static org.mockito.Mockito.mock;
  35. import static org.mockito.Mockito.verify;
  36. import static org.mockito.Mockito.when;
  37. class FlashDeliveryControllerContractTest {
  38. @Test
  39. void newStopDeliveryAcceptsAllBusinessParametersInBody() throws Exception {
  40. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  41. var mvc = org.springframework.test.web.servlet.setup.MockMvcBuilders
  42. .standaloneSetup(new FlashDeliveryRiderController(service)).build();
  43. mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders
  44. .post("/system/flashDelivery/rider/orders/stops/deliver")
  45. .header("token", JwtUtil.token("8", "rider"))
  46. .contentType(org.springframework.http.MediaType.APPLICATION_JSON)
  47. .content("{\"orderId\":10,\"stopId\":13,\"pinCode\":\"1111\",\"imageUrls\":[\"/profile/d.jpg\"]}"))
  48. .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isOk());
  49. var request = org.mockito.ArgumentCaptor.forClass(FlashDeliveryDeliverRequest.class);
  50. verify(service).deliverAtStop(org.mockito.ArgumentMatchers.eq(8L), org.mockito.ArgumentMatchers.eq(10L),
  51. org.mockito.ArgumentMatchers.eq(13L), request.capture());
  52. org.junit.jupiter.api.Assertions.assertEquals("1111", request.getValue().getPinCode());
  53. org.junit.jupiter.api.Assertions.assertEquals(java.util.List.of("/profile/d.jpg"), request.getValue().getImageUrls());
  54. org.junit.jupiter.api.Assertions.assertEquals(13L, request.getValue().getStopId());
  55. }
  56. @Test
  57. void newStopDeliveryRejectsMissingOrNonPositiveBodyIds() throws Exception {
  58. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  59. var mvc = org.springframework.test.web.servlet.setup.MockMvcBuilders
  60. .standaloneSetup(new FlashDeliveryRiderController(service))
  61. .setControllerAdvice(new com.ruoyi.framework.web.exception.GlobalExceptionHandler()).build();
  62. for (String body : java.util.List.of("{}", "{\"orderId\":0,\"stopId\":13}",
  63. "{\"orderId\":-1,\"stopId\":13}", "{\"orderId\":10}",
  64. "{\"orderId\":10,\"stopId\":0}", "{\"orderId\":10,\"stopId\":-1}")) {
  65. mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders
  66. .post("/system/flashDelivery/rider/orders/stops/deliver")
  67. .header("token", JwtUtil.token("8", "rider"))
  68. .contentType(org.springframework.http.MediaType.APPLICATION_JSON).content(body))
  69. .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isOk())
  70. .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.code").value(500));
  71. }
  72. org.mockito.Mockito.verifyNoInteractions(service);
  73. }
  74. @Test
  75. void newV2EndpointsUseBodyDtosAndHeaderAuthentication() throws Exception {
  76. for (Method method : java.util.List.of(
  77. FlashDeliveryUserController.class.getMethod("quoteV2", String.class,
  78. com.ruoyi.app.flashdelivery.dto.FlashDeliveryV2QuoteRequest.class),
  79. FlashDeliveryUserController.class.getMethod("createV2", String.class,
  80. com.ruoyi.app.flashdelivery.dto.FlashDeliveryV2CreateRequest.class),
  81. FlashDeliveryUserController.class.getMethod("quoteAddressV2", String.class,
  82. com.ruoyi.app.flashdelivery.dto.FlashDeliveryV2AddressChangeRequest.class),
  83. FlashDeliveryUserController.class.getMethod("updateAddressV2", String.class,
  84. com.ruoyi.app.flashdelivery.dto.FlashDeliveryV2AddressConfirmRequest.class),
  85. FlashDeliveryRiderController.class.getMethod("deliverAtStop", String.class,
  86. FlashDeliveryStopDeliverRequest.class))) {
  87. assertNotNull(method.getAnnotation(Auth.class));
  88. assertNotNull(method.getParameters()[0].getAnnotation(RequestHeader.class));
  89. assertNotNull(method.getParameters()[1].getAnnotation(RequestBody.class));
  90. org.junit.jupiter.api.Assertions.assertEquals(2, method.getParameterCount());
  91. assertTrue(java.util.Arrays.stream(method.getAnnotation(org.springframework.web.bind.annotation.PostMapping.class)
  92. .value()).noneMatch(path -> path.contains("{")));
  93. }
  94. }
  95. @Test
  96. void perStopFulfilmentUsesTokenExplicitPathsAndBodyDtos() throws Exception {
  97. for (Method method : java.util.List.of(
  98. FlashDeliveryRiderController.class.getMethod("pickupStop", String.class, Long.class, Long.class, FlashDeliveryProofRequest.class),
  99. FlashDeliveryRiderController.class.getMethod("target", String.class, Long.class, Long.class,
  100. com.ruoyi.app.flashdelivery.dto.FlashDeliveryTargetRequest.class))) {
  101. assertNotNull(method.getAnnotation(Auth.class));
  102. assertNotNull(method.getParameters()[0].getAnnotation(RequestHeader.class));
  103. assertNotNull(method.getParameters()[1].getAnnotation(org.springframework.web.bind.annotation.PathVariable.class));
  104. assertNotNull(method.getParameters()[2].getAnnotation(org.springframework.web.bind.annotation.PathVariable.class));
  105. assertNotNull(method.getParameters()[3].getAnnotation(RequestBody.class));
  106. }
  107. }
  108. private static ConfigurableListableBeanFactory originalBeanFactory;
  109. @BeforeAll
  110. static void initializeMessages() {
  111. originalBeanFactory = (ConfigurableListableBeanFactory)
  112. ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
  113. DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
  114. StaticMessageSource messageSource = new StaticMessageSource();
  115. messageSource.setUseCodeAsDefaultMessage(true);
  116. beanFactory.registerSingleton("messageSource", messageSource);
  117. new SpringUtils().postProcessBeanFactory(beanFactory);
  118. }
  119. @AfterAll
  120. static void restoreMessages() {
  121. new SpringUtils().postProcessBeanFactory(originalBeanFactory);
  122. }
  123. @Test
  124. void waitingOrderEditingEndpointsExistAndUseExplicitAuthenticatedDtos() {
  125. java.util.Set<String> paths = new java.util.HashSet<>();
  126. for (Method method : FlashDeliveryUserController.class.getDeclaredMethods()) {
  127. var post = method.getAnnotation(org.springframework.web.bind.annotation.PostMapping.class);
  128. if (post == null) continue;
  129. for (String path : post.value()) {
  130. if (!java.util.Set.of("/orders/{id}/address/quote", "/orders/address", "/orders/tip").contains(path)) continue;
  131. paths.add(path);
  132. assertNotNull(method.getAnnotation(Auth.class));
  133. assertNotNull(method.getParameters()[0].getAnnotation(RequestHeader.class));
  134. int bodyIndex;
  135. if (path.endsWith("/quote")) {
  136. bodyIndex = 2;
  137. assertNotNull(method.getParameters()[1].getAnnotation(org.springframework.web.bind.annotation.PathVariable.class));
  138. } else {
  139. org.junit.jupiter.api.Assertions.assertEquals(2, method.getParameterCount());
  140. bodyIndex = 1;
  141. }
  142. assertNotNull(method.getParameters()[bodyIndex].getAnnotation(RequestBody.class));
  143. assertTrue(!java.util.Map.class.isAssignableFrom(method.getParameters()[bodyIndex].getType()));
  144. }
  145. }
  146. assertTrue(paths.containsAll(java.util.Set.of("/orders/{id}/address/quote",
  147. "/orders/address", "/orders/tip")), "Missing waiting-order editing endpoints");
  148. }
  149. @Test
  150. void appControllersReadIdentityFromTokenAndUseRequestBodies() throws Exception {
  151. Method create = FlashDeliveryUserController.class.getMethod("create", String.class,
  152. FlashDeliveryCreateRequest.class);
  153. assertNotNull(create.getParameters()[0].getAnnotation(RequestHeader.class));
  154. assertNotNull(create.getParameters()[1].getAnnotation(RequestBody.class));
  155. Method pickup = FlashDeliveryRiderController.class.getMethod("pickup", String.class,
  156. Long.class, FlashDeliveryProofRequest.class);
  157. assertNotNull(pickup.getParameters()[0].getAnnotation(RequestHeader.class));
  158. assertNotNull(pickup.getParameters()[2].getAnnotation(RequestBody.class));
  159. Method deliver = FlashDeliveryRiderController.class.getMethod("deliver", String.class,
  160. Long.class, FlashDeliveryDeliverRequest.class);
  161. assertNotNull(deliver.getParameters()[2].getAnnotation(RequestBody.class));
  162. }
  163. @Test
  164. void appOrderListsUseSimplePageAndRiderFoodOrderParameters() throws Exception {
  165. Method userOrders = FlashDeliveryUserController.class.getMethod("orders",
  166. String.class, int.class, int.class, String.class);
  167. assertNotNull(userOrders.getParameters()[0].getAnnotation(RequestHeader.class));
  168. assertNotNull(userOrders.getParameters()[1].getAnnotation(RequestParam.class));
  169. assertNotNull(userOrders.getParameters()[2].getAnnotation(RequestParam.class));
  170. assertNotNull(userOrders.getParameters()[3].getAnnotation(RequestParam.class));
  171. assertTrue(!userOrders.getParameters()[3].getAnnotation(RequestParam.class).required());
  172. Method riderOrders = FlashDeliveryRiderController.class.getMethod("orders",
  173. String.class, int.class, int.class, String.class, BigDecimal.class, BigDecimal.class);
  174. assertArrayEquals(new String[]{"/orders"}, riderOrders.getAnnotation(GetMapping.class).value());
  175. for (int index = 1; index < riderOrders.getParameterCount(); index++) {
  176. assertNotNull(riderOrders.getParameters()[index].getAnnotation(RequestParam.class));
  177. }
  178. assertThrows(NoSuchMethodException.class,
  179. () -> FlashDeliveryRiderController.class.getMethod("available",
  180. String.class, int.class, int.class, String.class));
  181. assertThrows(NoSuchMethodException.class,
  182. () -> FlashDeliveryRiderController.class.getMethod("mine",
  183. String.class, int.class, int.class, String.class, String.class));
  184. }
  185. @Test
  186. void riderOrderCountEndpointUsesOptionalCoordinatesAndReturnsCounts() throws Exception {
  187. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  188. var counts = new com.ruoyi.app.flashdelivery.dto.FlashDeliveryRiderOrderCountView();
  189. when(service.riderOrderCounts(8L, BigDecimal.ONE, BigDecimal.TEN)).thenReturn(counts);
  190. AjaxResult result = new FlashDeliveryRiderController(service)
  191. .orderCount(JwtUtil.token("8", "rider"), BigDecimal.ONE, BigDecimal.TEN);
  192. assertSame(counts, result.get(AjaxResult.DATA_TAG));
  193. Method orderCount = FlashDeliveryRiderController.class.getMethod("orderCount",
  194. String.class, BigDecimal.class, BigDecimal.class);
  195. assertArrayEquals(new String[]{"/orderCount"}, orderCount.getAnnotation(GetMapping.class).value());
  196. assertNotNull(orderCount.getParameters()[0].getAnnotation(RequestHeader.class));
  197. for (int index = 1; index < orderCount.getParameterCount(); index++) {
  198. assertTrue(!orderCount.getParameters()[index].getAnnotation(RequestParam.class).required());
  199. }
  200. }
  201. @Test
  202. void appDetailAndAcceptPutDirectOrderDtoInAjaxData() {
  203. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  204. FlashDeliveryOrderDetailView detail = new FlashDeliveryOrderDetailView();
  205. detail.setId(10L);
  206. when(service.userDetail(7L, 10L)).thenReturn(detail);
  207. when(service.riderDetail(8L, 10L)).thenReturn(detail);
  208. when(service.accept(8L, 10L)).thenReturn(detail);
  209. AjaxResult userResult = new FlashDeliveryUserController(service)
  210. .detail(JwtUtil.token("7", "user"), 10L);
  211. AjaxResult riderResult = new FlashDeliveryRiderController(service)
  212. .detail(JwtUtil.token("8", "rider"), 10L);
  213. AjaxResult acceptResult = new FlashDeliveryRiderController(service)
  214. .accept(JwtUtil.token("8", "rider"), 10L);
  215. assertSame(detail, userResult.get(AjaxResult.DATA_TAG));
  216. assertSame(detail, riderResult.get(AjaxResult.DATA_TAG));
  217. assertSame(detail, acceptResult.get(AjaxResult.DATA_TAG));
  218. }
  219. @Test
  220. void editingEndpointsReturnServiceQuoteAndUpdatedDetails() throws Exception {
  221. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  222. var change = new com.ruoyi.app.flashdelivery.dto.FlashDeliveryAddressChangeRequest();
  223. var json = new com.fasterxml.jackson.databind.ObjectMapper();
  224. var confirmation = json.readValue("{\"orderId\":10,\"orderVersion\":2}",
  225. com.ruoyi.app.flashdelivery.dto.FlashDeliveryAddressConfirmRequest.class);
  226. var tip = json.readValue("{\"orderId\":10,\"orderVersion\":2,\"additionalTipAmount\":20}",
  227. com.ruoyi.app.flashdelivery.dto.FlashDeliveryTipAddRequest.class);
  228. FlashDeliveryQuoteView quote = new FlashDeliveryQuoteView();
  229. FlashDeliveryOrderDetailView detail = new FlashDeliveryOrderDetailView();
  230. when(service.quoteAddress(7L, 10L, change)).thenReturn(quote);
  231. when(service.updateAddress(7L, 10L, confirmation)).thenReturn(detail);
  232. when(service.addTip(7L, 10L, tip)).thenReturn(detail);
  233. var controller = new FlashDeliveryUserController(service);
  234. String token = JwtUtil.token("7", "user");
  235. assertSame(quote, controller.quoteAddress(token, 10L, change).get(AjaxResult.DATA_TAG));
  236. assertSame(detail, controller.updateAddress(token, confirmation).get(AjaxResult.DATA_TAG));
  237. assertSame(detail, controller.addTip(token, tip).get(AjaxResult.DATA_TAG));
  238. }
  239. @Test
  240. void rateEndpointDelegatesSenderAndStarsToService() {
  241. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  242. var request = new com.ruoyi.app.flashdelivery.dto.FlashDeliveryRatingRequest();
  243. request.setOrderId(10L);
  244. request.setStars(4.5);
  245. new FlashDeliveryUserController(service).rate(JwtUtil.token("7", "user"), request);
  246. verify(service).rateRider(7L, 10L, request);
  247. }
  248. @Test
  249. void detailByOrderNoEndpointsDelegateToService() {
  250. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  251. FlashDeliveryOrderDetailView detail = new FlashDeliveryOrderDetailView();
  252. when(service.userDetailByOrderNo(7L, "FL-1")).thenReturn(detail);
  253. when(service.riderDetailByOrderNo(8L, "FL-1")).thenReturn(detail);
  254. AjaxResult userResult = new FlashDeliveryUserController(service)
  255. .detailByOrderNo(JwtUtil.token("7", "user"), "FL-1");
  256. AjaxResult riderResult = new FlashDeliveryRiderController(service)
  257. .detailByOrderNo(JwtUtil.token("8", "rider"), "FL-1");
  258. assertSame(detail, userResult.get(AjaxResult.DATA_TAG));
  259. assertSame(detail, riderResult.get(AjaxResult.DATA_TAG));
  260. }
  261. @Test
  262. void addressConfirmationReturnsLatestQuoteForAnotherUserConfirmation() {
  263. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  264. var input = new com.ruoyi.app.flashdelivery.dto.FlashDeliveryAddressConfirmRequest();
  265. input.setOrderId(10L);
  266. FlashDeliveryQuoteView latest = new FlashDeliveryQuoteView();
  267. latest.setOrderVersion(2);
  268. latest.setAmount(123L);
  269. when(service.updateAddress(7L, 10L, input)).thenThrow(new FlashDeliveryQuoteChangedException(latest));
  270. AjaxResult result = new FlashDeliveryUserController(service)
  271. .updateAddress(JwtUtil.token("7", "user"), input);
  272. assertSame(latest, result.get(AjaxResult.DATA_TAG));
  273. assertTrue(((Number) result.get(AjaxResult.CODE_TAG)).intValue() != 200);
  274. }
  275. @Test
  276. void createReturnsLatestQuoteWhenClientQuoteChanged() {
  277. FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
  278. FlashDeliveryCreateRequest request = new FlashDeliveryCreateRequest();
  279. FlashDeliveryQuoteView latest = new FlashDeliveryQuoteView();
  280. latest.setAmount(120L);
  281. when(service.create(7L, request)).thenThrow(new FlashDeliveryQuoteChangedException(latest));
  282. AjaxResult result = new FlashDeliveryUserController(service)
  283. .create(JwtUtil.token("7", "user"), request);
  284. assertSame(latest, result.get(AjaxResult.DATA_TAG));
  285. assertTrue(((Number) result.get(AjaxResult.CODE_TAG)).intValue() != 200);
  286. }
  287. @Test
  288. void everyAdminEndpointDeclaresPlatformPermission() {
  289. for (Method method : FlashDeliveryAdminController.class.getDeclaredMethods()) {
  290. if (method.isSynthetic()) continue;
  291. assertNotNull(method.getAnnotation(PreAuthorize.class), method.getName());
  292. assertTrue(method.getAnnotation(PreAuthorize.class).value().contains("flash:"));
  293. }
  294. }
  295. @Test
  296. void pricingAndOrderInterventionsAreOperationAudited() throws Exception {
  297. assertNotNull(FlashDeliveryAdminController.class.getMethod("createPricing",
  298. com.ruoyi.app.flashdelivery.dto.FlashDeliveryPricingRequest.class).getAnnotation(Log.class));
  299. assertNotNull(FlashDeliveryAdminController.class.getMethod("updatePricing", Long.class,
  300. com.ruoyi.app.flashdelivery.dto.FlashDeliveryPricingRequest.class).getAnnotation(Log.class));
  301. assertNotNull(FlashDeliveryAdminController.class.getMethod("deletePricing", Long.class).getAnnotation(Log.class));
  302. assertNotNull(FlashDeliveryAdminController.class.getMethod("cancel", Long.class,
  303. com.ruoyi.app.flashdelivery.dto.FlashDeliveryReasonRequest.class).getAnnotation(Log.class));
  304. assertNotNull(FlashDeliveryAdminController.class.getMethod("complete", Long.class).getAnnotation(Log.class));
  305. }
  306. @Test
  307. void everyAppEndpointDeclaresMethodLevelTokenAuthentication() {
  308. for (Class<?> controller : new Class<?>[]{FlashDeliveryUserController.class,
  309. FlashDeliveryRiderController.class}) {
  310. for (Method method : controller.getDeclaredMethods()) {
  311. if (!java.lang.reflect.Modifier.isPublic(method.getModifiers())) continue;
  312. assertNotNull(method.getAnnotation(Auth.class), controller.getSimpleName() + "." + method.getName());
  313. }
  314. }
  315. }
  316. }