package com.ruoyi.app.omgpay; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Instant; import java.time.ZoneOffset; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class OmgStoreCredentialVerifierTest { private static final String HASH_KEY = "5294y06JbISpM5x9"; private static final String HASH_IV = "v77hoKGq4kWxNNIS"; private OmgPaymentQueryGateway gateway; private OmgCheckMacSigner signer; private OmgStoreCredentialVerifier verifier; @BeforeEach void setUp() { gateway = mock(OmgPaymentQueryGateway.class); signer = new OmgCheckMacSigner(); verifier = new OmgStoreCredentialVerifier(gateway, new OmgQueryResponseParser(), signer, Clock.fixed(Instant.ofEpochSecond(1_786_598_400L), ZoneOffset.UTC)); } @Test void signsAStageProbeAndVerifiesEveryReturnedField() { when(gateway.query(anyMap())).thenAnswer(invocation -> signedResponse(invocation.getArgument(0))); Map response = verifier.verify("1000031", HASH_KEY, HASH_IV); assertEquals("0", response.get("TradeStatus")); @SuppressWarnings("unchecked") var request = org.mockito.ArgumentCaptor.forClass(Map.class); verify(gateway).query(request.capture()); assertEquals(Set.of("MerchantID", "MerchantTradeNo", "TimeStamp", "CheckMacValue"), request.getValue().keySet()); assertEquals("1000031", request.getValue().get("MerchantID")); assertEquals("OMGVERIFY1786598400", request.getValue().get("MerchantTradeNo")); assertEquals("1786598400", request.getValue().get("TimeStamp")); LinkedHashMap signingFields = new LinkedHashMap<>(request.getValue()); String actual = signingFields.remove("CheckMacValue"); assertEquals(signer.sign(signingFields, HASH_KEY, HASH_IV), actual); } @Test void rejectsAnyTamperedExtraResponseField() { when(gateway.query(anyMap())).thenAnswer(invocation -> signedResponse(invocation.getArgument(0)).replace("FutureEmptyField=", "FutureEmptyField=tampered")); assertThrows(IllegalArgumentException.class, () -> verifier.verify("1000031", HASH_KEY, HASH_IV)); } @Test void springContextCreatesCredentialVerifierWithDependencies() { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { context.registerBean(OmgPaymentQueryGateway.class, () -> gateway); context.registerBean(OmgQueryResponseParser.class); context.registerBean(OmgCheckMacSigner.class); context.register(OmgStoreCredentialVerifier.class); context.refresh(); assertNotNull(context.getBean(OmgStoreCredentialVerifier.class)); } } private String signedResponse(Map request) { LinkedHashMap fields = new LinkedHashMap<>(); fields.put("MerchantID", request.get("MerchantID")); fields.put("MerchantTradeNo", request.get("MerchantTradeNo")); fields.put("TradeStatus", "0"); fields.put("RtnMsg", "Not found"); fields.put("FutureEmptyField", ""); fields.put("CheckMacValue", signer.sign(fields, HASH_KEY, HASH_IV)); return fields.entrySet().stream() .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) .collect(java.util.stream.Collectors.joining("&")); } private static String encode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); } }