OmgPaymentControllerTest.java 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package com.ruoyi.app.omgpay;
  2. import ch.qos.logback.classic.Logger;
  3. import ch.qos.logback.classic.spi.ILoggingEvent;
  4. import ch.qos.logback.core.read.ListAppender;
  5. import com.ruoyi.app.omgpay.dto.OmgCreatePaymentRequest;
  6. import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
  7. import com.ruoyi.app.omgpay.dto.OmgPaymentErrorResponse;
  8. import com.ruoyi.common.core.domain.AjaxResult;
  9. import com.ruoyi.common.utils.MessageUtils;
  10. import com.ruoyi.system.omgpay.service.OmgIpnAuditService;
  11. import org.junit.jupiter.api.AfterEach;
  12. import org.junit.jupiter.api.BeforeEach;
  13. import org.junit.jupiter.api.Test;
  14. import org.mockito.MockedStatic;
  15. import org.slf4j.LoggerFactory;
  16. import org.springframework.web.bind.annotation.PostMapping;
  17. import org.springframework.web.bind.annotation.RequestBody;
  18. import org.springframework.web.bind.annotation.RequestHeader;
  19. import org.springframework.web.bind.annotation.RequestMapping;
  20. import java.lang.reflect.Field;
  21. import java.lang.reflect.Method;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.stream.Collectors;
  25. import static org.junit.jupiter.api.Assertions.*;
  26. import static org.mockito.Mockito.*;
  27. class OmgPaymentControllerTest {
  28. private MockedStatic<MessageUtils> messages;
  29. @BeforeEach
  30. void setUpMessages() {
  31. messages = mockStatic(MessageUtils.class);
  32. messages.when(() -> MessageUtils.message(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
  33. }
  34. @AfterEach
  35. void closeMessages() {
  36. messages.close();
  37. }
  38. @Test
  39. void exposesOnlyExplicitCreateContract() throws Exception {
  40. RequestMapping mapping = OmgPaymentController.class.getAnnotation(RequestMapping.class);
  41. assertArrayEquals(new String[]{"/pay/omg"}, mapping.value());
  42. Method create = OmgPaymentController.class.getDeclaredMethod("create", String.class,
  43. String.class, OmgCreatePaymentRequest.class);
  44. assertArrayEquals(new String[]{"/create"}, create.getAnnotation(PostMapping.class).value());
  45. assertEquals("token", create.getParameters()[0].getAnnotation(RequestHeader.class).name());
  46. assertNotNull(create.getParameters()[1].getAnnotation(RequestHeader.class));
  47. assertNotNull(create.getParameters()[2].getAnnotation(RequestBody.class));
  48. assertEquals(Set.of("orderId", "paymentMethod"), java.util.Arrays.stream(OmgCreatePaymentRequest.class.getDeclaredFields())
  49. .map(Field::getName).collect(Collectors.toSet()));
  50. assertTrue(java.util.Arrays.stream(create.getParameterTypes()).noneMatch(Map.class::isAssignableFrom));
  51. }
  52. @Test
  53. void returnsSuccessShapeAndSafeDiagnosticLogs() {
  54. OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
  55. OmgPaymentCreateService service = mock(OmgPaymentCreateService.class);
  56. when(resolver.requireUserId("SECRET_TOKEN")).thenReturn(5L);
  57. OmgCreatePaymentResponse response = new OmgCreatePaymentResponse("stage", Map.of("CheckMacValue", "A".repeat(64)));
  58. when(service.create(5L, "DD-1\r\nforged=true", OmgPaymentMethod.CREDIT)).thenReturn(new OmgPaymentCreateOutcome(
  59. response, 9L, "DD-1", 5L, 77L, 100, "OMG12***4567"));
  60. OmgPaymentController controller = controller(resolver, service);
  61. OmgCreatePaymentRequest request = new OmgCreatePaymentRequest();
  62. request.setOrderId("DD-1\r\nforged=true");
  63. request.setPaymentMethod("CREDIT");
  64. ListAppender<ILoggingEvent> logs = captureLogs();
  65. AjaxResult result = controller.create("SECRET_TOKEN", null, request);
  66. assertSame(response, result.get("data"));
  67. verify(service).create(5L, "DD-1\r\nforged=true", OmgPaymentMethod.CREDIT);
  68. String messages = logs.list.stream().map(ILoggingEvent::getFormattedMessage).collect(Collectors.joining("\n"));
  69. assertTrue(messages.contains("attemptId=9"));
  70. assertFalse(messages.contains("SECRET_TOKEN"));
  71. assertFalse(messages.contains("\r"));
  72. assertFalse(messages.contains("forged=true"));
  73. assertFalse(messages.contains("A".repeat(64)));
  74. assertFalse(messages.contains("CheckMacValue"));
  75. }
  76. @Test
  77. void mapsBusinessAndUnexpectedErrorsToStableStatus() {
  78. OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
  79. OmgPaymentCreateService service = mock(OmgPaymentCreateService.class);
  80. when(resolver.requireUserId(anyString())).thenReturn(5L);
  81. OmgPaymentController controller = controller(resolver, service);
  82. AjaxResult business = controller.create("token", null, null);
  83. assertEquals("PAYMENT_METHOD_INVALID", ((OmgPaymentErrorResponse) business.get("data")).status());
  84. RuntimeException failure = new RuntimeException("unexpected");
  85. when(service.create(5L, "DD-2", OmgPaymentMethod.CREDIT)).thenThrow(failure);
  86. OmgCreatePaymentRequest request = new OmgCreatePaymentRequest();
  87. request.setOrderId("DD-2");
  88. request.setPaymentMethod("CREDIT");
  89. ListAppender<ILoggingEvent> logs = captureLogs();
  90. AjaxResult unexpected = controller.create("token", null, request);
  91. assertEquals("PAYMENT_CREATION_FAILED", ((OmgPaymentErrorResponse) unexpected.get("data")).status());
  92. assertTrue(logs.list.stream().anyMatch(event -> event.getThrowableProxy() != null
  93. && event.getThrowableProxy().getMessage().equals("unexpected")));
  94. }
  95. @Test
  96. void mapsEveryBusinessErrorToItsStableStatus() {
  97. for (OmgPaymentErrorCode code : OmgPaymentErrorCode.values()) {
  98. OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
  99. OmgPaymentCreateService service = mock(OmgPaymentCreateService.class);
  100. when(resolver.requireUserId("token")).thenReturn(5L);
  101. when(service.create(5L, "DD-1", OmgPaymentMethod.CREDIT)).thenThrow(new OmgPaymentBusinessException(code, 77L));
  102. OmgCreatePaymentRequest request = new OmgCreatePaymentRequest();
  103. request.setOrderId("DD-1");
  104. request.setPaymentMethod("CREDIT");
  105. AjaxResult result = controller(resolver, service).create("token", null, request);
  106. assertEquals(code.name(), ((OmgPaymentErrorResponse) result.get("data")).status());
  107. }
  108. }
  109. @Test
  110. void mapsResolverFailureWithoutLoggingToken() {
  111. OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
  112. when(resolver.requireUserId("NEVER_LOG_THIS")).thenThrow(
  113. new OmgPaymentBusinessException(OmgPaymentErrorCode.AUTH_REQUIRED));
  114. OmgPaymentController controller = controller(resolver, mock(OmgPaymentCreateService.class));
  115. ListAppender<ILoggingEvent> logs = captureLogs();
  116. AjaxResult result = controller.create("NEVER_LOG_THIS", null, null);
  117. assertEquals("AUTH_REQUIRED", ((OmgPaymentErrorResponse) result.get("data")).status());
  118. assertTrue(logs.list.stream().noneMatch(event -> event.getFormattedMessage().contains("NEVER_LOG_THIS")));
  119. }
  120. @Test
  121. void rejectsInvalidPaymentMethodsWithoutCallingCreateService() {
  122. OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
  123. OmgPaymentCreateService service = mock(OmgPaymentCreateService.class);
  124. when(resolver.requireUserId("token")).thenReturn(5L);
  125. OmgPaymentController controller = controller(resolver, service);
  126. for (String paymentMethod : new String[]{null, "ALL", "Credit", " CREDIT "}) {
  127. OmgCreatePaymentRequest request = new OmgCreatePaymentRequest();
  128. request.setOrderId("DD-1");
  129. request.setPaymentMethod(paymentMethod);
  130. AjaxResult result = controller.create("token", null, request);
  131. assertEquals("PAYMENT_METHOD_INVALID", ((OmgPaymentErrorResponse) result.get("data")).status());
  132. }
  133. verifyNoInteractions(service);
  134. }
  135. @Test
  136. void multiStoreChildNeedsNewAppCapabilityButOrdinaryOrderDoesNot() {
  137. OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
  138. OmgPaymentCreateService service = mock(OmgPaymentCreateService.class);
  139. OmgPaymentController controller = controller(resolver, service);
  140. OmgCreatePaymentRequest request = new OmgCreatePaymentRequest();
  141. request.setOrderId("MS-CHILD-001");
  142. request.setPaymentMethod("CREDIT");
  143. AjaxResult rejected = controller.create("token", null, request);
  144. assertEquals("UPGRADE_REQUIRED", rejected.get("errorCode"));
  145. verifyNoInteractions(service);
  146. }
  147. private static ListAppender<ILoggingEvent> captureLogs() {
  148. Logger logger = (Logger) LoggerFactory.getLogger(OmgPaymentController.class);
  149. ListAppender<ILoggingEvent> appender = new ListAppender<>();
  150. appender.start();
  151. logger.addAppender(appender);
  152. return appender;
  153. }
  154. private static OmgPaymentController controller(OmgPaymentTokenUserResolver resolver,
  155. OmgPaymentCreateService service) {
  156. return new OmgPaymentController(resolver, service, mock(OmgPaymentNotifyService.class),
  157. mock(OmgIpnAuditService.class), mock(OmgPaymentQueryService.class),
  158. mock(OmgPaymentRetryService.class),
  159. mock(OmgPaymentRefundService.class));
  160. }
  161. }