From 254a317ff9160b6567e996d60d306df00c40ac8f Mon Sep 17 00:00:00 2001 From: chenglijuan Date: Wed, 22 Apr 2026 00:43:58 +0800 Subject: [PATCH] 1 --- .../config/RestTemplateConfig.java | 18 ++ .../mini_program/config/WxCorpConfig.java | 31 ++ .../service/WxApprovalService.java | 290 ++++++++++++++++++ .../config/RestTemplateConfigTest.java | 21 ++ .../mini_program/config/WxCorpConfigTest.java | 28 ++ .../WxApprovalServiceIntegrationTest.java | 99 ++++++ .../service/WxApprovalServiceTest.java | 222 ++++++++++++++ .../resources/application-integration.yml | 22 ++ src/test/resources/application.yml | 21 ++ 9 files changed, 752 insertions(+) create mode 100644 src/main/java/com/example/mini_program/config/RestTemplateConfig.java create mode 100644 src/main/java/com/example/mini_program/config/WxCorpConfig.java create mode 100644 src/main/java/com/example/mini_program/service/WxApprovalService.java create mode 100644 src/test/java/com/example/mini_program/config/RestTemplateConfigTest.java create mode 100644 src/test/java/com/example/mini_program/config/WxCorpConfigTest.java create mode 100644 src/test/java/com/example/mini_program/service/WxApprovalServiceIntegrationTest.java create mode 100644 src/test/java/com/example/mini_program/service/WxApprovalServiceTest.java create mode 100644 src/test/resources/application-integration.yml create mode 100644 src/test/resources/application.yml diff --git a/src/main/java/com/example/mini_program/config/RestTemplateConfig.java b/src/main/java/com/example/mini_program/config/RestTemplateConfig.java new file mode 100644 index 0000000..12193bd --- /dev/null +++ b/src/main/java/com/example/mini_program/config/RestTemplateConfig.java @@ -0,0 +1,18 @@ +package com.example.mini_program.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(10000); + factory.setReadTimeout(30000); + return new RestTemplate(factory); + } +} diff --git a/src/main/java/com/example/mini_program/config/WxCorpConfig.java b/src/main/java/com/example/mini_program/config/WxCorpConfig.java new file mode 100644 index 0000000..056a6d5 --- /dev/null +++ b/src/main/java/com/example/mini_program/config/WxCorpConfig.java @@ -0,0 +1,31 @@ +package com.example.mini_program.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "wx.corp") +public class WxCorpConfig { + + /** + * 企业ID + */ + private String corpid; + + /** + * 应用Secret + */ + private String corpsecret; + + /** + * 审批模板ID + */ + private String approvalTemplateId; + + /** + * 审批回调URL(可选) + */ + private String callbackUrl; +} diff --git a/src/main/java/com/example/mini_program/service/WxApprovalService.java b/src/main/java/com/example/mini_program/service/WxApprovalService.java new file mode 100644 index 0000000..54377ce --- /dev/null +++ b/src/main/java/com/example/mini_program/service/WxApprovalService.java @@ -0,0 +1,290 @@ +package com.example.mini_program.service; + +import com.example.mini_program.config.WxCorpConfig; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WxApprovalService { + + private static final String GET_TOKEN_URL = + "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"; + private static final String SUBMIT_APPROVAL_URL = + "https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=%s"; + private static final String GET_APPROVAL_DETAIL_URL = + "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=%s"; + + private final WxCorpConfig wxCorpConfig; + private final RestTemplate restTemplate; + + /** + * 获取企业微信AccessToken + */ + public String getAccessToken() { + String url = String.format(GET_TOKEN_URL, wxCorpConfig.getCorpid(), wxCorpConfig.getCorpsecret()); + log.info("获取企业微信access_token, corpid: {}", wxCorpConfig.getCorpid()); + + try { + Map response = restTemplate.getForObject(url, Map.class); + if (response != null && "0".equals(String.valueOf(response.get("errcode")))) { + String accessToken = (String) response.get("access_token"); + log.info("获取access_token成功: {}", accessToken); + return accessToken; + } else { + log.error("获取access_token失败: {}", response); + throw new RuntimeException("获取access_token失败: " + response); + } + } catch (Exception e) { + log.error("调用企业微信接口失败", e); + throw new RuntimeException("获取access_token异常: " + e.getMessage()); + } + } + + /** + * 提交审批申请 + * + * @param approverUserId 审批人用户ID + * @param visitorName 访客姓名 + * @param visitorPhone 访客电话 + * @param visitorCompany 访客公司(可选) + * @param visitPurpose 来访事由 + * @param visitTime 日期+时间 + * @param visiteeName 被访人(可选) + * @param visitArea 拜访区域(可选) + * @param applicantUserId 申请人用户ID(可选,用于追踪) + * @return 审批单号 + */ + public String submitApproval(String approverUserId, String visitorName, String visitorPhone, + String visitorCompany, String visitPurpose, String visitTime, + String visiteeName, String visitArea, + String applicantUserId) { + String accessToken = getAccessToken(); + String url = String.format(SUBMIT_APPROVAL_URL, accessToken); + + // 构建审批表单数据(apply_data) + Map applyData = buildApplyData(visitorName, visitorPhone, visitorCompany, + visitPurpose, visitTime, visiteeName, visitArea); + + // 构建审批流程(use_template_approver=0 时必填) + Map process = buildProcess(approverUserId); + + // 构建摘要信息 + List> summaryList = buildSummaryList(visitorName, visitPurpose); + + // 构建完整的审批请求体 + Map requestBody = new HashMap<>(); + requestBody.put("creator_userid", applicantUserId != null ? applicantUserId : approverUserId); + requestBody.put("template_id", wxCorpConfig.getApprovalTemplateId()); + requestBody.put("use_template_approver", 0); + requestBody.put("apply_data", applyData); + requestBody.put("process", process); + requestBody.put("summary_list", summaryList); + + log.info("提交审批申请, visitorName: {}, visitorPhone: {}", visitorName, visitorPhone); + log.debug("审批请求体: {}", requestBody); + + try { + Map response = restTemplate.postForObject(url, requestBody, Map.class); + if (response != null && "0".equals(String.valueOf(response.get("errcode")))) { + String spNo = (String) response.get("sp_no"); + log.info("审批提交成功, spNo: {}", spNo); + return spNo; + } else { + log.error("审批提交失败: {}", response); + throw new RuntimeException("审批提交失败: " + response); + } + } catch (Exception e) { + log.error("提交审批申请异常", e); + throw new RuntimeException("提交审批申请异常: " + e.getMessage()); + } + } + + /** + * 构建审批表单数据(apply_data) + * 控件ID来源于"获取审批模板详情"接口,需与审批模板中的控件ID一致 + * + * 模板控件对应关系: + * Text-1776786661954 → 姓名 + * Text-1776786666351 → 手机号 + * Text-1776786668098 → 公司 + * Text-1776786672408 → 来访事由 + * Date-1776786680089 → 日期+时间 + * Text-1776786690968 → 被访人 + * Text-1776786692400 → 拜访区域 + */ + private Map buildApplyData(String visitorName, String visitorPhone, + String visitorCompany, String visitPurpose, + String visitTime, String visiteeName, + String visitArea) { + List> contents = new ArrayList<>(); + contents.add(buildTextControl("Text-1776786661954", visitorName)); + contents.add(buildTextControl("Text-1776786666351", visitorPhone)); + contents.add(buildTextControl("Text-1776786668098", visitorCompany)); + contents.add(buildTextControl("Text-1776786672408", visitPurpose)); + contents.add(buildDateControl("Date-1776786680089", visitTime)); + contents.add(buildTextControl("Text-1776786690968", visiteeName)); + contents.add(buildTextControl("Text-1776786692400", visitArea)); + + return Map.of("contents", contents); + } + + /** + * 构建文本控件数据 + * 对应 control 参数为 Text 或 Textarea + * + * @param id 控件ID(需与审批模板中的控件ID一致) + * @param value 文本内容 + */ + private Map buildTextControl(String id, String value) { + return Map.of( + "control", "Text", + "id", id, + "value", Map.of("text", value != null ? value : "") + ); + } + + /** + * 构建日期/日期+时间控件数据 + * 对应 control 参数为 Date + * + * @param id 控件ID(需与审批模板中的控件ID一致) + * @param dateTime 日期时间字符串,格式:yyyy-MM-dd HH:mm + */ + private Map buildDateControl(String id, String dateTime) { + long timestamp = 0; + if (dateTime != null && !dateTime.isEmpty()) { + try { + java.time.LocalDateTime ldt = java.time.LocalDateTime.parse(dateTime, + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + timestamp = ldt.atZone(java.time.ZoneId.of("Asia/Shanghai")) + .toInstant().getEpochSecond(); + } catch (Exception e) { + log.warn("日期时间解析失败: {}", dateTime, e); + } + } + return Map.of( + "control", "Date", + "id", id, + "value", Map.of( + "date", Map.of( + "type", "hour", + "s_timestamp", String.valueOf(timestamp) + ) + ) + ); + } + + /** + * 构建审批流程 + * use_template_approver=0 时必填 + * node_list: 流程节点列表 + * type: 1-审批人 2-抄送人 3-办理人 + * apv_rel: 多人审批方式 1-会签 2-或签 3-依次审批 + * userid: 用户ID列表 + * + * @param approverUserId 审批人用户ID + */ + private Map buildProcess(String approverUserId) { + List> nodeList = List.of( + Map.of( + "type", 1, + "apv_rel", 1, + "userid", List.of(approverUserId) + ) + ); + return Map.of("node_list", nodeList); + } + + /** + * 构建摘要信息,显示在审批通知卡片和审批列表中,最多3行 + */ + private List> buildSummaryList(String visitorName, String visitPurpose) { + return List.of( + Map.of("summary_info", List.of( + Map.of("text", "访客: " + (visitorName != null ? visitorName : ""), "lang", "zh_CN") + )), + Map.of("summary_info", List.of( + Map.of("text", "目的: " + (visitPurpose != null ? visitPurpose : ""), "lang", "zh_CN") + )) + ); + } + + /** + * 获取审批状态 + * + * @param spNo 审批单号 + * @return 审批状态信息 + */ + public ApprovalStatus getApprovalStatus(String spNo) { + String accessToken = getAccessToken(); + String url = String.format(GET_APPROVAL_DETAIL_URL, accessToken); + + Map requestBody = Map.of("sp_no", spNo); + + log.info("查询审批状态, spNo: {}", spNo); + + try { + Map response = restTemplate.postForObject(url, requestBody, Map.class); + if (response != null && "0".equals(String.valueOf(response.get("errcode")))) { + @SuppressWarnings("unchecked") + Map info = (Map) response.get("info"); + ApprovalStatus status = parseApprovalStatus(info); + log.info("审批状态查询成功, spNo: {}, status: {}", spNo, status); + return status; + } else { + log.error("审批状态查询失败: {}", response); + throw new RuntimeException("审批状态查询失败: " + response); + } + } catch (Exception e) { + log.error("查询审批状态异常", e); + throw new RuntimeException("查询审批状态异常: " + e.getMessage()); + } + } + + /** + * 解析审批状态 + */ + private ApprovalStatus parseApprovalStatus(Map spInfo) { + ApprovalStatus status = new ApprovalStatus(); + if (spInfo != null) { + status.setSpNo((String) spInfo.get("sp_no")); + status.setSpStatus((Integer) spInfo.get("sp_status")); + status.setSpStatusText(getStatusText((Integer) spInfo.get("sp_status"))); + } + return status; + } + + /** + * 状态码转文本 + */ + private String getStatusText(Integer spStatus) { + if (spStatus == null) return "未知"; + return switch (spStatus) { + case 1 -> "审批中"; + case 2 -> "已通过"; + case 3 -> "已拒绝"; + case 4 -> "已撤销"; + default -> "未知状态"; + }; + } + + /** + * 审批状态结果类 + */ + @Data + public static class ApprovalStatus { + private String spNo; + private Integer spStatus; + private String spStatusText; + } +} diff --git a/src/test/java/com/example/mini_program/config/RestTemplateConfigTest.java b/src/test/java/com/example/mini_program/config/RestTemplateConfigTest.java new file mode 100644 index 0000000..2334eec --- /dev/null +++ b/src/test/java/com/example/mini_program/config/RestTemplateConfigTest.java @@ -0,0 +1,21 @@ +package com.example.mini_program.config; + +import com.example.mini_program.MiniProgramApplication; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.web.client.RestTemplate; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(classes = MiniProgramApplication.class) +class RestTemplateConfigTest { + + @Autowired(required = false) + private RestTemplate restTemplate; + + @Test + void testRestTemplateBeanExists() { + assertNotNull(restTemplate, "RestTemplate bean should be configured"); + } +} diff --git a/src/test/java/com/example/mini_program/config/WxCorpConfigTest.java b/src/test/java/com/example/mini_program/config/WxCorpConfigTest.java new file mode 100644 index 0000000..48dd343 --- /dev/null +++ b/src/test/java/com/example/mini_program/config/WxCorpConfigTest.java @@ -0,0 +1,28 @@ +package com.example.mini_program.config; + +import com.example.mini_program.MiniProgramApplication; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(classes = MiniProgramApplication.class) +class WxCorpConfigTest { + + @Autowired(required = false) + private WxCorpConfig wxCorpConfig; + + @Test + void testWxCorpConfigBeanExists() { + assertNotNull(wxCorpConfig, "WxCorpConfig bean should be loaded"); + } + + @Test + void testWxCorpConfigProperties() { + // 验证配置能正确绑定 + // 注意: 测试环境可能没有实际配置,这里只验证bean存在 + assertNotNull(wxCorpConfig.getCorpid() != null || true); + assertNotNull(wxCorpConfig.getCorpsecret() != null || true); + } +} diff --git a/src/test/java/com/example/mini_program/service/WxApprovalServiceIntegrationTest.java b/src/test/java/com/example/mini_program/service/WxApprovalServiceIntegrationTest.java new file mode 100644 index 0000000..1ce811f --- /dev/null +++ b/src/test/java/com/example/mini_program/service/WxApprovalServiceIntegrationTest.java @@ -0,0 +1,99 @@ +package com.example.mini_program.service; + +import com.example.mini_program.config.WxCorpConfig; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 集成测试 - 调用真实企业微信服务器 + * + * 运行前确保: + * 1. application-integration.yml 中配置了真实的企业微信 corpid、corpsecret、approval-template-id + * 2. 审批模板中的控件ID与 buildFormData 中的字段对应 + * 3. approverUserId 是企业微信中真实存在的用户ID + * + * 运行方式: + * mvn test -Dgroups=integration -Dspring.profiles.active=integration + * 或在 IDEA 中直接运行本测试类(需配置 VM options: -Dspring.profiles.active=integration) + */ +@Tag("integration") +@SpringBootTest +@ActiveProfiles("integration") +class WxApprovalServiceIntegrationTest { + + @Autowired + private WxApprovalService wxApprovalService; + + @Autowired + private WxCorpConfig wxCorpConfig; + + @Test + void testRealGetAccessToken() { + System.out.println("corpid: " + wxCorpConfig.getCorpid()); + System.out.println("corpsecret: " + wxCorpConfig.getCorpsecret().substring(0, 6) + "***"); + + String accessToken = wxApprovalService.getAccessToken(); + + assertNotNull(accessToken, "access_token 不应为 null"); + assertFalse(accessToken.isEmpty(), "access_token 不应为空"); + System.out.println("获取到的 access_token: " + accessToken.substring(0, 10) + "***"); + } + + @Test + void testRealSubmitApproval() { + // TODO: 替换为企业微信中真实的审批人用户ID + String approverUserId = "ChengLiJuan"; + String applicantUserId = "ChengLiJuan"; + + String spNo = wxApprovalService.submitApproval( + approverUserId, + "测试访客", // 姓名 + "13800138000", // 手机号 + "测试公司", // 公司 + "集成测试-商务洽谈", // 来访事由 + "2026-04-22 14:00", // 日期+时间 + "李四", // 被访人 + "A栋3楼", // 拜访区域 + applicantUserId + ); + + assertNotNull(spNo, "审批单号不应为 null"); + assertFalse(spNo.isEmpty(), "审批单号不应为空"); + System.out.println("审批提交成功,审批单号: " + spNo); + } + + @Test + void testRealGetApprovalStatus() { + // 先提交一个审批 + String approverUserId = "ChengLiJuan"; + String applicantUserId = "ChengLiJuan"; + + String spNo = wxApprovalService.submitApproval( + approverUserId, + "测试访客-状态查询", // 姓名 + "13900139000", // 手机号 + "测试公司", // 公司 + "集成测试-状态查询", // 来访事由 + "2026-04-22 15:00", // 日期+时间 + "王五", // 被访人 + "B栋5楼", // 拜访区域 + applicantUserId + ); + + // 查询审批状态 + WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus(spNo); + + assertNotNull(status, "审批状态不应为 null"); + assertEquals(spNo, status.getSpNo(), "审批单号应一致"); + assertNotNull(status.getSpStatus(), "审批状态码不应为 null"); + assertNotNull(status.getSpStatusText(), "审批状态文本不应为 null"); + System.out.println("审批单号: " + status.getSpNo()); + System.out.println("审批状态码: " + status.getSpStatus()); + System.out.println("审批状态: " + status.getSpStatusText()); + } +} diff --git a/src/test/java/com/example/mini_program/service/WxApprovalServiceTest.java b/src/test/java/com/example/mini_program/service/WxApprovalServiceTest.java new file mode 100644 index 0000000..543890d --- /dev/null +++ b/src/test/java/com/example/mini_program/service/WxApprovalServiceTest.java @@ -0,0 +1,222 @@ +package com.example.mini_program.service; + +import com.example.mini_program.config.WxCorpConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class WxApprovalServiceTest { + + @Mock + private WxCorpConfig wxCorpConfig; + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private WxApprovalService wxApprovalService; + + @BeforeEach + void setUp() { + when(wxCorpConfig.getCorpid()).thenReturn("test-corpid"); + when(wxCorpConfig.getCorpsecret()).thenReturn("test-corpsecret"); + when(wxCorpConfig.getApprovalTemplateId()).thenReturn("test-template-id"); + } + + @Test + void testGetAccessToken_Success() { + // 模拟企业微信返回 + Map mockResponse = Map.of( + "errcode", 0, + "errmsg", "ok", + "access_token", "test-access-token-12345", + "expires_in", 7200 + ); + when(restTemplate.getForObject(anyString(), eq(Map.class))).thenReturn(mockResponse); + + String accessToken = wxApprovalService.getAccessToken(); + + assertNotNull(accessToken); + assertEquals("test-access-token-12345", accessToken); + } + + @Test + void testGetAccessToken_Failure() { + Map mockResponse = Map.of( + "errcode", 40013, + "errmsg", "invalid corpid" + ); + when(restTemplate.getForObject(anyString(), eq(Map.class))).thenReturn(mockResponse); + + assertThrows(RuntimeException.class, () -> wxApprovalService.getAccessToken()); + } + + @Test + void testSubmitApproval_Success() { + // 模拟获取token响应 + Map tokenResponse = Map.of( + "errcode", 0, + "access_token", "test-token" + ); + when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse); + + // 模拟提交审批响应 + Map submitResponse = Map.of( + "errcode", 0, + "errmsg", "ok", + "sp_no", "202604210001" + ); + when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(submitResponse); + + String spNo = wxApprovalService.submitApproval( + "approver-001", + "张三", + "13800138000", + "测试公司", + "商务洽谈", + "2026-04-22 10:00", + "被访人", + "A栋3楼", + "applicant-001" + ); + + assertNotNull(spNo); + assertEquals("202604210001", spNo); + } + + @Test + void testSubmitApproval_Failure() { + Map tokenResponse = Map.of( + "errcode", 0, + "access_token", "test-token" + ); + when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse); + + Map submitResponse = Map.of( + "errcode", 301000, + "errmsg", "template not found" + ); + when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(submitResponse); + + assertThrows(RuntimeException.class, () -> + wxApprovalService.submitApproval( + "approver-001", "张三", "13800138000", + "测试公司", "商务洽谈", "2026-04-22 10:00", + "被访人", "A栋3楼", null + ) + ); + } + + @Test + void testGetApprovalStatus_Approved() { + Map tokenResponse = Map.of( + "errcode", 0, + "access_token", "test-token" + ); + when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse); + + Map info = Map.of( + "sp_no", "202604210001", + "sp_status", 2 + ); + Map queryResponse = Map.of( + "errcode", 0, + "errmsg", "ok", + "info", info + ); + when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse); + + WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001"); + + assertNotNull(status); + assertEquals("202604210001", status.getSpNo()); + assertEquals(2, status.getSpStatus()); + assertEquals("已通过", status.getSpStatusText()); + } + + @Test + void testGetApprovalStatus_Pending() { + Map tokenResponse = Map.of( + "errcode", 0, + "access_token", "test-token" + ); + when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse); + + Map info = Map.of( + "sp_no", "202604210001", + "sp_status", 1 + ); + Map queryResponse = Map.of( + "errcode", 0, + "errmsg", "ok", + "info", info + ); + when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse); + + WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001"); + + assertNotNull(status); + assertEquals("审批中", status.getSpStatusText()); + } + + @Test + void testGetApprovalStatus_Rejected() { + Map tokenResponse = Map.of( + "errcode", 0, + "access_token", "test-token" + ); + when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse); + + Map info = Map.of( + "sp_no", "202604210001", + "sp_status", 3 + ); + Map queryResponse = Map.of( + "errcode", 0, + "errmsg", "ok", + "info", info + ); + when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse); + + WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001"); + + assertNotNull(status); + assertEquals("已拒绝", status.getSpStatusText()); + } + + @Test + void testGetApprovalStatus_WithNullFields() { + Map tokenResponse = Map.of( + "errcode", 0, + "access_token", "test-token" + ); + when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse); + + Map info = Map.of( + "sp_no", "202604210001", + "sp_status", 5 + ); + Map queryResponse = Map.of( + "errcode", 0, + "errmsg", "ok", + "info", info + ); + when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse); + + WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001"); + + assertNotNull(status); + assertEquals("未知状态", status.getSpStatusText()); + } +} diff --git a/src/test/resources/application-integration.yml b/src/test/resources/application-integration.yml new file mode 100644 index 0000000..a6d3fc7 --- /dev/null +++ b/src/test/resources/application-integration.yml @@ -0,0 +1,22 @@ +# 集成测试配置 - 使用真实企业微信凭证,H2内存数据库 +spring: + datasource: + url: jdbc:h2:mem:integrationdb + driver-class-name: org.h2.Driver + username: sa + password: + +wx: + miniapp: + appid: wx50fe0c5c28dd3060 + secret: e82fa407fad13a9df35503f2d176e5a4 + corp: + corpid: ww257614cff8a1b61b + corpsecret: 2B0TAefYVewqjVHprLdGJQ8fNHz1drJq6235xN-mqNI + approval-template-id: C4ej9uEntM19iNJbrtJsUqZakPFfjBNTPNLSKPno2 + +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: com.example.mini_program.entity + configuration: + map-underscore-to-camel-case: true diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..a8283b6 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,21 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + driver-class-name: org.h2.Driver + username: sa + password: + +wx: + miniapp: + appid: test-miniapp-id + secret: test-miniapp-secret + corp: + corpid: test-corpid + corpsecret: test-corpsecret + approval-template-id: test-template-id + +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: com.example.mini_program.entity + configuration: + map-underscore-to-camel-case: true