This commit is contained in:
2026-08-07 19:23:40 +08:00
parent 37fec1e012
commit c698290106
39 changed files with 1897 additions and 13 deletions
@@ -0,0 +1,83 @@
package com.baiyue.file.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.file.service.ProductImageService;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class ProductImageControllerContractTest {
@Test
void merchantRouteUsesMultipartAndOrderPermissions() throws Exception {
RequestMapping mapping = MerchantProductImageController.class.getAnnotation(RequestMapping.class);
assertThat(mapping.value()).contains("/api/merchant/v1/files/product-images");
var upload = MerchantProductImageController.class.getDeclaredMethod(
"upload", AuthenticatedUser.class, org.springframework.web.multipart.MultipartFile.class);
assertThat(upload.getAnnotation(PostMapping.class).consumes())
.contains(MediaType.MULTIPART_FORM_DATA_VALUE);
assertThat(upload.getAnnotation(PreAuthorize.class).value())
.contains("merchant:order:create").contains("merchant:order:edit");
}
@Test
void merchantUploadAndDownloadAlwaysUseAuthenticatedCustomer() {
ProductImageService service = mock(ProductImageService.class);
MerchantProductImageController controller = new MerchantProductImageController();
ReflectionTestUtils.setField(controller, "images", service);
var user = merchant(10001L);
var file = new MockMultipartFile("file", "product.png", "image/png", new byte[] {1});
var uploaded = new ProductImageService.UploadResult(31L, "product.png", "image/png", 1L);
when(service.upload(10001L, 2L, file)).thenReturn(uploaded);
when(service.download(10001L, 31L)).thenReturn(
new ProductImageService.Download("product.png", "image/png", new byte[] {1}));
assertThat(controller.upload(user, file).data()).isEqualTo(uploaded);
assertThat(controller.download(user, 31L).getBody()).containsExactly(1);
verify(service).upload(10001L, 2L, file);
verify(service).download(10001L, 31L);
}
@Test
void internalValidationRequiresSharedTokenAndForwardsCustomerScope() {
ProductImageService service = mock(ProductImageService.class);
InternalProductImageController controller = new InternalProductImageController();
ReflectionTestUtils.setField(controller, "images", service);
ReflectionTestUtils.setField(controller, "internalToken", "token");
controller.initializeToken();
var metadata = new ProductImageService.UploadResult(31L, "product.png", "image/png", 1L);
when(service.validate(10001L, 31L)).thenReturn(metadata);
assertThat(controller.validate("token", 10001L, 31L).data()).isEqualTo(metadata);
assertThatThrownBy(() -> controller.validate("wrong", 10001L, 31L))
.isInstanceOf(BizException.class).hasMessageContaining("凭证无效");
verify(service).validate(10001L, 31L);
}
@Test
void rejectsPlatformOrMalformedIdentityBeforeFileAccess() {
ProductImageService service = mock(ProductImageService.class);
MerchantProductImageController controller = new MerchantProductImageController();
ReflectionTestUtils.setField(controller, "images", service);
var platform = new AuthenticatedUser(1L, 0L, 0L, 1L, "admin", "PLATFORM", "WEB", Set.of());
var file = new MockMultipartFile("file", "product.png", "image/png", new byte[] {1});
assertThatThrownBy(() -> controller.upload(platform, file)).isInstanceOf(BizException.class);
}
private static AuthenticatedUser merchant(long tenantId) {
return new AuthenticatedUser(2L, tenantId, tenantId, 2L, "merchant", "MERCHANT", "WEB", Set.of());
}
}
@@ -0,0 +1,62 @@
package com.baiyue.file.service;
import com.baiyue.common.core.exception.BizException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ProductImageStorageTest {
@TempDir Path root;
@Test
void storesValidatedImageUnderCustomerDirectoryAndReadsItBack() throws Exception {
ProductImageStorage storage = storage();
byte[] content = png();
var stored = storage.store(10001L,
new MockMultipartFile("file", "../product.png", "image/png", content));
assertThat(stored.objectKey()).startsWith("product-images/10001/").endsWith(".png");
assertThat(stored.originalName()).isEqualTo("product.png");
assertThat(storage.read(stored.objectKey())).isEqualTo(content);
}
@Test
void rejectsEmptyOversizedForgedAndMismatchedFiles() throws Exception {
ProductImageStorage storage = storage();
assertThatThrownBy(() -> storage.store(10001L,
new MockMultipartFile("file", "empty.png", "image/png", new byte[0])))
.isInstanceOf(BizException.class).hasMessageContaining("不能为空");
assertThatThrownBy(() -> storage.store(10001L,
new MockMultipartFile("file", "large.png", "image/png", new byte[5 * 1024 * 1024 + 1])))
.isInstanceOf(BizException.class).hasMessageContaining("5MB");
assertThatThrownBy(() -> storage.store(10001L,
new MockMultipartFile("file", "fake.jpg", "image/jpeg", "not-an-image".getBytes())))
.isInstanceOf(BizException.class).hasMessageContaining("有效图片");
assertThatThrownBy(() -> storage.store(10001L,
new MockMultipartFile("file", "mismatch.jpg", "image/jpeg", png())))
.isInstanceOf(BizException.class).hasMessageContaining("不一致");
}
private ProductImageStorage storage() {
ProductImageStorage storage = new ProductImageStorage();
ReflectionTestUtils.setField(storage, "configuredRoot", root);
storage.initializeRoot();
return storage;
}
private static byte[] png() throws Exception {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB), "png", output);
return output.toByteArray();
}
}