207 lines
8.8 KiB
Java
207 lines
8.8 KiB
Java
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);
|
||
}
|
||
}
|