wip: media migration progress

This commit is contained in:
irving
2025-11-16 11:33:58 -05:00
parent 69909a3b83
commit 8558d203af
37 changed files with 2314 additions and 11 deletions

View File

@@ -43,8 +43,8 @@ class PlayClerkUserInfoApiTest extends AbstractApiTest {
private final List<String> clerkIdsToCleanup = new ArrayList<>();
private int scenarioSequence = 0;
private static final Comparator<ClerkScenario> BACKEND_ORDERING = Comparator
.comparing(ClerkScenario::isOnline).reversed()
.thenComparing(ClerkScenario::isPinned).reversed()
.comparing(ClerkScenario::isOnline, Comparator.reverseOrder())
.thenComparing(ClerkScenario::isPinned, Comparator.reverseOrder())
.thenComparingLong(ClerkScenario::getLevelOrder)
.thenComparingInt(ClerkScenario::getSequence)
.thenComparing(ClerkScenario::getId);

View File

@@ -0,0 +1,118 @@
package com.starry.admin.modules.clerk.service.impl;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo;
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.service.IPlayMediaService;
import com.starry.common.enums.ClerkReviewState;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
@ExtendWith(MockitoExtension.class)
class PlayClerkDataReviewInfoServiceImplTest {
@Mock
private IPlayClerkUserInfoService clerkUserInfoService;
@Mock
private IPlayClerkMediaAssetService clerkMediaAssetService;
@Mock
private IPlayMediaService mediaService;
private PlayClerkDataReviewInfoServiceImpl service;
@BeforeEach
void setUp() {
service = spy(new PlayClerkDataReviewInfoServiceImpl());
ReflectionTestUtils.setField(service, "playClerkUserInfoService", clerkUserInfoService);
ReflectionTestUtils.setField(service, "clerkMediaAssetService", clerkMediaAssetService);
ReflectionTestUtils.setField(service, "mediaService", mediaService);
doReturn(true).when(service).update(any(PlayClerkDataReviewInfoEntity.class));
}
@Test
void updateDataReviewStateShouldSynchronizeAlbumOnApproval() {
PlayClerkDataReviewInfoEntity review = buildReview("review-1", "clerk-1", "2",
Arrays.asList("media-existing", "media-new"));
doReturn(review).when(service).selectPlayClerkDataReviewInfoById("review-1");
PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
clerk.setId("clerk-1");
clerk.setTenantId("tenant-1");
when(clerkUserInfoService.getById("clerk-1")).thenReturn(clerk);
PlayMediaEntity existingMedia = new PlayMediaEntity();
existingMedia.setId("media-existing");
when(mediaService.getById("media-existing")).thenReturn(existingMedia);
PlayMediaEntity newMedia = new PlayMediaEntity();
newMedia.setId("media-new");
when(mediaService.getById("media-new")).thenReturn(newMedia);
PlayClerkMediaAssetEntity assetStub = new PlayClerkMediaAssetEntity();
assetStub.setId("asset-1");
when(clerkMediaAssetService.linkDraftAsset(any(), any(), any(), any())).thenReturn(assetStub);
PlayClerkDataReviewStateEditVo vo = new PlayClerkDataReviewStateEditVo();
vo.setId("review-1");
vo.setReviewState(ClerkReviewState.APPROVED);
vo.setReviewCon("ok");
service.updateDataReviewState(vo);
verify(clerkMediaAssetService).linkDraftAsset(eq("tenant-1"), eq("clerk-1"), eq("media-existing"),
eq(ClerkMediaUsage.PROFILE));
verify(clerkMediaAssetService).linkDraftAsset(eq("tenant-1"), eq("clerk-1"), eq("media-new"), eq(ClerkMediaUsage.PROFILE));
verify(clerkMediaAssetService).applyReviewDecision(eq("clerk-1"), eq(ClerkMediaUsage.PROFILE),
eq(Arrays.asList("media-existing", "media-new")), eq("review-1"), eq("ok"));
verify(clerkUserInfoService).update(any(PlayClerkUserInfoEntity.class));
}
@Test
void updateDataReviewStateThrowsWhenClerkMissing() {
PlayClerkDataReviewInfoEntity review = buildReview("review-2", "ghost", "2",
Collections.singletonList("media"));
doReturn(review).when(service).selectPlayClerkDataReviewInfoById("review-2");
when(clerkUserInfoService.getById("ghost")).thenReturn(null);
PlayClerkDataReviewStateEditVo vo = new PlayClerkDataReviewStateEditVo();
vo.setId("review-2");
vo.setReviewState(ClerkReviewState.APPROVED);
assertThatThrownBy(() -> service.updateDataReviewState(vo))
.isInstanceOf(CustomException.class)
.hasMessageContaining("店员信息不存在");
}
private PlayClerkDataReviewInfoEntity buildReview(String id, String clerkId, String dataType,
java.util.List<String> payload) {
PlayClerkDataReviewInfoEntity entity = new PlayClerkDataReviewInfoEntity();
entity.setId(id);
entity.setClerkId(clerkId);
entity.setDataType(dataType);
entity.setDataContent(payload);
entity.setAddTime(LocalDateTime.now());
return entity;
}
}

View File

@@ -0,0 +1,128 @@
package com.starry.admin.modules.clerk.service.impl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
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.media.entity.PlayMediaEntity;
import com.starry.admin.modules.media.service.IPlayMediaService;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
@ExtendWith(MockitoExtension.class)
class PlayClerkMediaAssetServiceImplTest {
@Mock
private IPlayMediaService mediaService;
@Test
void submitWithOrderShouldReindexAndFlagPending() {
PlayClerkMediaAssetServiceImpl service = org.mockito.Mockito.spy(new PlayClerkMediaAssetServiceImpl());
ReflectionTestUtils.setField(service, "mediaService", mediaService);
List<PlayClerkMediaAssetEntity> assets = Arrays.asList(
buildAsset("A", "media-a", 0),
buildAsset("B", "media-b", 1),
buildAsset("C", "media-c", 2));
doReturn(assets).when(service).listActiveByUsage("clerk-1", ClerkMediaUsage.PROFILE);
java.util.List<java.util.List<PlayClerkMediaAssetEntity>> batches = new java.util.ArrayList<>();
org.mockito.Mockito.doAnswer(inv -> {
java.util.Collection<PlayClerkMediaAssetEntity> collection = inv.getArgument(0);
batches.add(new java.util.ArrayList<>(collection));
return true;
}).when(service).updateBatchById(any());
service.submitWithOrder("clerk-1", ClerkMediaUsage.PROFILE, Arrays.asList("media-b", "media-a"));
List<PlayClerkMediaAssetEntity> updates = flatten(batches);
PlayClerkMediaAssetEntity b = find(updates, "B");
PlayClerkMediaAssetEntity a = find(updates, "A");
PlayClerkMediaAssetEntity c = find(updates, "C");
assertThat(b.getOrderIndex()).isEqualTo(0);
assertThat(b.getReviewState()).isEqualTo(ClerkMediaReviewState.PENDING.getCode());
assertThat(a.getOrderIndex()).isEqualTo(1);
assertThat(a.getReviewState()).isEqualTo(ClerkMediaReviewState.PENDING.getCode());
assertThat(c.getReviewState()).isEqualTo(ClerkMediaReviewState.REJECTED.getCode());
}
@Test
void applyReviewDecisionShouldApproveAndReject() {
PlayClerkMediaAssetServiceImpl service = org.mockito.Mockito.spy(new PlayClerkMediaAssetServiceImpl());
ReflectionTestUtils.setField(service, "mediaService", mediaService);
List<PlayClerkMediaAssetEntity> assets = Arrays.asList(
buildAsset("A", "media-1", 0),
buildAsset("B", "media-2", 1));
doReturn(assets).when(service).listActiveByUsage("clerk-1", ClerkMediaUsage.PROFILE);
when(mediaService.listByIds(any())).thenReturn(Arrays.asList(
buildMedia("media-1"),
buildMedia("media-2")));
java.util.List<java.util.List<PlayClerkMediaAssetEntity>> batches = new java.util.ArrayList<>();
org.mockito.Mockito.doAnswer(inv -> {
java.util.Collection<PlayClerkMediaAssetEntity> collection = inv.getArgument(0);
batches.add(new java.util.ArrayList<>(collection));
return true;
}).when(service).updateBatchById(any());
service.applyReviewDecision("clerk-1", ClerkMediaUsage.PROFILE, Arrays.asList("media-2"), "review-1", "ok");
List<PlayClerkMediaAssetEntity> updates = flatten(batches);
PlayClerkMediaAssetEntity approved = find(updates, "B");
PlayClerkMediaAssetEntity rejected = find(updates, "A");
assertThat(approved.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
assertThat(approved.getOrderIndex()).isEqualTo(0);
assertThat(approved.getReviewRecordId()).isEqualTo("review-1");
assertThat(rejected.getReviewState()).isEqualTo(ClerkMediaReviewState.REJECTED.getCode());
}
private static PlayClerkMediaAssetEntity buildAsset(String id, String mediaId, int order) {
PlayClerkMediaAssetEntity entity = new PlayClerkMediaAssetEntity();
entity.setId(id);
entity.setClerkId("clerk-1");
entity.setTenantId("tenant-1");
entity.setMediaId(mediaId);
entity.setUsage(ClerkMediaUsage.PROFILE.getCode());
entity.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
entity.setOrderIndex(order);
entity.setSubmittedTime(LocalDateTime.now());
return entity;
}
private static PlayMediaEntity buildMedia(String id) {
PlayMediaEntity media = new PlayMediaEntity();
media.setId(id);
media.setUrl("url-" + id);
return media;
}
private static PlayClerkMediaAssetEntity find(List<PlayClerkMediaAssetEntity> updates, String id) {
return updates.stream()
.filter(asset -> id.equals(asset.getId()))
.findFirst()
.orElseThrow();
}
@SuppressWarnings("unchecked")
private static List<PlayClerkMediaAssetEntity> flatten(List<? extends List<PlayClerkMediaAssetEntity>> captured) {
return captured.stream()
.flatMap(List::stream)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View File

@@ -0,0 +1,27 @@
package com.starry.admin.modules.clerk.service.impl;
import static org.assertj.core.api.Assertions.assertThat;
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
class PlayClerkUserInfoServiceImplTest {
@Test
void mergeLegacyAlbumAppendsUniqueUrls() {
List<String> legacy = Arrays.asList("https://oss/1.png", " ", "https://oss/2.png", "https://oss/1.png");
List<MediaVo> destination = new ArrayList<>();
MediaVo existing = new MediaVo();
existing.setUrl("https://oss/2.png");
destination.add(existing);
List<MediaVo> merged = PlayClerkUserInfoServiceImpl.mergeLegacyAlbum(legacy, destination);
assertThat(merged).hasSize(2);
assertThat(merged.stream().map(MediaVo::getUrl))
.containsExactlyInAnyOrder("https://oss/2.png", "https://oss/1.png");
}
}

View File

@@ -0,0 +1,143 @@
package com.starry.admin.modules.weichat.controller;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
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.starry.admin.common.conf.ThreadLocalRequestDetail;
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.IPlayClerkMediaAssetService;
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.clerk.MediaOrderRequest;
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
import com.starry.admin.modules.weichat.service.MediaUploadService;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@ExtendWith(MockitoExtension.class)
public class WxClerkMediaControllerTest {
@Mock
private MediaUploadService mediaUploadService;
@Mock
private IPlayMediaService mediaService;
@Mock
private IPlayClerkMediaAssetService clerkMediaAssetService;
@InjectMocks
private WxClerkMediaController controller;
private MockMvc mockMvc;
private final ObjectMapper objectMapper = new ObjectMapper();
private PlayClerkUserInfoEntity clerk;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
clerk = new PlayClerkUserInfoEntity();
clerk.setId("clerk-1");
clerk.setTenantId("tenant-1");
ThreadLocalRequestDetail.setRequestDetail(clerk);
}
@AfterEach
void tearDown() {
ThreadLocalRequestDetail.remove();
}
@Test
void uploadShouldReturnMediaVo() throws Exception {
MediaVo vo = new MediaVo();
vo.setId("media-1");
vo.setUsage(ClerkMediaUsage.PROFILE.getCode());
when(mediaUploadService.upload(any(), eq(clerk), eq(ClerkMediaUsage.PROFILE))).thenReturn(vo);
MockMultipartFile file = new MockMultipartFile("file", "avatar.png", "image/png", new byte[] {1, 2});
mockMvc.perform(multipart("/wx/clerk/media/upload")
.file(file)
.param("usage", ClerkMediaUsage.PROFILE.getCode()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.id").value("media-1"));
}
@Test
void updateOrderShouldDelegateToService() throws Exception {
MediaOrderRequest request = new MediaOrderRequest();
request.setUsage(ClerkMediaUsage.PROFILE.getCode());
request.setMediaIds(Arrays.asList("m1", "m2"));
mockMvc.perform(put("/wx/clerk/media/order")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
verify(clerkMediaAssetService).submitWithOrder(eq("clerk-1"), eq(ClerkMediaUsage.PROFILE),
eq(Arrays.asList("m1", "m2")));
}
@Test
void listApprovedShouldReturnAssemblerOutput() throws Exception {
PlayClerkMediaAssetEntity asset = new PlayClerkMediaAssetEntity();
asset.setId("asset-1");
asset.setClerkId("clerk-1");
asset.setUsage(ClerkMediaUsage.PROFILE.getCode());
asset.setMediaId("media-1");
asset.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
asset.setCreatedTime(java.sql.Timestamp.valueOf(LocalDateTime.now()));
when(clerkMediaAssetService.listByState(eq("clerk-1"), eq(ClerkMediaUsage.PROFILE),
eq(Collections.singletonList(ClerkMediaReviewState.APPROVED)))).thenReturn(
Collections.singletonList(asset));
PlayMediaEntity media = new PlayMediaEntity();
media.setId("media-1");
media.setUrl("https://oss/mock.png");
when(mediaService.listByIds(Collections.singletonList("media-1")))
.thenReturn(Collections.singletonList(media));
mockMvc.perform(get("/wx/clerk/media/approved"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data", hasSize(1)))
.andExpect(jsonPath("$.data[0].url").value("https://oss/mock.png"));
}
@Test
void deleteShouldInvokeServices() throws Exception {
mockMvc.perform(delete("/wx/clerk/media/{id}", "media-77"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
verify(clerkMediaAssetService).softDelete("clerk-1", "media-77");
verify(mediaService).softDelete(MediaOwnerType.CLERK, "clerk-1", "media-77");
}
}

View File

@@ -0,0 +1,138 @@
package com.starry.admin.modules.weichat.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.oss.service.IOssFileService;
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.IPlayClerkMediaAssetService;
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.clerk.MediaVo;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
@ExtendWith(MockitoExtension.class)
class MediaUploadServiceTest {
@Mock
private IOssFileService ossFileService;
@Mock
private IPlayMediaService mediaService;
@Mock
private IPlayClerkMediaAssetService clerkMediaAssetService;
private MediaUploadService service;
@BeforeEach
void setUp() {
service = new MediaUploadService(ossFileService, mediaService, clerkMediaAssetService);
}
@Test
@DisplayName("上传图片时应创建媒资与草稿资产,并返回完整的 MediaVo")
void uploadImageCreatesMediaAndAsset() throws Exception {
PlayClerkUserInfoEntity clerk = buildClerk();
MultipartFile file = buildImageFile("avatar.png", "image/png");
when(mediaService.normalizeAndSave(any())).thenAnswer(invocation -> invocation.getArgument(0));
when(mediaService.updateById(any())).thenReturn(true);
when(ossFileService.upload(any(), eq(clerk.getTenantId()), any()))
.thenReturn("https://oss.mock/avatar.png");
PlayClerkMediaAssetEntity asset = new PlayClerkMediaAssetEntity();
asset.setId("asset-1");
asset.setUsage(ClerkMediaUsage.PROFILE.getCode());
asset.setReviewState("draft");
when(clerkMediaAssetService.linkDraftAsset(any(), any(), any(), any())).thenAnswer(invocation -> {
asset.setMediaId(invocation.getArgument(2));
return asset;
});
MediaVo result = service.upload(file, clerk, null);
assertThat(result).isNotNull();
assertThat(result.getUrl()).isEqualTo("https://oss.mock/avatar.png");
assertThat(result.getUsage()).isEqualTo(ClerkMediaUsage.PROFILE.getCode());
assertThat(result.getAssetId()).isEqualTo("asset-1");
ArgumentCaptor<PlayMediaEntity> mediaCaptor = ArgumentCaptor.forClass(PlayMediaEntity.class);
verify(mediaService).normalizeAndSave(mediaCaptor.capture());
PlayMediaEntity persisted = mediaCaptor.getValue();
assertThat(persisted.getOwnerType()).isEqualTo(MediaOwnerType.CLERK);
assertThat(persisted.getOwnerId()).isEqualTo(clerk.getId());
assertThat(persisted.getMetadata()).containsKeys("originalFilename", "contentType", "uploadTraceId");
verify(clerkMediaAssetService).linkDraftAsset(
eq(clerk.getTenantId()),
eq(clerk.getId()),
eq(persisted.getId()),
eq(ClerkMediaUsage.PROFILE));
}
@Test
@DisplayName("不支援的檔案格式應回報錯誤,且不呼叫外部服務")
void uploadRejectsUnsupportedFormat() {
PlayClerkUserInfoEntity clerk = buildClerk();
MultipartFile invalidFile = new MockMultipartFile("file", "note.txt", "text/plain", "oops".getBytes());
assertThatThrownBy(() -> service.upload(invalidFile, clerk, ClerkMediaUsage.PROFILE))
.isInstanceOf(CustomException.class)
.hasMessage("不支持的文件格式");
verify(mediaService, never()).normalizeAndSave(any());
verify(ossFileService, never()).upload(any(), any(), any());
verify(clerkMediaAssetService, never()).linkDraftAsset(any(), any(), any(), any());
}
@Test
@DisplayName("缺少檔案或店員資訊時應回報錯誤")
void uploadRejectsMissingInputs() {
PlayClerkUserInfoEntity clerk = buildClerk();
MultipartFile emptyFile = new MockMultipartFile("file", new byte[0]);
assertThatThrownBy(() -> service.upload(emptyFile, clerk, ClerkMediaUsage.PROFILE))
.isInstanceOf(CustomException.class)
.hasMessage("请选择要上传的文件");
MultipartFile file = new MockMultipartFile("file", "a.png", "image/png", new byte[] {1});
assertThatThrownBy(() -> service.upload(file, null, ClerkMediaUsage.PROFILE))
.isInstanceOf(CustomException.class)
.hasMessage("店员信息不存在");
}
private MultipartFile buildImageFile(String filename, String contentType) throws IOException {
BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(image, "png", baos);
return new MockMultipartFile("file", filename, contentType, baos.toByteArray());
}
}
private PlayClerkUserInfoEntity buildClerk() {
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
entity.setId("clerk-1");
entity.setTenantId("tenant-1");
return entity;
}
}