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.*; /** * 营业执照和优惠券模块测试 * * 覆盖场景: * 1. 营业执照上传/查询(可选功能) * 2. 优惠券获取/使用 * 3. 用户等级查询 */ @DisplayName("营业执照和优惠券模块测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class BusinessLicenseAndCouponTest extends GatewayBaseTest { private String userToken() { return generateAccessToken(1L, "13800138000"); } // ==================== 营业执照上传(可选功能)==================== @Test @Order(1) @DisplayName("POST /api/business-license/upload - 上传营业执照") void uploadBusinessLicense_shouldSucceed() throws Exception { String body = """ { "licenseImageUrl": "https://oss.example.com/license/test.jpg", "creditCode": "91110000XXXXXXXXXX", "pharmacyName": "测试大药房", "legalPerson": "张三" } """; mockMvc.perform(post("/api/business-license/upload") .header("Authorization", "Bearer " + userToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(2) @DisplayName("POST /api/business-license/upload - 缺少必填字段应返回400") void uploadBusinessLicense_missingFields_shouldReturnBadRequest() throws Exception { String body = """ { "pharmacyName": "测试大药房" } """; mockMvc.perform(post("/api/business-license/upload") .header("Authorization", "Bearer " + userToken()) .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @Order(3) @DisplayName("POST /api/business-license/upload - 无Token应返回401") void uploadBusinessLicense_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(post("/api/business-license/upload") .contentType(MediaType.APPLICATION_JSON) .content("{}")) .andExpect(status().isUnauthorized()); } @Test @Order(4) @DisplayName("GET /api/business-license/info - 查询营业执照信息") void getBusinessLicenseInfo_shouldReturnInfo() throws Exception { mockMvc.perform(get("/api/business-license/info") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(5) @DisplayName("GET /api/business-license/info - 未上传应返回空或null") void getBusinessLicenseInfo_notUploaded_shouldReturnEmpty() throws Exception { mockMvc.perform(get("/api/business-license/info") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()); // 可能返回data为null或空对象,但不应报错 } @Test @Order(6) @DisplayName("GET /api/business-license/info - 无Token应返回401") void getBusinessLicenseInfo_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(get("/api/business-license/info")) .andExpect(status().isUnauthorized()); } // ==================== 优惠券管理 ==================== @Test @Order(10) @DisplayName("GET /api/coupon/my - 获取我的优惠券列表") void getMyCoupons_shouldReturnList() throws Exception { mockMvc.perform(get("/api/coupon/my") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").isArray()); } @Test @Order(11) @DisplayName("GET /api/coupon/my?status=0 - 筛选未使用优惠券") void getMyCoupons_filterByStatus_shouldWork() throws Exception { mockMvc.perform(get("/api/coupon/my?status=0") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data").isArray()); } @Test @Order(12) @DisplayName("GET /api/coupon/my?status=1 - 筛选已使用优惠券") void getMyCoupons_filterByUsedStatus_shouldWork() throws Exception { mockMvc.perform(get("/api/coupon/my?status=1") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()); } @Test @Order(13) @DisplayName("GET /api/coupon/my?status=2 - 筛选已过期优惠券") void getMyCoupons_filterByExpiredStatus_shouldWork() throws Exception { mockMvc.perform(get("/api/coupon/my?status=2") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()); } @Test @Order(14) @DisplayName("GET /api/coupon/my - 无Token应返回401") void getMyCoupons_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(get("/api/coupon/my")) .andExpect(status().isUnauthorized()); } @Test @Order(15) @DisplayName("GET /api/coupon/my/available?type=CRAWLER - 获取可用优惠券") void getAvailableCoupons_shouldReturnList() throws Exception { mockMvc.perform(get("/api/coupon/my/available?type=CRAWLER") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").isArray()); } @Test @Order(16) @DisplayName("GET /api/coupon/my/available - 无Token应返回401") void getAvailableCoupons_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(get("/api/coupon/my/available")) .andExpect(status().isUnauthorized()); } // ==================== 用户等级查询 ==================== @Test @Order(20) @DisplayName("GET /api/level/list - 获取等级列表(公开)") void getLevelList_shouldReturnList() throws Exception { mockMvc.perform(get("/api/level/list")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").isArray()); } @Test @Order(21) @DisplayName("GET /api/level/list - 等级应包含必要字段") void getLevelList_shouldContainRequiredFields() throws Exception { mockMvc.perform(get("/api/level/list")) .andExpect(status().isOk()) .andExpect(result -> { String content = result.getResponse().getContentAsString(); com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); var json = mapper.readTree(content); var data = json.get("data"); if (data.isArray() && data.size() > 0) { var firstLevel = data.get(0); assert firstLevel.has("id") : "应包含id"; assert firstLevel.has("levelName") : "应包含levelName"; assert firstLevel.has("levelCode") : "应包含levelCode"; assert firstLevel.has("crawlerQuota") : "应包含crawlerQuota"; assert firstLevel.has("monthlyQuota") : "应包含monthlyQuota"; assert firstLevel.has("yearlyQuota") : "应包含yearlyQuota"; assert firstLevel.has("maxConcurrent") : "应包含maxConcurrent"; } }); } @Test @Order(22) @DisplayName("GET /api/level/{id} - 获取等级详情") void getLevelDetail_shouldReturnDetail() throws Exception { mockMvc.perform(get("/api/level/1") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(23) @DisplayName("GET /api/level/{id} - 不存在的等级应返回404") void getLevelDetail_nonExistent_shouldReturn404() throws Exception { mockMvc.perform(get("/api/level/999") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isNotFound()); } @Test @Order(24) @DisplayName("GET /api/level/my - 获取当前用户等级") void getMyLevel_shouldReturnLevel() throws Exception { mockMvc.perform(get("/api/level/my") .header("Authorization", "Bearer " + userToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)); } @Test @Order(25) @DisplayName("GET /api/level/my - 无Token应返回401") void getMyLevel_withoutToken_shouldReturn401() throws Exception { mockMvc.perform(get("/api/level/my")) .andExpect(status().isUnauthorized()); } // ==================== Token刷新 ==================== @Test @Order(30) @DisplayName("POST /api/auth/refresh - 刷新Token") void refreshToken_shouldReturnNewTokens() throws Exception { // 注意:这个测试可能需要一个有效的refresh token // 由于是集成测试,我们验证接口可访问性 mockMvc.perform(post("/api/auth/refresh") .header("Authorization", "Bearer " + userToken()) .contentType(MediaType.APPLICATION_JSON) .content("{\"refreshToken\":\"test_refresh_token\"}")) .andExpect(status().isOk()); // 如果token无效,会返回业务错误,但不应401 } @Test @Order(31) @DisplayName("POST /api/auth/refresh - 空请求体应返回400") void refreshToken_emptyBody_shouldReturnBadRequest() throws Exception { mockMvc.perform(post("/api/auth/refresh") .contentType(MediaType.APPLICATION_JSON) .content("")) .andExpect(status().isBadRequest()); } }