feat(media): clerk profile media flow
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
package com.starry.admin.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
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.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.common.oss.service.IOssFileService;
|
||||
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.service.IPlayMediaService;
|
||||
import com.starry.admin.modules.weichat.entity.PlayClerkUserLoginResponseVo;
|
||||
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 com.starry.common.enums.ClerkReviewState;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
/**
|
||||
* 覆盖店员端媒资上传与相册审核的关键业务路径(图片/视频 + 删除后不复活)。
|
||||
*/
|
||||
class WxClerkMediaControllerApiTest extends AbstractApiTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayMediaService mediaService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkDataReviewInfoService dataReviewInfoService;
|
||||
|
||||
@Autowired
|
||||
private IPlayClerkMediaAssetService mediaAssetService;
|
||||
|
||||
@MockBean
|
||||
private IOssFileService ossFileService;
|
||||
|
||||
@Test
|
||||
void clerkCanUploadImageMediaAndPersistUrl() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
String ossUrl = "https://oss.mock/apitest/avatar.png";
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn(ossUrl);
|
||||
|
||||
BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", baos);
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"avatar.png",
|
||||
"image/png",
|
||||
baos.toByteArray());
|
||||
|
||||
MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(file)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.url").value(ossUrl))
|
||||
.andExpect(jsonPath("$.data.kind").value("image"))
|
||||
.andExpect(jsonPath("$.data.usage").value("profile"))
|
||||
.andReturn();
|
||||
|
||||
String body = result.getResponse().getContentAsString();
|
||||
JsonNode root = objectMapper.readTree(body);
|
||||
JsonNode data = root.path("data");
|
||||
String mediaId = data.path("mediaId").asText(null);
|
||||
assertThat(mediaId).isNotBlank();
|
||||
|
||||
ensureTenantContext();
|
||||
PlayMediaEntity persisted = mediaService.getById(mediaId);
|
||||
assertThat(persisted).isNotNull();
|
||||
assertThat(persisted.getOwnerType()).isEqualTo(MediaOwnerType.CLERK);
|
||||
assertThat(persisted.getOwnerId()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
assertThat(persisted.getUrl()).isEqualTo(ossUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clerkCanUploadVideoMediaAndPersistUrl() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
String videoUrl = "https://oss.mock/apitest/video.mp4";
|
||||
String coverUrl = "https://oss.mock/apitest/video-cover.jpg";
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn(videoUrl, coverUrl);
|
||||
|
||||
byte[] videoBytes = Files.readAllBytes(Paths.get("/Volumes/main/code/yunpei/sample_data/sample_video.mp4"));
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"sample_video.mp4",
|
||||
"video/mp4",
|
||||
videoBytes);
|
||||
|
||||
MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(file)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.kind").value("video"))
|
||||
.andExpect(jsonPath("$.data.usage").value("profile"))
|
||||
.andReturn();
|
||||
|
||||
String body = result.getResponse().getContentAsString();
|
||||
JsonNode root = objectMapper.readTree(body);
|
||||
JsonNode data = root.path("data");
|
||||
String mediaId = data.path("mediaId").asText(null);
|
||||
assertThat(mediaId).isNotBlank();
|
||||
|
||||
ensureTenantContext();
|
||||
PlayMediaEntity persisted = mediaService.getById(mediaId);
|
||||
assertThat(persisted).isNotNull();
|
||||
assertThat(persisted.getKind()).isEqualTo("video");
|
||||
assertThat(persisted.getUrl()).isEqualTo(videoUrl);
|
||||
assertThat(persisted.getCoverUrl()).isEqualTo(coverUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void aggressiveAlbumLifecycleWithFourMediaAndDeletionReflectedForClerkAndCustomer() throws Exception {
|
||||
ensureTenantContext();
|
||||
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
||||
|
||||
when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
|
||||
.thenReturn(
|
||||
"https://oss.mock/apitest/album-a.png",
|
||||
"https://oss.mock/apitest/album-b.png",
|
||||
"https://oss.mock/apitest/album-c.png",
|
||||
"https://oss.mock/apitest/album-d.png");
|
||||
|
||||
String mediaIdA = extractMediaIdFromUpload(buildTinyPng("album-a.png"), clerkToken);
|
||||
String mediaIdB = extractMediaIdFromUpload(buildTinyPng("album-b.png"), clerkToken);
|
||||
String mediaIdC = extractMediaIdFromUpload(buildTinyPng("album-c.png"), clerkToken);
|
||||
String mediaIdD = extractMediaIdFromUpload(buildTinyPng("album-d.png"), clerkToken);
|
||||
|
||||
List<String> allMediaIds = List.of(mediaIdA, mediaIdB, mediaIdC, mediaIdD);
|
||||
|
||||
submitAlbumUpdate(allMediaIds, clerkToken);
|
||||
ensureTenantContext();
|
||||
approveLatestAlbumReview();
|
||||
|
||||
List<PlayClerkMediaAssetEntity> assetsAfterFirstApprove = mediaAssetService
|
||||
.listActiveByUsage(ApiTestDataSeeder.DEFAULT_CLERK_ID, ClerkMediaUsage.PROFILE);
|
||||
assertThat(assetsAfterFirstApprove)
|
||||
.extracting(PlayClerkMediaAssetEntity::getMediaId)
|
||||
.containsAll(allMediaIds);
|
||||
List<String> reviewStatesForNewMedia = assetsAfterFirstApprove.stream()
|
||||
.filter(asset -> allMediaIds.contains(asset.getMediaId()))
|
||||
.map(PlayClerkMediaAssetEntity::getReviewState)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(reviewStatesForNewMedia).containsOnly(ClerkMediaReviewState.APPROVED.getCode());
|
||||
|
||||
List<String> clerkVisibleMediaIdsAfterFirst = assetsAfterFirstApprove.stream()
|
||||
.filter(asset -> !ClerkMediaReviewState.REJECTED.getCode().equals(asset.getReviewState()))
|
||||
.map(PlayClerkMediaAssetEntity::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(clerkVisibleMediaIdsAfterFirst).containsAll(allMediaIds);
|
||||
|
||||
PlayClerkUserInfoResultVo customerDetailAfterFirst =
|
||||
clerkUserInfoService.buildCustomerDetail(ApiTestDataSeeder.DEFAULT_CLERK_ID, "");
|
||||
List<String> customerMediaIdsAfterFirst = customerDetailAfterFirst.getMediaList().stream()
|
||||
.map(MediaVo::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(customerMediaIdsAfterFirst).containsAll(allMediaIds);
|
||||
|
||||
List<String> keptMedia = List.of(mediaIdA, mediaIdC);
|
||||
submitAlbumUpdate(keptMedia, clerkToken);
|
||||
ensureTenantContext();
|
||||
approveLatestAlbumReview();
|
||||
|
||||
List<PlayClerkMediaAssetEntity> assetsAfterSecondApprove = mediaAssetService
|
||||
.listActiveByUsage(ApiTestDataSeeder.DEFAULT_CLERK_ID, ClerkMediaUsage.PROFILE);
|
||||
|
||||
PlayClerkMediaAssetEntity assetA = assetsAfterSecondApprove.stream()
|
||||
.filter(a -> mediaIdA.equals(a.getMediaId()))
|
||||
.max(Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
PlayClerkMediaAssetEntity assetB = assetsAfterSecondApprove.stream()
|
||||
.filter(a -> mediaIdB.equals(a.getMediaId()))
|
||||
.max(Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
PlayClerkMediaAssetEntity assetC = assetsAfterSecondApprove.stream()
|
||||
.filter(a -> mediaIdC.equals(a.getMediaId()))
|
||||
.max(Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
PlayClerkMediaAssetEntity assetD = assetsAfterSecondApprove.stream()
|
||||
.filter(a -> mediaIdD.equals(a.getMediaId()))
|
||||
.max(Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
|
||||
.orElse(null);
|
||||
|
||||
assertThat(assetA).isNotNull();
|
||||
assertThat(assetA.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
|
||||
assertThat(assetC).isNotNull();
|
||||
assertThat(assetC.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
|
||||
assertThat(assetB).isNotNull();
|
||||
assertThat(assetB.getReviewState()).isEqualTo(ClerkMediaReviewState.REJECTED.getCode());
|
||||
assertThat(assetD).isNotNull();
|
||||
assertThat(assetD.getReviewState()).isEqualTo(ClerkMediaReviewState.REJECTED.getCode());
|
||||
|
||||
PlayClerkUserInfoResultVo customerDetailAfterSecond =
|
||||
clerkUserInfoService.buildCustomerDetail(ApiTestDataSeeder.DEFAULT_CLERK_ID, "");
|
||||
List<String> customerMediaIdsAfterSecond = customerDetailAfterSecond.getMediaList().stream()
|
||||
.map(MediaVo::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(customerMediaIdsAfterSecond)
|
||||
.contains(mediaIdA, mediaIdC)
|
||||
.doesNotContain(mediaIdB, mediaIdD);
|
||||
|
||||
List<String> clerkVisibleMediaIdsAfterSecond = assetsAfterSecondApprove.stream()
|
||||
.filter(asset -> !ClerkMediaReviewState.REJECTED.getCode().equals(asset.getReviewState()))
|
||||
.map(PlayClerkMediaAssetEntity::getMediaId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(clerkVisibleMediaIdsAfterSecond)
|
||||
.contains(mediaIdA, mediaIdC)
|
||||
.doesNotContain(mediaIdB, mediaIdD);
|
||||
}
|
||||
|
||||
private MockMultipartFile buildTinyPng(String filename) throws Exception {
|
||||
BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", baos);
|
||||
return new MockMultipartFile("file", filename, "image/png", baos.toByteArray());
|
||||
}
|
||||
|
||||
private String extractMediaIdFromUpload(MockMultipartFile file, String clerkToken) throws Exception {
|
||||
MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
|
||||
.file(file)
|
||||
.param("usage", "profile")
|
||||
.header(USER_HEADER, DEFAULT_USER)
|
||||
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andReturn();
|
||||
String body = result.getResponse().getContentAsString();
|
||||
JsonNode root = objectMapper.readTree(body);
|
||||
return root.path("data").path("mediaId").asText();
|
||||
}
|
||||
|
||||
private void submitAlbumUpdate(List<String> mediaIds, String clerkToken) throws Exception {
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
com.fasterxml.jackson.databind.node.ArrayNode albumArray = payload.putArray("album");
|
||||
mediaIds.forEach(albumArray::add);
|
||||
|
||||
mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.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));
|
||||
}
|
||||
|
||||
private void approveLatestAlbumReview() {
|
||||
List<com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity> reviews = dataReviewInfoService
|
||||
.lambdaQuery()
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId,
|
||||
ApiTestDataSeeder.DEFAULT_CLERK_ID)
|
||||
.eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
|
||||
.orderByDesc(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getAddTime)
|
||||
.list();
|
||||
assertThat(reviews).isNotEmpty();
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity latest = reviews.get(0);
|
||||
|
||||
com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo vo =
|
||||
new com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo();
|
||||
vo.setId(latest.getId());
|
||||
vo.setReviewState(ClerkReviewState.APPROVED);
|
||||
vo.setReviewCon("ok");
|
||||
vo.setReviewTime(LocalDateTime.now());
|
||||
dataReviewInfoService.updateDataReviewState(vo);
|
||||
}
|
||||
|
||||
private void ensureTenantContext() {
|
||||
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user