382 lines
24 KiB
Java
382 lines
24 KiB
Java
package com.baiyue.file.service;
|
|
|
|
import com.baiyue.common.core.exception.BizException;
|
|
import com.baiyue.file.entity.FileObjectEntity;
|
|
import com.baiyue.file.mapper.FileObjectMapper;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.mockito.*;
|
|
import org.springframework.mock.web.MockMultipartFile;
|
|
|
|
import java.util.List;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
|
import static org.assertj.core.api.Assertions.*;
|
|
import static org.mockito.ArgumentMatchers.*;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
class InternalBusinessFileServiceTest {
|
|
@Mock FileObjectMapper files;
|
|
@Mock BusinessFileStorage storage;
|
|
@InjectMocks InternalBusinessFileService service;
|
|
|
|
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
|
|
|
|
@Test void customsDocumentUploadPersistsExactBusinessOwnership() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "invoice.txt", "text/plain", "invoice".getBytes());
|
|
when(storage.store(10001L, "41", file, BusinessFileCategory.CUSTOMS_DOCUMENT)).thenReturn(new BusinessFileStorage.Stored(
|
|
"business-files/10001/41/key.txt", "text/plain", 7, "a".repeat(64), "invoice.txt"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(71L); return 1;
|
|
});
|
|
|
|
var result = service.upload(10001L, "CUSTOMS_DOCUMENT", "41", 7L, file);
|
|
|
|
assertThat(result.fileId()).isEqualTo(71L);
|
|
assertThat(result.businessType()).isEqualTo("CUSTOMS_DOCUMENT");
|
|
assertThat(result.businessId()).isEqualTo("41");
|
|
assertThat(result.contentType()).isEqualTo("text/plain");
|
|
}
|
|
|
|
@Test void platformContainerAndLoadingDocumentsSupportZeroTenantScope() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "customs.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
|
when(storage.store(eq(0L), anyString(), same(file), any(BusinessFileCategory.class))).thenAnswer(invocation ->
|
|
new BusinessFileStorage.Stored("business-files/0/" + invocation.getArgument(1) + "/customs.pdf",
|
|
"application/pdf", file.getSize(), "b".repeat(64), "customs.pdf"));
|
|
when(storage.store(0L, "51", file, BusinessFileCategory.OVERSEAS_UNLOAD_PHOTO)).thenReturn(new BusinessFileStorage.Stored(
|
|
"business-files/0/51/unload.jpg", "image/jpeg", file.getSize(), "c".repeat(64), "unload.jpg"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(81L); return 1;
|
|
});
|
|
|
|
var clearance = service.upload(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41", 7L, file);
|
|
var customs = service.upload(0L, "LOADING_CUSTOMS_DECLARATION", "51", 7L, file);
|
|
var deliveryOrder = service.upload(0L, "CONTAINER_DELIVERY_ORDER", "41", 7L, file);
|
|
var pickupOrder = service.upload(0L, "CONTAINER_PICKUP_ORDER", "41", 7L, file);
|
|
var unloadPhoto = service.upload(0L, "OVERSEAS_UNLOAD_PHOTO", "51", 7L, file);
|
|
|
|
assertThat(clearance).extracting(InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId).containsExactly(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41");
|
|
assertThat(customs).extracting(InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId).containsExactly(0L, "LOADING_CUSTOMS_DECLARATION", "51");
|
|
assertThat(deliveryOrder.businessType()).isEqualTo("CONTAINER_DELIVERY_ORDER");
|
|
assertThat(pickupOrder.businessType()).isEqualTo("CONTAINER_PICKUP_ORDER");
|
|
assertThat(unloadPhoto).extracting(InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId)
|
|
.containsExactly(0L, "OVERSEAS_UNLOAD_PHOTO", "51");
|
|
verify(storage).store(0L, "41", file, BusinessFileCategory.CONTAINER_CLEARANCE_DOCUMENT);
|
|
verify(storage).store(0L, "51", file, BusinessFileCategory.LOADING_CUSTOMS_DECLARATION);
|
|
verify(storage).store(0L, "41", file, BusinessFileCategory.CONTAINER_DELIVERY_ORDER);
|
|
verify(storage).store(0L, "41", file, BusinessFileCategory.CONTAINER_PICKUP_ORDER);
|
|
verify(storage).store(0L, "51", file, BusinessFileCategory.OVERSEAS_UNLOAD_PHOTO);
|
|
assertThatThrownBy(() -> service.upload(11L, "OVERSEAS_UNLOAD_PHOTO", "51", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("卸柜照片必须归属平台");
|
|
}
|
|
|
|
@Test void oceanBookingDocumentUsesPlatformAndBookingOwnership() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "booking.xlsx",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx".getBytes());
|
|
when(storage.store(0L, "61", file, BusinessFileCategory.OCEAN_BOOKING_DOCUMENT))
|
|
.thenReturn(new BusinessFileStorage.Stored("business-files/0/61/booking",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
file.getSize(), "f".repeat(64), "booking.xlsx"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(111L); return 1;
|
|
});
|
|
|
|
var metadata = service.upload(0L, "OCEAN_BOOKING_DOCUMENT", "61", 7L, file);
|
|
|
|
assertThat(metadata).extracting(InternalBusinessFileService.Metadata::ownerScope,
|
|
InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId)
|
|
.containsExactly("PLATFORM", 0L, "OCEAN_BOOKING_DOCUMENT", "61");
|
|
assertThatThrownBy(() -> service.upload(10001L, "OCEAN_BOOKING_DOCUMENT", "61", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("必须归属平台");
|
|
}
|
|
|
|
@Test void oceanBookingDocumentRejectsWrongCustomerBookingAndTruckOwnership() {
|
|
FileObjectEntity value = new FileObjectEntity(); value.setId(111L); value.setTenantId(0L);
|
|
value.setBusinessType("OCEAN_BOOKING_DOCUMENT"); value.setBusinessId("61");
|
|
value.setOriginalName("booking.xlsx"); value.setObjectKey("business-files/0/61/booking");
|
|
value.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
value.setSha256("f".repeat(64)); value.setSizeBytes(8L);
|
|
when(files.selectOne(any())).thenReturn(value, null, null);
|
|
|
|
assertThat(service.metadata(0L, "OCEAN_BOOKING_DOCUMENT", "61", 111L).ownerScope())
|
|
.isEqualTo("PLATFORM");
|
|
assertThatThrownBy(() -> service.metadata(10002L, "OCEAN_BOOKING_DOCUMENT", "61", 111L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("必须归属平台");
|
|
assertThatThrownBy(() -> service.metadata(0L, "OCEAN_BOOKING_DOCUMENT", "62", 111L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(10001L, "TRUCK_APPOINTMENT_ATTACHMENT", "61", 111L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
}
|
|
|
|
@Test void bookingFileScopeRemainsPlatformRegardlessOfOptionalCustomerSnapshot() {
|
|
FileObjectEntity value = new FileObjectEntity(); value.setId(111L); value.setTenantId(0L);
|
|
value.setBusinessType("OCEAN_BOOKING_DOCUMENT"); value.setBusinessId("61");
|
|
value.setOriginalName("booking.bin"); value.setObjectKey("business-files/0/61/object");
|
|
when(files.selectOne(any())).thenReturn(value);
|
|
when(storage.read(value.getObjectKey())).thenReturn(new byte[]{1, 2});
|
|
// Customer snapshot is deliberately absent from File commands before and after clearing it.
|
|
assertThat(service.metadata(0L, "OCEAN_BOOKING_DOCUMENT", "61", 111L).ownerScope()).isEqualTo("PLATFORM");
|
|
assertThat(service.download(0L, "OCEAN_BOOKING_DOCUMENT", "61", 111L).content()).containsExactly(1, 2);
|
|
assertThat(service.metadata(0L, "OCEAN_BOOKING_DOCUMENT", "61", 111L).fileId()).isEqualTo(111L);
|
|
assertThatThrownBy(() -> service.download(10001L, "OCEAN_BOOKING_DOCUMENT", "61", 111L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("必须归属平台");
|
|
verify(storage, times(1)).read(value.getObjectKey());
|
|
}
|
|
|
|
@Test void rejectsNegativeTenantAndInvalidOwnershipInputs() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "a.txt", "text/plain", "x".getBytes());
|
|
assertThatThrownBy(() -> service.upload(-1L, "CONTAINER_CLEARANCE_DOCUMENT", "41", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属");
|
|
assertThatThrownBy(() -> service.upload(0L, "CONTAINER_CLEARANCE_DOCUMENT", "0", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("业务ID");
|
|
assertThatThrownBy(() -> service.upload(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41", 0L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("操作人");
|
|
verifyNoInteractions(files, storage);
|
|
}
|
|
|
|
@Test void overseasRelabelPhotoPersistsExactTaskOwnership() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "relabel.jpg", "image/jpeg", "photo".getBytes());
|
|
when(storage.store(100L, "81", file, BusinessFileCategory.OVERSEAS_RELABEL_PHOTO)).thenReturn(new BusinessFileStorage.Stored(
|
|
"business-files/100/81/relabel.jpg", "image/jpeg", file.getSize(), "d".repeat(64), "relabel.jpg"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(91L); return 1;
|
|
});
|
|
|
|
var result = service.upload(100L, "OVERSEAS_RELABEL_PHOTO", "81", 7L, file);
|
|
|
|
assertThat(result).extracting(InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId)
|
|
.containsExactly(100L, "OVERSEAS_RELABEL_PHOTO", "81");
|
|
}
|
|
|
|
@Test void loadingCustomsSourceReceiptAndResultAreCustomerOwnedArbitraryFormatObjects() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "资料包.zip", "application/zip", "zip".getBytes());
|
|
when(storage.store(eq(10001L), eq("51"), same(file), any(BusinessFileCategory.class)))
|
|
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/51/key", "application/zip",
|
|
file.getSize(), "e".repeat(64), "资料包.zip"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(101L); return 1;
|
|
});
|
|
|
|
var source = service.upload(10001L, "LOADING_LIST_CUSTOMS_SOURCE", "51", 7L, file);
|
|
var receipt = service.upload(10001L, "LOADING_LIST_CUSTOMS_RECEIPT", "51", 7L, file);
|
|
var result = service.upload(10001L, "LOADING_LIST_CUSTOMS_RESULT", "51", 7L, file);
|
|
|
|
assertThat(source).extracting(InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId)
|
|
.containsExactly(10001L, "LOADING_LIST_CUSTOMS_SOURCE", "51");
|
|
assertThat(receipt.businessType()).isEqualTo("LOADING_LIST_CUSTOMS_RECEIPT");
|
|
assertThat(result.businessType()).isEqualTo("LOADING_LIST_CUSTOMS_RESULT");
|
|
verify(storage).store(10001L, "51", file, BusinessFileCategory.LOADING_LIST_CUSTOMS_SOURCE);
|
|
verify(storage).store(10001L, "51", file, BusinessFileCategory.LOADING_LIST_CUSTOMS_RECEIPT);
|
|
verify(storage).store(10001L, "51", file, BusinessFileCategory.LOADING_LIST_CUSTOMS_RESULT);
|
|
}
|
|
|
|
@Test void loadingCustomsCategoriesRejectPlatformOwnerScope() {
|
|
var file = new MockMultipartFile("file", "source.bin", "application/octet-stream", new byte[]{1});
|
|
|
|
assertThatThrownBy(() -> service.upload(0L, "LOADING_LIST_CUSTOMS_SOURCE", "51", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("明确客户");
|
|
assertThatThrownBy(() -> service.upload(0L, "LOADING_LIST_CUSTOMS_RECEIPT", "51", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("明确客户");
|
|
assertThatThrownBy(() -> service.metadata(0L, "LOADING_LIST_CUSTOMS_RESULT", "51", 91L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("明确客户");
|
|
verifyNoInteractions(files, storage);
|
|
}
|
|
|
|
@Test void unsupportedBusinessTypeFailsClosed() {
|
|
assertThatThrownBy(() -> service.upload(10001L, "ARBITRARY", "41", 7L,
|
|
new MockMultipartFile("file", "a.txt", "text/plain", "x".getBytes())))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("不支持");
|
|
verifyNoInteractions(files, storage);
|
|
}
|
|
|
|
@Test void orderBatchSourceAndErrorReportUseCustomerAndBatchNumberOwnership() {
|
|
String batchNo = "OBI-0123456789ABCDEF0123";
|
|
var file = new MockMultipartFile("file", "orders.xlsx",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
new byte[]{0x50, 0x4b, 0x03, 0x04, 1});
|
|
when(storage.store(eq(10001L), eq(batchNo), same(file), any(BusinessFileCategory.class)))
|
|
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/" + batchNo + "/object.xlsx",
|
|
file.getContentType(), file.getSize(), "9".repeat(64), file.getOriginalFilename()));
|
|
AtomicLong ids = new AtomicLong(300);
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(ids.incrementAndGet());
|
|
return 1;
|
|
});
|
|
|
|
var source = service.upload(10001L, "ORDER_BATCH_IMPORT_SOURCE", batchNo, 7L, file);
|
|
var error = service.upload(10001L, "ORDER_BATCH_IMPORT_ERROR_REPORT", batchNo, 7L, file);
|
|
|
|
assertThat(source).extracting(InternalBusinessFileService.Metadata::ownerScope,
|
|
InternalBusinessFileService.Metadata::tenantId, InternalBusinessFileService.Metadata::businessId)
|
|
.containsExactly("CUSTOMER", 10001L, batchNo);
|
|
assertThat(error.businessType()).isEqualTo("ORDER_BATCH_IMPORT_ERROR_REPORT");
|
|
verify(storage).store(10001L, batchNo, file, BusinessFileCategory.ORDER_BATCH_IMPORT_SOURCE);
|
|
verify(storage).store(10001L, batchNo, file, BusinessFileCategory.ORDER_BATCH_IMPORT_ERROR_REPORT);
|
|
}
|
|
|
|
@Test void orderBatchFilesRejectPlatformOwnerMalformedBatchAndWrongCompositeOwnership() {
|
|
String batchNo = "OBI-0123456789ABCDEF0123";
|
|
var file = new MockMultipartFile("file", "orders.xlsx",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
new byte[]{0x50, 0x4b, 0x03, 0x04});
|
|
assertThatThrownBy(() -> service.upload(0L, "ORDER_BATCH_IMPORT_SOURCE", batchNo, 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("明确客户");
|
|
assertThatThrownBy(() -> service.upload(10001L, "ORDER_BATCH_IMPORT_SOURCE", "41", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("批量导入业务编号");
|
|
|
|
FileObjectEntity value = new FileObjectEntity(); value.setId(401L); value.setTenantId(10001L);
|
|
value.setBusinessType("ORDER_BATCH_IMPORT_SOURCE"); value.setBusinessId(batchNo);
|
|
value.setOriginalName("orders.xlsx"); value.setObjectKey("business-files/10001/" + batchNo + "/object.xlsx");
|
|
value.setContentType(file.getContentType()); value.setSha256("8".repeat(64)); value.setSizeBytes(file.getSize());
|
|
when(files.selectOne(any())).thenReturn(value, null, null, null);
|
|
|
|
assertThat(service.metadata(10001L, "ORDER_BATCH_IMPORT_SOURCE", batchNo, 401L).fileId()).isEqualTo(401L);
|
|
assertThatThrownBy(() -> service.metadata(10002L, "ORDER_BATCH_IMPORT_SOURCE", batchNo, 401L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(10001L, "ORDER_BATCH_IMPORT_ERROR_REPORT", batchNo, 401L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(10001L, "ORDER_BATCH_IMPORT_SOURCE",
|
|
"OBI-FFFFFFFFFFFFFFFFFFFF", 401L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
}
|
|
|
|
@Test void downloadUsesValidatedMetadataObjectKey() {
|
|
FileObjectEntity value = new FileObjectEntity(); value.setId(71L); value.setTenantId(10001L);
|
|
value.setBusinessType("CUSTOMS_RECEIPT"); value.setBusinessId("61"); value.setOriginalName("release.txt");
|
|
value.setObjectKey("business-files/10001/61/release.txt"); value.setContentType("text/plain");
|
|
when(files.selectOne(any())).thenReturn(value);
|
|
when(storage.read(value.getObjectKey())).thenReturn("released".getBytes());
|
|
|
|
var result = service.download(10001L, "CUSTOMS_RECEIPT", "61", 71L);
|
|
|
|
assertThat(result.fileName()).isEqualTo("release.txt");
|
|
assertThat(result.content()).isEqualTo("released".getBytes());
|
|
verify(storage).read(value.getObjectKey());
|
|
}
|
|
|
|
@Test void platformMetadataAndDownloadRequireExactCompositeOwnership() {
|
|
FileObjectEntity value = new FileObjectEntity(); value.setId(91L); value.setTenantId(0L);
|
|
value.setBusinessType("LOADING_CUSTOMS_DECLARATION"); value.setBusinessId("51");
|
|
value.setOriginalName("declaration.pdf"); value.setObjectKey("business-files/0/51/declaration.pdf");
|
|
value.setContentType("application/pdf"); value.setSha256("c".repeat(64)); value.setSizeBytes(8L);
|
|
when(files.selectOne(any())).thenReturn(value, value, null, null, null);
|
|
when(storage.read(value.getObjectKey())).thenReturn("document".getBytes());
|
|
|
|
assertThat(service.metadata(0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L).fileName())
|
|
.isEqualTo("declaration.pdf");
|
|
assertThat(service.download(0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L).content())
|
|
.isEqualTo("document".getBytes());
|
|
assertThatThrownBy(() -> service.metadata(0L, "CONTAINER_CLEARANCE_DOCUMENT", "51", 91L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(0L, "LOADING_CUSTOMS_DECLARATION", "52", 91L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(0L, "LOADING_CUSTOMS_DECLARATION", "51", 92L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
verify(storage).read(value.getObjectKey());
|
|
}
|
|
|
|
@Test void concurrentRepeatedUploadsCreateDistinctImmutableObjectsForFirstlegToVersion() throws Exception {
|
|
MockMultipartFile file = new MockMultipartFile("file", "booking.bin",
|
|
"application/x-booking", new byte[]{1, 2, 3});
|
|
when(storage.store(0L, "61", file, BusinessFileCategory.OCEAN_BOOKING_DOCUMENT))
|
|
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/61/object",
|
|
"application/x-booking", 3, "a".repeat(64), "booking.bin"));
|
|
AtomicLong ids = new AtomicLong(200);
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(ids.incrementAndGet());
|
|
return 1;
|
|
});
|
|
var executor = Executors.newFixedThreadPool(2);
|
|
try {
|
|
var firstFuture = executor.submit(() ->
|
|
service.upload(0L, "OCEAN_BOOKING_DOCUMENT", "61", 7L, file));
|
|
var secondFuture = executor.submit(() ->
|
|
service.upload(0L, "OCEAN_BOOKING_DOCUMENT", "61", 7L, file));
|
|
|
|
var first = firstFuture.get();
|
|
var second = secondFuture.get();
|
|
assertThat(first.fileId()).isNotEqualTo(second.fileId());
|
|
assertThat(first.businessId()).isEqualTo("61");
|
|
assertThat(second.businessId()).isEqualTo("61");
|
|
verify(files, times(2)).insert(any(FileObjectEntity.class));
|
|
verify(files, never()).updateById(any(FileObjectEntity.class));
|
|
} finally {
|
|
executor.shutdownNow();
|
|
}
|
|
}
|
|
|
|
@Test void inboundHoldEvidenceIsCustomerOwnedAndScopedToInboundId() {
|
|
MockMultipartFile file = new MockMultipartFile("file", "hold.pdf", "application/pdf", new byte[]{1, 2});
|
|
when(storage.store(10001L, "61", file, BusinessFileCategory.INBOUND_HOLD_EVIDENCE))
|
|
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/61/hold.pdf",
|
|
"application/pdf", 2, "a".repeat(64), "hold.pdf"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(301L);
|
|
return 1;
|
|
});
|
|
|
|
var result = service.upload(10001L, "INBOUND_HOLD_EVIDENCE", "61", 7L, file);
|
|
|
|
assertThat(result.fileId()).isEqualTo(301L);
|
|
assertThat(result.ownerScope()).isEqualTo("CUSTOMER");
|
|
assertThat(result.businessId()).isEqualTo("61");
|
|
assertThatThrownBy(() -> service.upload(0L, "INBOUND_HOLD_EVIDENCE", "61", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("明确客户");
|
|
}
|
|
|
|
@Test void overseasExceptionEvidenceUsesExactCustomerAndExceptionOwnership() {
|
|
MockMultipartFile file = new MockMultipartFile(
|
|
"file", "异常处理证据.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
|
when(storage.store(10001L, "701", file, BusinessFileCategory.OVERSEAS_EXCEPTION_EVIDENCE))
|
|
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/701/evidence.pdf",
|
|
"application/pdf", file.getSize(), "b".repeat(64), "异常处理证据.pdf"));
|
|
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
|
|
((FileObjectEntity) invocation.getArgument(0)).setId(801L);
|
|
return 1;
|
|
});
|
|
|
|
var result = service.upload(10001L, "OVERSEAS_EXCEPTION_EVIDENCE", "701", 7L, file);
|
|
|
|
assertThat(result).extracting(InternalBusinessFileService.Metadata::ownerScope,
|
|
InternalBusinessFileService.Metadata::tenantId,
|
|
InternalBusinessFileService.Metadata::businessType,
|
|
InternalBusinessFileService.Metadata::businessId)
|
|
.containsExactly("CUSTOMER", 10001L, "OVERSEAS_EXCEPTION_EVIDENCE", "701");
|
|
verify(storage).store(10001L, "701", file, BusinessFileCategory.OVERSEAS_EXCEPTION_EVIDENCE);
|
|
assertThatThrownBy(() -> service.upload(0L, "OVERSEAS_EXCEPTION_EVIDENCE", "701", 7L, file))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("明确客户");
|
|
}
|
|
|
|
@Test void overseasExceptionEvidenceMetadataFailsClosedForEveryOwnershipMismatch() {
|
|
FileObjectEntity value = new FileObjectEntity();
|
|
value.setId(801L); value.setTenantId(10001L);
|
|
value.setBusinessType("OVERSEAS_EXCEPTION_EVIDENCE"); value.setBusinessId("701");
|
|
value.setOriginalName("evidence.pdf"); value.setObjectKey("business-files/10001/701/evidence.pdf");
|
|
value.setContentType("application/pdf"); value.setSha256("b".repeat(64)); value.setSizeBytes(8L);
|
|
when(files.selectOne(any())).thenReturn(value, null, null, null);
|
|
|
|
assertThat(service.metadata(10001L, "OVERSEAS_EXCEPTION_EVIDENCE", "701", 801L).fileId())
|
|
.isEqualTo(801L);
|
|
assertThatThrownBy(() -> service.metadata(10002L, "OVERSEAS_EXCEPTION_EVIDENCE", "701", 801L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(10001L, "OVERSEAS_EXCEPTION_EVIDENCE", "702", 801L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
assertThatThrownBy(() -> service.metadata(10001L, "INBOUND_HOLD_EVIDENCE", "701", 801L))
|
|
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
|
}
|
|
|
|
}
|