feat(media): refine clerk album review and tests
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
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.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
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.PlayClerkMediaAssetEntity;
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.constant.Constants;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 专门验证 /wx/clerk/user/updateAlbum 在“只有删除/排序”时不会创建新的审核记录,
|
||||
* 并且会立即更新顾客端视图。
|
||||
*/
|
||||
class WxClerkAlbumUpdateApiTest extends AbstractApiTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkDataReviewInfoService dataReviewInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkMediaAssetService mediaAssetService;
|
||||
|
||||
@Autowired
|
||||
private IPlayMediaService mediaService;
|
||||
|
||||
@Test
|
||||
void reorderExistingApprovedMediaDoesNotCreateNewReviewAndUpdatesOrder() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
PlayMediaEntity media1 = seedMedia(clerkId);
|
||||
PlayMediaEntity media2 = seedMedia(clerkId);
|
||||
PlayMediaEntity media3 = seedMedia(clerkId);
|
||||
|
||||
seedApprovedAsset(clerkId, media1.getId(), 0);
|
||||
seedApprovedAsset(clerkId, media2.getId(), 1);
|
||||
seedApprovedAsset(clerkId, media3.getId(), 2);
|
||||
|
||||
long reviewCountBefore = dataReviewInfoService.lambdaQuery()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
|
||||
.count();
|
||||
|
||||
// 仅调整顺序:album 只包含已审核通过的媒资 id,不引入任何新媒资
|
||||
List<String> reordered = List.of(media3.getId(), media1.getId(), media2.getId());
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
com.fasterxml.jackson.databind.node.ArrayNode albumArray = payload.putArray("album");
|
||||
reordered.forEach(albumArray::add);
|
||||
|
||||
mockMvc.perform(post("/wx/clerk/user/updateAlbum")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
ensureTenantContext();
|
||||
long reviewCountAfter = dataReviewInfoService.lambdaQuery()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
|
||||
.count();
|
||||
assertThat(reviewCountAfter)
|
||||
.as("reordering without introducing new media should not create review records")
|
||||
.isEqualTo(reviewCountBefore);
|
||||
|
||||
List<PlayClerkMediaAssetEntity> assets = mediaAssetService
|
||||
.listByState(clerkId, ClerkMediaUsage.PROFILE,
|
||||
Collections.singletonList(ClerkMediaReviewState.APPROVED));
|
||||
PlayClerkMediaAssetEntity asset1 = assets.stream()
|
||||
.filter(a -> media1.getId().equals(a.getMediaId()))
|
||||
.max(java.util.Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
PlayClerkMediaAssetEntity asset2 = assets.stream()
|
||||
.filter(a -> media2.getId().equals(a.getMediaId()))
|
||||
.max(java.util.Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
PlayClerkMediaAssetEntity asset3 = assets.stream()
|
||||
.filter(a -> media3.getId().equals(a.getMediaId()))
|
||||
.max(java.util.Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
|
||||
assertThat(asset3).as("asset for media3 should exist").isNotNull();
|
||||
assertThat(asset1).as("asset for media1 should exist").isNotNull();
|
||||
assertThat(asset2).as("asset for media2 should exist").isNotNull();
|
||||
|
||||
assertThat(asset3.getOrderIndex()).isEqualTo(0);
|
||||
assertThat(asset1.getOrderIndex()).isEqualTo(1);
|
||||
assertThat(asset2.getOrderIndex()).isEqualTo(2);
|
||||
|
||||
PlayClerkUserInfoResultVo detail = clerkUserInfoService.buildCustomerDetail(clerkId, "");
|
||||
List<String> customerMediaIds = detail.getMediaList().stream()
|
||||
.map(MediaVo::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int index3 = customerMediaIds.indexOf(media3.getId());
|
||||
int index1 = customerMediaIds.indexOf(media1.getId());
|
||||
int index2 = customerMediaIds.indexOf(media2.getId());
|
||||
|
||||
assertThat(index3).isGreaterThanOrEqualTo(0);
|
||||
assertThat(index1).isGreaterThanOrEqualTo(0);
|
||||
assertThat(index2).isGreaterThanOrEqualTo(0);
|
||||
assertThat(index3).isLessThan(index1);
|
||||
assertThat(index1).isLessThan(index2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteAndReorderAlbumDoesNotCreateNewReviewAndIsImmediatelyVisible() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
// 预置两条已审核通过的 PROFILE 媒资
|
||||
PlayMediaEntity media1 = seedMedia(clerkId);
|
||||
PlayMediaEntity media2 = seedMedia(clerkId);
|
||||
|
||||
seedApprovedAsset(clerkId, media1.getId(), 0);
|
||||
seedApprovedAsset(clerkId, media2.getId(), 1);
|
||||
|
||||
// 顾客端初始视图应包含两条媒资
|
||||
PlayClerkUserInfoResultVo beforeDetail = clerkUserInfoService.buildCustomerDetail(clerkId, "");
|
||||
List<String> beforeIds = beforeDetail.getMediaList().stream()
|
||||
.map(MediaVo::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(beforeIds).contains(media1.getId(), media2.getId());
|
||||
|
||||
// 记录当前相册审核记录数量
|
||||
long reviewCountBefore = dataReviewInfoService.lambdaQuery()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
|
||||
.count();
|
||||
|
||||
// 现在通过 updateAlbum 只保留 media2,相当于“删除 media1 + 不引入新媒资”
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.putArray("album").add(media2.getId());
|
||||
|
||||
mockMvc.perform(post("/wx/clerk/user/updateAlbum")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
ensureTenantContext();
|
||||
long reviewCountAfter = dataReviewInfoService.lambdaQuery()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
|
||||
.count();
|
||||
assertThat(reviewCountAfter)
|
||||
.as("Deleting/reordering without new media should not create new review records")
|
||||
.isEqualTo(reviewCountBefore);
|
||||
|
||||
// 资产表中仅剩 media2 且仍为 APPROVED 状态
|
||||
List<PlayClerkMediaAssetEntity> assets = mediaAssetService
|
||||
.listByState(clerkId, ClerkMediaUsage.PROFILE,
|
||||
Collections.singletonList(ClerkMediaReviewState.APPROVED));
|
||||
List<String> assetMediaIds = assets.stream()
|
||||
.map(PlayClerkMediaAssetEntity::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(assetMediaIds)
|
||||
.contains(media2.getId())
|
||||
.doesNotContain(media1.getId());
|
||||
|
||||
// 顾客端视图应立即反映删除结果
|
||||
PlayClerkUserInfoResultVo afterDetail = clerkUserInfoService.buildCustomerDetail(clerkId, "");
|
||||
List<String> afterIds = afterDetail.getMediaList().stream()
|
||||
.map(MediaVo::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(afterIds)
|
||||
.contains(media2.getId())
|
||||
.doesNotContain(media1.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAlbumRejectsEmptyAlbumPayload() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
|
||||
String clerkToken = wxTokenService.createWxUserToken(clerkId);
|
||||
clerkUserInfoService.updateTokenById(clerkId, clerkToken);
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.putArray("album"); // 空数组
|
||||
|
||||
MvcResult result = mockMvc.perform(post("/wx/clerk/user/updateAlbum")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(payload.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
String body = result.getResponse().getContentAsString();
|
||||
com.fasterxml.jackson.databind.JsonNode root = new ObjectMapper().readTree(body);
|
||||
assertThat(root.path("code").asInt())
|
||||
.as("empty album should be rejected, response=%s", body)
|
||||
.isEqualTo(500);
|
||||
assertThat(root.path("message").asText())
|
||||
.as("error message for empty album should be present, response=%s", body)
|
||||
.isNotBlank();
|
||||
}
|
||||
|
||||
private PlayMediaEntity seedMedia(String clerkId) {
|
||||
String mediaId = "media-" + java.util.UUID.randomUUID().toString().substring(0, 16);
|
||||
PlayMediaEntity entity = new PlayMediaEntity();
|
||||
entity.setId(mediaId);
|
||||
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
entity.setOwnerType(MediaOwnerType.CLERK);
|
||||
entity.setOwnerId(clerkId);
|
||||
entity.setKind("image");
|
||||
entity.setStatus(MediaStatus.READY.getCode());
|
||||
entity.setUrl("https://oss.apitest/" + mediaId + ".png");
|
||||
mediaService.save(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void seedApprovedAsset(String clerkId, String mediaId, int orderIndex) {
|
||||
PlayClerkMediaAssetEntity asset = new PlayClerkMediaAssetEntity();
|
||||
asset.setId("asset-" + mediaId);
|
||||
asset.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
asset.setClerkId(clerkId);
|
||||
asset.setMediaId(mediaId);
|
||||
asset.setUsage(ClerkMediaUsage.PROFILE.getCode());
|
||||
asset.setOrderIndex(orderIndex);
|
||||
asset.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
|
||||
asset.setDeleted(false);
|
||||
mediaAssetService.save(asset);
|
||||
}
|
||||
|
||||
private void ensureTenantContext() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user