package com.ruoyi.lsfx.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.lsfx.client.CreditParseClient; import com.ruoyi.lsfx.domain.CallerContext; import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse; import com.ruoyi.lsfx.exception.LsfxApiException; import com.ruoyi.lsfx.util.HttpUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import java.util.ArrayList; import java.util.Collections; import java.util.List; 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.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CreditParseControllerTest { private static final CallerContext CALLER = CallerContext.of(7L, "tester"); @Mock private CreditParseClient client; @InjectMocks private CreditParseController controller; @AfterEach void clearSecurityContext() { SecurityContextHolder.clearContext(); } @Test void parse_shouldRejectBlankRemotePath() { AjaxResult result = controller.parse(null, null); assertEquals(500, result.get("code")); } @Test void shouldUseDefaultModelWhenMissing() { setLoginUser(CALLER.userId(), CALLER.username()); CreditParseInvokeResponse response = new CreditParseInvokeResponse(); response.setSuccess(true); response.setCode(10000); String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html"; when(client.parse(eq(CALLER), eq("LXCUSTALL"), eq(remotePath))).thenReturn(response); AjaxResult result = controller.parse(remotePath, null); assertEquals(200, result.get("code")); assertSame(response, result.get("data")); } @Test void shouldReturnAjaxErrorWhenClientThrows() { setLoginUser(CALLER.userId(), CALLER.username()); when(client.parse(any(CallerContext.class), anyString(), anyString())) .thenThrow(new LsfxApiException("超时")); AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", null); assertEquals(500, result.get("code")); } private void setLoginUser(Long userId, String username) { SysUser user = new SysUser(); user.setUserName(username); LoginUser loginUser = new LoginUser(user, Set.of("*:*:*")); loginUser.setUserId(userId); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(authentication); } @Test @SuppressWarnings({"unchecked", "rawtypes"}) void creditParseClient_shouldInitiateAndQueryResultWithSameSerialNum() throws Exception { HttpUtil httpUtil = mock(HttpUtil.class); CreditParseClient parseClient = new CreditParseClient(); ObjectMapper objectMapper = new ObjectMapper(); ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil); ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature"); ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult"); ReflectionTestUtils.setField(parseClient, "orgCode", "999000"); ReflectionTestUtils.setField(parseClient, "runType", "1"); ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL"); ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper); String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"},\"lx_debt\":{\"uncle_bank_house_bal\":\"1\"},\"lx_publictype\":{\"civil_cnt\":1}}"; when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeature"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn(initiateSuccessResponse()); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn(resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE")); String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html"; CreditParseInvokeResponse actual = parseClient.parse(CALLER, remotePath); assertEquals(true, actual.getSuccess()); assertEquals(10000, actual.getCode()); assertEquals("330101199001010011", actual.getData().getMappingOutputFields() .getPayload().getLxHeader().get("query_cert_no")); ArgumentCaptor> initiateParamsCaptor = ArgumentCaptor.forClass((Class) Map.class); verify(httpUtil).postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeature"), initiateParamsCaptor.capture(), isNull() ); ArgumentCaptor> resultParamsCaptor = ArgumentCaptor.forClass((Class) Map.class); verify(httpUtil).postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), resultParamsCaptor.capture(), isNull() ); Map initiateParams = initiateParamsCaptor.getValue(); Map resultParams = resultParamsCaptor.getValue(); assertNotNull(initiateParams.get("serialNum")); assertTrue(initiateParams.get("serialNum").toString().startsWith("CCDI_CREDIT_")); assertEquals(initiateParams.get("serialNum"), resultParams.get("serialNum")); assertEquals("999000", initiateParams.get("orgCode")); assertEquals("999000", resultParams.get("orgCode")); assertEquals("1", initiateParams.get("runType")); assertEquals("1", resultParams.get("runType")); assertEquals(remotePath, initiateParams.get("remotePath")); assertEquals("LXCUSTALL", initiateParams.get("model")); assertEquals(false, resultParams.containsKey("remotePath")); assertEquals(false, resultParams.containsKey("model")); } @Test @SuppressWarnings({"unchecked", "rawtypes"}) void creditParseClient_shouldRetryEmptyPayloadFiveTimesWithTwoSecondInterval() throws Exception { HttpUtil httpUtil = mock(HttpUtil.class); TestableCreditParseClient parseClient = new TestableCreditParseClient(); ObjectMapper objectMapper = new ObjectMapper(); ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil); ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature"); ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult"); ReflectionTestUtils.setField(parseClient, "orgCode", "999000"); ReflectionTestUtils.setField(parseClient, "runType", "1"); ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL"); ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper); String emptyPayloadResponse = "{\"success\":true,\"code\":10000,\"data\":{\"status\":1,\"reasonCode\":200,\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"ERR_SHOULD_IGNORE\"}}}"; String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"}}"; String resultResponse = resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE"); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeature"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn(initiateSuccessResponse()); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn(emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, resultResponse); CreditParseInvokeResponse actual = parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html"); assertEquals("330101199001010011", actual.getData().getMappingOutputFields() .getPayload().getLxHeader().get("query_cert_no")); verify(httpUtil, times(5)).postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), org.mockito.ArgumentMatchers.>any(), isNull() ); assertEquals(List.of(2000L, 2000L, 2000L, 2000L), parseClient.getSleepIntervals()); } @Test void creditParseClient_shouldRejectInvalidOuterCode() throws Exception { HttpUtil httpUtil = mock(HttpUtil.class); TestableCreditParseClient parseClient = new TestableCreditParseClient(); ObjectMapper objectMapper = new ObjectMapper(); ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil); ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature"); ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult"); ReflectionTestUtils.setField(parseClient, "orgCode", "999000"); ReflectionTestUtils.setField(parseClient, "runType", "1"); ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL"); ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeature"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn(initiateSuccessResponse()); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn("{\"success\":true,\"code\":99999,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\"}}}"); LsfxApiException exception = assertThrows(LsfxApiException.class, () -> parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html")); assertTrue(exception.getMessage().contains("平台状态码异常")); } @Test void creditParseClient_shouldRejectFailedResultStatus() throws Exception { HttpUtil httpUtil = mock(HttpUtil.class); TestableCreditParseClient parseClient = new TestableCreditParseClient(); ObjectMapper objectMapper = new ObjectMapper(); ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil); ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature"); ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult"); ReflectionTestUtils.setField(parseClient, "orgCode", "999000"); ReflectionTestUtils.setField(parseClient, "runType", "1"); ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL"); ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeature"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn(initiateSuccessResponse()); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"reasonMessage\":\"解析失败\",\"reasonCode\":500,\"status\":0,\"mappingOutputFields\":{\"message\":\"结果异常\"}}}"); LsfxApiException exception = assertThrows(LsfxApiException.class, () -> parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html")); assertTrue(exception.getMessage().contains("解析失败")); } @Test void creditParseClient_shouldRejectFailedInitiateStatus() throws Exception { HttpUtil httpUtil = mock(HttpUtil.class); TestableCreditParseClient parseClient = new TestableCreditParseClient(); ObjectMapper objectMapper = new ObjectMapper(); ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil); ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature"); ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult"); ReflectionTestUtils.setField(parseClient, "orgCode", "999000"); ReflectionTestUtils.setField(parseClient, "runType", "1"); ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL"); ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper); when(httpUtil.postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeature"), org.mockito.ArgumentMatchers.>any(), isNull() )).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"文件写入失败\"},\"reasonMessage\":\"文件写入失败\",\"reasonCode\":500,\"status\":0}}"); LsfxApiException exception = assertThrows(LsfxApiException.class, () -> parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html")); assertTrue(exception.getMessage().contains("文件写入失败")); verify(httpUtil, times(0)).postUrlEncodedFormForString( eq(CALLER), eq("http://tz/api/service/interface/invokeService/xfeatureResult"), org.mockito.ArgumentMatchers.>any(), isNull() ); } private static String initiateSuccessResponse() { return "{\"success\":true,\"code\":10000,\"data\":{\"traceId\":\"TRACE-001\"," + "\"mappingOutputFields\":{\"message\":\"文件写入成功,流水号为: CCDI_CREDIT_TEST\"}," + "\"reasonMessage\":\"Running successfully\",\"procCode\":\"999000\"," + "\"reasonCode\":200,\"status\":1}}"; } private static String resultSuccessResponse(ObjectMapper objectMapper, String payload, String statusCode) throws Exception { return "{\"success\":true,\"code\":10000,\"data\":{\"status\":1,\"reasonCode\":200," + "\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"" + statusCode + "\",\"payload\":" + objectMapper.writeValueAsString(payload) + "}}}"; } private static class TestableCreditParseClient extends CreditParseClient { private final List sleepIntervals = new ArrayList<>(); @Override protected void sleepBeforeNextResultQuery(long intervalMillis) { sleepIntervals.add(intervalMillis); } private List getSleepIntervals() { return sleepIntervals; } } }