test(media): legacy album compatibility for user list and detail

This commit is contained in:
irving
2025-12-04 23:12:50 -05:00
parent 086aa47226
commit e683ef6863

View File

@@ -0,0 +1,206 @@
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;
/**
* 覆盖微信顾客端店员列表 / 详情在仅存在历史相册数据时的兼容行为。
*
* <p>重点校验:
* <ul>
* <li>旧字段 {@code album} 在列表接口中仍然可见;</li>
* <li>同时会被折叠进 {@code mediaList}且不会破坏后续媒资结构id/url/usage/status/reviewState</li>
* <li>详情接口中 {@link PlayClerkUserInfoResultVo#mediaList} 也包含这些历史相册。</li>
* </ul>
* </p>
*/
class WxClerkUserBackwardCompatApiTest extends AbstractApiTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private IPlayClerkUserInfoService clerkUserInfoService;
private final List<String> 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<String> 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<String> 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<String> 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<String> 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<MediaVo> mediaList = detail.getMediaList();
assertThat(mediaList).isNotNull();
assertThat(mediaList).hasSize(legacyAlbum.size());
Set<String> 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<String> 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);
}
}