package com.starry.admin.api; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.starry.admin.common.apitest.ApiTestDataSeeder; import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState; import com.starry.admin.modules.clerk.enums.ClerkMediaUsage; import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity; import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService; import com.starry.admin.modules.media.enums.MediaStatus; import com.starry.admin.modules.weichat.entity.clerk.MediaVo; import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo; import com.starry.admin.utils.SecurityUtils; import com.starry.common.utils.IdUtils; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; /** * 覆盖微信顾客端店员列表 / 详情在仅存在历史相册数据时的兼容行为。 * *

重点校验: *

*

*/ class WxClerkUserBackwardCompatApiTest extends AbstractApiTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired private IPlayClerkUserInfoService clerkUserInfoService; private final List clerkIdsToCleanup = new ArrayList<>(); @AfterEach void tearDown() { ensureTenantContext(); if (!clerkIdsToCleanup.isEmpty()) { clerkUserInfoService.removeByIds(clerkIdsToCleanup); clerkIdsToCleanup.clear(); } } /** * 场景:只有旧相册(album) 而没有 play_media / play_clerk_media_asset 记录时, * 微信顾客端列表接口仍然返回可用的 album 和 mediaList。 */ @Test void customerListIncludesLegacyAlbumAsMediaList() throws Exception { ensureTenantContext(); String marker = "legacy-album-" + IdUtils.getUuid().substring(0, 8); List legacyAlbum = List.of( "https://example.com/apitest/legacy/" + marker + "-a.png", "https://example.com/apitest/legacy/" + marker + "-b.png"); String clerkId = createLegacyAlbumClerk(marker, legacyAlbum); ObjectNode payload = objectMapper.createObjectNode(); payload.put("pageNum", 1); payload.put("pageSize", 20); payload.put("nickname", marker); payload.put("typeId", ""); payload.put("levelId", ""); payload.put("sex", ""); payload.put("province", ""); MvcResult result = mockMvc.perform(post("/wx/clerk/user/queryByPage") .header(TENANT_HEADER, DEFAULT_TENANT) .header(USER_HEADER, DEFAULT_USER) .contentType(MediaType.APPLICATION_JSON) .content(payload.toString())) .andExpect(status().isOk()) .andReturn(); String body = result.getResponse().getContentAsString(); JsonNode root = objectMapper.readTree(body); assertThat(root.path("code").asInt()).as("response=%s", body).isEqualTo(200); JsonNode records = root.path("data").path("records"); assertThat(records.isArray()).as("records should be array, response=%s", body).isTrue(); JsonNode target = null; for (JsonNode record : records) { if (clerkId.equals(record.path("id").asText())) { target = record; break; } } assertThat(target) .withFailMessage("列表中未找到目标店员 %s, response=%s", clerkId, body) .isNotNull(); // album 字段仍然保留原有的历史值 JsonNode albumNode = target.path("album"); assertThat(albumNode.isArray()).isTrue(); Set albumUrls = new HashSet<>(); albumNode.forEach(node -> albumUrls.add(node.asText())); assertThat(albumUrls).containsExactlyInAnyOrderElementsOf(legacyAlbum); // mediaList 中应当包含折叠后的 Legacy 媒资,并为其补齐 usage/status/reviewState 等字段 JsonNode mediaListNode = target.path("mediaList"); assertThat(mediaListNode.isArray()).isTrue(); assertThat(mediaListNode.size()).isEqualTo(legacyAlbum.size()); Set mediaUrls = new HashSet<>(); for (JsonNode media : mediaListNode) { String url = media.path("url").asText(); mediaUrls.add(url); if (!legacyAlbum.contains(url)) { continue; } // 对于从 album 折叠出来的条目,我们要求: // id 与 url 相同;usage/status/reviewState 均为兼容值。 assertThat(media.path("id").asText()).isEqualTo(url); assertThat(media.path("usage").asText()).isEqualTo(ClerkMediaUsage.PROFILE.getCode()); assertThat(media.path("status").asText()).isEqualTo(MediaStatus.READY.getCode()); assertThat(media.path("reviewState").asText()) .isEqualTo(ClerkMediaReviewState.APPROVED.getCode()); } assertThat(mediaUrls).containsAll(legacyAlbum); } /** * 场景:只有旧相册(album) 数据时,顾客端店员详情接口 buildCustomerDetail 也会将其折叠进 mediaList, * 同时保留 album 字段不变。 */ @Test void customerDetailMergesLegacyAlbumIntoMediaList() { ensureTenantContext(); String marker = "legacy-detail-" + IdUtils.getUuid().substring(0, 8); List legacyAlbum = List.of( "https://example.com/apitest/legacy/" + marker + "-a.png", "https://example.com/apitest/legacy/" + marker + "-b.png"); String clerkId = createLegacyAlbumClerk(marker, legacyAlbum); PlayClerkUserInfoResultVo detail = clerkUserInfoService.buildCustomerDetail(clerkId, ""); assertThat(detail.getAlbum()).containsExactlyElementsOf(legacyAlbum); List mediaList = detail.getMediaList(); assertThat(mediaList).isNotNull(); assertThat(mediaList).hasSize(legacyAlbum.size()); Set mediaUrls = new HashSet<>(); for (MediaVo media : mediaList) { String url = media.getUrl(); mediaUrls.add(url); if (!legacyAlbum.contains(url)) { continue; } assertThat(media.getId()).isEqualTo(url); assertThat(media.getUsage()).isEqualTo(ClerkMediaUsage.PROFILE.getCode()); assertThat(media.getStatus()).isEqualTo(MediaStatus.READY.getCode()); assertThat(media.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode()); } assertThat(mediaUrls).containsAll(legacyAlbum); } private String createLegacyAlbumClerk(String marker, List album) { String clerkId = IdUtils.getUuid(); PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity(); clerk.setId(clerkId); clerk.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); clerk.setNickname("兼容测试店员-" + marker); clerk.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID); clerk.setClerkState("1"); clerk.setOnboardingState("1"); clerk.setListingState("1"); clerk.setDisplayState("1"); clerk.setRecommendationState("0"); clerk.setPinToTopState("0"); clerk.setRandomOrderState("1"); clerk.setFixingLevel("0"); clerk.setOnlineState("1"); clerk.setPhone("138" + clerkId.substring(0, 8)); clerk.setOpenid("openid-legacy-" + marker); clerk.setWeiChatCode("wx-code-legacy-" + marker); clerk.setTypeId("api-type-legacy"); clerk.setProvince("API省"); clerk.setCity("API市"); clerk.setEntryTime(LocalDateTime.now()); clerk.setAddTime(LocalDateTime.now()); clerk.setAlbum(new ArrayList<>(album)); clerkUserInfoService.save(clerk); clerkIdsToCleanup.add(clerkId); return clerkId; } private void ensureTenantContext() { SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID); } }