|
@@ -1,5 +1,6 @@
|
|
|
package com.payment.platform.module.payment.leshouying.service;
|
|
package com.payment.platform.module.payment.leshouying.service;
|
|
|
|
|
|
|
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
|
|
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
@@ -13,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.Test;
|
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.mockito.ArgumentCaptor;
|
|
|
|
|
+import org.mockito.MockedStatic;
|
|
|
import org.mockito.Mock;
|
|
import org.mockito.Mock;
|
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
import org.springframework.mock.web.MockHttpServletRequest;
|
|
import org.springframework.mock.web.MockHttpServletRequest;
|
|
@@ -89,11 +91,36 @@ class LsyCheckoutLinkServiceTest {
|
|
|
assertEquals("测试商户", inserted.getMerchantName());
|
|
assertEquals("测试商户", inserted.getMerchantName());
|
|
|
assertEquals("ENABLED", inserted.getStatus(), "新链接必须是可用状态(status 从 DB 默认值改成显式写,见服务注释)");
|
|
assertEquals("ENABLED", inserted.getStatus(), "新链接必须是可用状态(status 从 DB 默认值改成显式写,见服务注释)");
|
|
|
assertNotNull(inserted.getToken());
|
|
assertNotNull(inserted.getToken());
|
|
|
- assertEquals(32, inserted.getToken().length(), "fastSimpleUUID 形态:32 位十六进制");
|
|
|
|
|
|
|
+ assertEquals(32, inserted.getToken().length(), "simpleUUID 形态:32 位十六进制");
|
|
|
assertTrue(inserted.getToken().matches("[0-9a-f]{32}"), "实际: " + inserted.getToken());
|
|
assertTrue(inserted.getToken().matches("[0-9a-f]{32}"), "实际: " + inserted.getToken());
|
|
|
assertSame(inserted, created, "返回值必须是落库的那一行(调用方要拿它的 token 拼 URL)");
|
|
assertSame(inserted, created, "返回值必须是落库的那一行(调用方要拿它的 token 拼 URL)");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * token 的随机源必须是 CSPRNG(2026-10-06,与 HAR 收款链接同一处订正)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>这是**唯一**能钉住这件事的形态:{@code fastSimpleUUID()}(ThreadLocalRandom,48 位 LCG)
|
|
|
|
|
+ * 与 {@code simpleUUID()}(SecureRandom)产出的字符串**一模一样**(都是 32 位 hex)——
|
|
|
|
|
+ * 上面那个形状断言对「换错生成器」永远是绿的,而免登录路径的安全性全押在
|
|
|
|
|
+ * 「这串字符猜不到」上(类 javadoc「(甲)·预防」第 2 条)。所以这里不测形状,
|
|
|
|
|
+ * 直接钉住**调的是哪一个方法**(形态同 HarCheckoutLinkServiceTest)。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void issue_tokenComesFromSimpleUuid_notFastSimpleUuid() {
|
|
|
|
|
+ when(linkMapper.selectList(any())).thenReturn(List.of());
|
|
|
|
|
+
|
|
|
|
|
+ try (MockedStatic<IdUtil> idUtil = mockStatic(IdUtil.class)) {
|
|
|
|
|
+ idUtil.when(IdUtil::simpleUUID).thenReturn("0123456789abcdef0123456789abcdef");
|
|
|
|
|
+
|
|
|
|
|
+ LsyCheckoutLinkEntity created = service.issue(TENANT, MCH_NO, "测试商户");
|
|
|
|
|
+
|
|
|
|
|
+ idUtil.verify(IdUtil::simpleUUID);
|
|
|
|
|
+ idUtil.verify(IdUtil::fastSimpleUUID, never());
|
|
|
|
|
+ assertEquals("0123456789abcdef0123456789abcdef", created.getToken(),
|
|
|
|
|
+ "生成器的返回值必须原样落到那一行上");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Test
|
|
@Test
|
|
|
void issue_whenRowExists_returnsSameToken_andDoesNotInsert() {
|
|
void issue_whenRowExists_returnsSameToken_andDoesNotInsert() {
|
|
|
LsyCheckoutLinkEntity existing = row(1L, "abcd1234abcd1234abcd1234abcd1234", "ENABLED");
|
|
LsyCheckoutLinkEntity existing = row(1L, "abcd1234abcd1234abcd1234abcd1234", "ENABLED");
|