126 lines
7.1 KiB
Java
126 lines
7.1 KiB
Java
package com.baiyue.file.service;
|
|
|
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
|
import com.baiyue.common.core.exception.BizException;
|
|
import com.baiyue.file.entity.FileObjectEntity;
|
|
import com.baiyue.file.mapper.FileObjectMapper;
|
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.io.TempDir;
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
import org.junit.jupiter.params.provider.ValueSource;
|
|
import org.springframework.mock.web.MockMultipartFile;
|
|
import org.springframework.test.util.ReflectionTestUtils;
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.nio.file.Path;
|
|
import java.util.regex.Pattern;
|
|
|
|
import static org.assertj.core.api.Assertions.*;
|
|
import static org.mockito.ArgumentMatchers.*;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
class OwnedBusinessPhotoFileTest {
|
|
private static final String LOADING = "OVERSEAS_DISPATCH_LOADING_PHOTO";
|
|
private static final String FUEL = "OVERSEAS_DISPATCH_FUEL_PHOTO";
|
|
@TempDir Path root;
|
|
|
|
@BeforeAll static void columns() {
|
|
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "owned-business-photo"), FileObjectEntity.class);
|
|
}
|
|
|
|
@ParameterizedTest @ValueSource(strings = {LOADING, FUEL})
|
|
void rejectsNonPlatformOrInvalidDispatchIdentityBeforeAnyStorage(String type) {
|
|
var files = mock(FileObjectMapper.class);
|
|
var storage = mock(BusinessFileStorage.class);
|
|
var service = service(files, storage);
|
|
var file = new MockMultipartFile("file", "photo.png", "image/png", new byte[]{1});
|
|
for (long owner : new long[]{-1, 1, 900})
|
|
assertThatThrownBy(() -> service.upload(owner, type, "71", 7, file)).isInstanceOf(BizException.class);
|
|
for (String businessId : new String[]{"0", "-1", "DS-71", "../71", ""})
|
|
assertThatThrownBy(() -> service.upload(0, type, businessId, 7, file)).isInstanceOf(BizException.class);
|
|
verifyNoInteractions(files, storage);
|
|
}
|
|
|
|
@ParameterizedTest @ValueSource(strings = {LOADING, FUEL})
|
|
void storesRealImagesButRejectsPdfSpoofedEmptyAndOversizedFiles(String type) throws Exception {
|
|
var storage = new BusinessFileStorage();
|
|
ReflectionTestUtils.setField(storage, "configuredRoot", root);
|
|
storage.initializeRoot();
|
|
var category = BusinessFileCategory.require(type);
|
|
for (String format : new String[]{"jpeg", "png"}) {
|
|
byte[] bytes = image(format);
|
|
var upload = new MockMultipartFile("file", "photo." + format, "image/" + format, bytes);
|
|
var saved = storage.store(0, "71", upload, category);
|
|
assertThat(storage.read(saved.objectKey())).isEqualTo(bytes);
|
|
}
|
|
for (var upload : new MockMultipartFile[]{
|
|
new MockMultipartFile("file", "empty.png", "image/png", new byte[0]),
|
|
new MockMultipartFile("file", "spoof.png", "image/png", "not-image".getBytes()),
|
|
new MockMultipartFile("file", "receipt.pdf", "application/pdf", "%PDF-1.7\nreceipt".getBytes())})
|
|
assertThatThrownBy(() -> storage.store(0, "71", upload, category)).isInstanceOf(BizException.class);
|
|
MultipartFile oversized = mock(MultipartFile.class);
|
|
when(oversized.isEmpty()).thenReturn(false);
|
|
when(oversized.getSize()).thenReturn(10L * 1024 * 1024 + 1);
|
|
assertThatThrownBy(() -> storage.store(0, "71", oversized, category)).isInstanceOf(BizException.class);
|
|
verify(oversized, never()).getInputStream();
|
|
}
|
|
|
|
/** 用实际Mapper查询条件核对四元组,错车、错类别和错文件必须先拒绝再读内容。 */
|
|
@ParameterizedTest @ValueSource(strings = {LOADING, FUEL})
|
|
void metadataAndDownloadRequireExactDispatchCategoryAndFile(String type) throws Exception {
|
|
var files = mock(FileObjectMapper.class);
|
|
var storage = mock(BusinessFileStorage.class);
|
|
var service = service(files, storage);
|
|
byte[] bytes = image("png");
|
|
var row = new FileObjectEntity();
|
|
row.setId(501L); row.setTenantId(0L); row.setBusinessType(type); row.setBusinessId("71");
|
|
row.setOriginalName("photo.png"); row.setContentType("image/png"); row.setSizeBytes((long) bytes.length);
|
|
row.setObjectKey("owned-photo"); row.setSha256("a".repeat(64)); row.setCreatedAt(java.time.LocalDateTime.now());
|
|
when(files.selectOne(any())).thenAnswer(call -> {
|
|
LambdaQueryWrapper<FileObjectEntity> query = call.getArgument(0);
|
|
assertThat(bound(query, "status")).isEqualTo("ACTIVE");
|
|
assertThat(bound(query, "deleted")).isEqualTo(false);
|
|
return Long.valueOf(0).equals(bound(query, "tenant_id")) && type.equals(bound(query, "business_type"))
|
|
&& "71".equals(bound(query, "business_id")) && Long.valueOf(501).equals(bound(query, "id")) ? row : null;
|
|
});
|
|
when(storage.read("owned-photo")).thenReturn(bytes);
|
|
assertThat(service.metadata(0, type, "71", 501).businessType()).isEqualTo(type);
|
|
assertThat(service.download(0, type, "71", 501).content()).isEqualTo(bytes);
|
|
clearInvocations(storage);
|
|
for (String other : new String[]{LOADING.equals(type) ? FUEL : LOADING,
|
|
"FLEET_VEHICLE_RECORD_PHOTO", "OVERSEAS_DISPATCH_VEHICLE_EVIDENCE", "DELIVERY_LOADING_PHOTO"}) {
|
|
assertThatThrownBy(() -> service.metadata(0, other, "71", 501)).isInstanceOf(BizException.class);
|
|
assertThatThrownBy(() -> service.download(0, other, "71", 501)).isInstanceOf(BizException.class);
|
|
}
|
|
assertThatThrownBy(() -> service.download(0, type, "72", 501)).isInstanceOf(BizException.class);
|
|
assertThatThrownBy(() -> service.download(0, type, "71", 502)).isInstanceOf(BizException.class);
|
|
assertThatThrownBy(() -> service.download(1, type, "71", 501)).isInstanceOf(BizException.class);
|
|
verifyNoInteractions(storage);
|
|
}
|
|
|
|
private static Object bound(LambdaQueryWrapper<FileObjectEntity> query, String column) {
|
|
var matcher = Pattern.compile("(?<![A-Za-z0-9_])" + column + " = #\\{ew\\.paramNameValuePairs\\.([^}]+)}")
|
|
.matcher(query.getSqlSegment());
|
|
assertThat(matcher.find()).as("scope column %s", column).isTrue();
|
|
return query.getParamNameValuePairs().get(matcher.group(1));
|
|
}
|
|
|
|
private static InternalBusinessFileService service(FileObjectMapper files, BusinessFileStorage storage) {
|
|
var service = new InternalBusinessFileService();
|
|
ReflectionTestUtils.setField(service, "files", files);
|
|
ReflectionTestUtils.setField(service, "storage", storage);
|
|
return service;
|
|
}
|
|
|
|
private static byte[] image(String format) throws Exception {
|
|
var bytes = new ByteArrayOutputStream();
|
|
assertThat(javax.imageio.ImageIO.write(new java.awt.image.BufferedImage(2, 2,
|
|
java.awt.image.BufferedImage.TYPE_INT_RGB), format, bytes)).isTrue();
|
|
return bytes.toByteArray();
|
|
}
|
|
}
|