package com.xuekairui.gateway; import org.junit.jupiter.api.*; import org.springframework.http.MediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * 入驻信息审核管理接口测试 * 覆盖:审核列表查询、审核通过/拒绝、异常场景 */ @DisplayName("入驻信息审核管理接口测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class AdminLicenseTest extends GatewayBaseTest { private String adminToken() { return generateAccessToken(100L, "13900000000", "ADMIN"); } // ==================== 前置:上传入驻信息 ==================== @Test @Order(1) @DisplayName("前置:上传入驻信息用于审核测试") void setup_uploadLicense() throws Exception { String body = """ { "storeName": "审核测试大药房", "terminalType": "SINGLE" "province": "广东省", "city": "深圳市", "district": "南山区", "storeAddress": "科技园路1号", "contactPerson": "李四", "contactPhone": "13900139001", "businessLicenseUrl": "https://oss.example.com/license/review-test.jpg", "drugLicenseUrl": "https://oss.example.com/drug/review-test.jpg", "creditCode": "91110000MA12345678" } """; mockMvc.perform(post("/api/business-license/upload") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } // ==================== 审核列表查询 ==================== @Test @Order(2) @DisplayName("GET /api/admin/license/review - 查询待审核列表(默认PENDING)") void listPendingReviews_defaultStatus_shouldReturnList() throws Exception { mockMvc.perform(get("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.records").isArray()) .andExpect(jsonPath("$.data.total").isNumber()); } @Test @Order(3) @DisplayName("GET /api/admin/license/review?reviewStatus=PENDING - 显式查询待审核") void listPendingReviews_explicitStatus_shouldReturnList() throws Exception { mockMvc.perform(get("/api/admin/license/review?reviewStatus=PENDING") .header("Authorization", "Bearer " + adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.records").isArray()) .andExpect(jsonPath("$.data.total").isNumber()); } @Test @Order(4) @DisplayName("GET /api/admin/license/review?reviewStatus=APPROVED - 查询已通过") void listApprovedReviews_shouldReturnList() throws Exception { mockMvc.perform(get("/api/admin/license/review?reviewStatus=APPROVED") .header("Authorization", "Bearer " + adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.records").isArray()) .andExpect(jsonPath("$.data.total").isNumber()); } @Test @Order(5) @DisplayName("GET /api/admin/license/review?reviewStatus=REJECTED - 查询已拒绝") void listRejectedReviews_shouldReturnList() throws Exception { mockMvc.perform(get("/api/admin/license/review?reviewStatus=REJECTED") .header("Authorization", "Bearer " + adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.records").isArray()) .andExpect(jsonPath("$.data.total").isNumber()); } @Test @Order(6) @DisplayName("GET /api/admin/license/review - 无Token应返回401") void listPendingReviews_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(get("/api/admin/license/review")) .andExpect(status().isUnauthorized()); } // ==================== 管理员新增 ==================== @Test @Order(7) @DisplayName("POST /api/admin/license - 管理员新增入驻信息(创建后状态为PENDING)") void createByAdmin_shouldSucceedAndReturnPending() throws Exception { String body = """ { "userId": 3, "storeName": "管理员新增药房", "terminalType": "SINGLE" "province": "广东省", "city": "深圳市", "district": "福田区", "storeAddress": "福华路100号", "contactPerson": "赵六", "contactPhone": "13900139003", "businessLicenseUrl": "https://oss.example.com/license/admin-create.jpg", "drugLicenseUrl": "https://oss.example.com/drug/admin-create.jpg", "creditCode": "91110000MA11111111", "medicalDeviceClass3Url": "https://oss.example.com/device/class3.jpg" } """; mockMvc.perform(post("/api/admin/license") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); // 验证创建后状态为 PENDING(待审核确认) mockMvc.perform(get("/api/admin/license/review?reviewStatus=PENDING") .header("Authorization", "Bearer " + adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").isArray()); } @Test @Order(8) @DisplayName("POST /api/admin/license - 缺少必填字段应返回400(缺少店铺名称)") void createByAdmin_missingStoreName_shouldReturn400() throws Exception { String body = """ { "userId": 4, "terminalType": "SINGLE" "province": "广东省", "city": "深圳市", "district": "福田区", "storeAddress": "福华路100号", "contactPerson": "赵六", "contactPhone": "13900139004", "businessLicenseUrl": "https://oss.example.com/license/missing.jpg", "drugLicenseUrl": "https://oss.example.com/drug/missing.jpg" } """; mockMvc.perform(post("/api/admin/license") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @Order(9) @DisplayName("POST /api/admin/license - 缺少userId应返回400") void createByAdmin_missingUserId_shouldReturn400() throws Exception { String body = """ { "storeName": "无客户药房", "terminalType": "SINGLE" "province": "广东省", "city": "深圳市", "district": "福田区", "storeAddress": "福华路100号", "contactPerson": "赵六", "contactPhone": "13900139005", "businessLicenseUrl": "https://oss.example.com/license/no-user.jpg", "drugLicenseUrl": "https://oss.example.com/drug/no-user.jpg" } """; mockMvc.perform(post("/api/admin/license") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @Order(10) @DisplayName("POST /api/admin/license - 无Token应返回401") void createByAdmin_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(post("/api/admin/license") .contentType(MediaType.APPLICATION_JSON) .content("{\"userId\":5,\"storeName\":\"test\"}")) .andExpect(status().isUnauthorized()); } // ==================== 管理员编辑 ==================== @Test @Order(20) @DisplayName("PUT /api/admin/license/{licenseId} - 管理员编辑入驻信息") void editByAdmin_shouldSucceed() throws Exception { String body = """ { "storeName": "编辑后药房名称", "terminalType": "CHAIN" "province": "北京市", "city": "北京市", "district": "海淀区", "storeAddress": "中关村大街1号", "contactPerson": "李四更新", "contactPhone": "13900139001", "businessLicenseUrl": "https://oss.example.com/license/edited.jpg", "drugLicenseUrl": "https://oss.example.com/drug/edited.jpg", "creditCode": "91110000MA12345678" } """; mockMvc.perform(put("/api/admin/license/1") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(21) @DisplayName("PUT /api/admin/license/{licenseId} - 缺少必填字段应返回400(缺少营业执照)") void editByAdmin_missingBusinessLicense_shouldReturn400() throws Exception { String body = """ { "storeName": "编辑测试药房", "terminalType": "SINGLE" "province": "广东省", "city": "深圳市", "district": "南山区", "storeAddress": "科技园路1号", "contactPerson": "李四", "contactPhone": "13900139001", "drugLicenseUrl": "https://oss.example.com/drug/edited.jpg" } """; mockMvc.perform(put("/api/admin/license/1") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @Order(22) @DisplayName("PUT /api/admin/license/{licenseId} - 不存在的licenseId应返回404") void editByAdmin_nonExistent_shouldReturn404() throws Exception { String body = """ { "storeName": "不存在的药房", "terminalType": "SINGLE" "province": "广东省", "city": "深圳市", "district": "南山区", "storeAddress": "科技园路1号", "contactPerson": "李四", "contactPhone": "13900139001", "businessLicenseUrl": "https://oss.example.com/license/not-found.jpg", "drugLicenseUrl": "https://oss.example.com/drug/not-found.jpg" } """; mockMvc.perform(put("/api/admin/license/9999") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(404)); } @Test @Order(23) @DisplayName("PUT /api/admin/license/{licenseId} - 无Token应返回401") void editByAdmin_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(put("/api/admin/license/1") .contentType(MediaType.APPLICATION_JSON) .content("{\"storeName\":\"test\"}")) .andExpect(status().isUnauthorized()); } // ==================== 审核操作 ==================== @Test @Order(30) @DisplayName("POST /api/admin/license/review - 审核通过") void reviewLicense_approve_shouldSucceed() throws Exception { String body = """ { "licenseId": 1, "action": "APPROVED" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(31) @DisplayName("POST /api/admin/license/review - 重复审核应返回错误") void reviewLicense_duplicate_shouldReturnError() throws Exception { String body = """ { "licenseId": 1, "action": "APPROVED" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(400)); } @Test @Order(32) @DisplayName("POST /api/admin/license/review - 不存在的licenseId应返回404") void reviewLicense_nonExistent_shouldReturn404() throws Exception { String body = """ { "licenseId": 9999, "action": "APPROVED" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(404)); } @Test @Order(33) @DisplayName("POST /api/admin/license/review - 参数缺失应返回400(缺少action)") void reviewLicense_missingAction_shouldReturn400() throws Exception { String body = """ { "licenseId": 1 } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @Order(34) @DisplayName("POST /api/admin/license/review - 参数缺失应返回400(缺少licenseId)") void reviewLicense_missingLicenseId_shouldReturn400() throws Exception { String body = """ { "action": "APPROVED" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @Order(35) @DisplayName("POST /api/admin/license/review - 无效审核动作应返回400") void reviewLicense_invalidAction_shouldReturn400() throws Exception { String body = """ { "licenseId": 1, "action": "INVALID_ACTION" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(400)); } @Test @Order(36) @DisplayName("POST /api/admin/license/review - 拒绝并填写原因") void reviewLicense_rejectWithReason_shouldSucceed() throws Exception { // 需要一条新的PENDING记录来测试拒绝 // 先用另一个用户上传 String uploadBody = """ { "storeName": "拒绝测试药房", "terminalType": "CHAIN" "province": "北京市", "city": "北京市", "district": "朝阳区", "storeAddress": "望京路88号", "contactPerson": "王五", "contactPhone": "13900139002", "businessLicenseUrl": "https://oss.example.com/license/reject-test.jpg", "drugLicenseUrl": "https://oss.example.com/drug/reject-test.jpg", "creditCode": "91110000MA87654321" } """; mockMvc.perform(post("/api/business-license/upload") .header("Authorization", "Bearer " + generateAccessToken(2L, "13900139000")) .contentType(MediaType.APPLICATION_JSON) .content(uploadBody)) .andExpect(status().isOk()); // 查询待审核列表找到新上传的执照ID并拒绝 String rejectBody = """ { "licenseId": 2, "action": "REJECTED", "rejectReason": "资质图片不清晰,请重新上传" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(rejectBody)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(37) @DisplayName("POST /api/admin/license/review - 驳回原因必填,缺失应返回400") void reviewLicense_rejectWithoutReason_shouldReturn400() throws Exception { // 先创建一条新的PENDING记录用于测试驳回原因必填 String uploadBody = """ { "storeName": "驳回原因测试药房", "terminalType": "CLINIC" "province": "上海市", "city": "上海市", "district": "浦东新区", "storeAddress": "张江路66号", "contactPerson": "钱七", "contactPhone": "13900139006", "businessLicenseUrl": "https://oss.example.com/license/reject-reason.jpg", "drugLicenseUrl": "https://oss.example.com/drug/reject-reason.jpg" } """; mockMvc.perform(post("/api/business-license/upload") .header("Authorization", "Bearer " + generateAccessToken(5L, "13900139006")) .contentType(MediaType.APPLICATION_JSON) .content(uploadBody)) .andExpect(status().isOk()); // 驳回但不填原因,应返回400 String rejectBody = """ { "licenseId": 3, "action": "REJECTED" } """; mockMvc.perform(post("/api/admin/license/review") .header("Authorization", "Bearer " + adminToken()) .contentType(MediaType.APPLICATION_JSON) .content(rejectBody)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(400)); } @Test @Order(38) @DisplayName("POST /api/admin/license/review - 无Token应返回401") void reviewLicense_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(post("/api/admin/license/review") .contentType(MediaType.APPLICATION_JSON) .content("{\"licenseId\":1,\"action\":\"APPROVED\"}")) .andExpect(status().isUnauthorized()); } }