fix(file): bind loading evidence to cabinet

This commit is contained in:
2026-08-31 17:33:11 +08:00
parent ba8e3595e4
commit 1288c8937c
10 changed files with 359 additions and 57 deletions
@@ -0,0 +1,26 @@
package com.baiyue.file.config;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class LoadingEvidenceFileContractTest {
@Test
void freezesPhysicalContainerOwnershipWithoutInboundCompatibilityPath() throws Exception {
String yaml = Files.readString(Path.of("src/main/resources/openapi/loading-evidence-v1.yaml"));
assertThat(yaml)
.contains("/api/platform/v1/files/loading-evidence/{containerBookingId}:")
.contains("/internal/file/v1/loading-evidence/{containerBookingId}:")
.contains("ownerScope: {type: string, enum: [PLATFORM]}")
.contains("tenantId: {type: string, enum: ['0']}")
.contains("description: 物理containerBookingId")
.contains("LOADING_PHOTO_STEP_1", "LOADING_PHOTO_STEP_2", "LOADING_PHOTO_STEP_3",
"LOADING_PHOTO_STEP_4", "LOADING_SEAL")
.contains("x-required-any-permission:", "platform:firstleg:pda:work", "Cache-Control固定no-store")
.doesNotContain("/{tenantId}/{inboundId}", "businessId=inboundId");
}
}
@@ -21,32 +21,32 @@ class InternalLoadingEvidenceControllerTest {
@Resource InternalLoadingEvidenceController controller;
@Test void downloadsOwnedEvidenceBehindInternalToken() {
when(service.download(81L, 91L, 101L)).thenReturn(
when(service.download(91L, 101L)).thenReturn(
new LoadingEvidenceService.Download("door.jpg", "image/jpeg", new byte[]{1, 2, 3}));
var response = controller.download("internal-token", 81L, 91L, 101L);
var response = controller.download("internal-token", 91L, 101L);
assertThat(response.getBody()).containsExactly(1, 2, 3);
assertThat(response.getHeaders().getContentType().toString()).isEqualTo("image/jpeg");
assertThat(response.getHeaders().getCacheControl()).contains("no-store");
verify(service).download(81L, 91L, 101L);
verify(service).download(91L, 101L);
}
@Test void rejectsInvalidTokenBeforeReadingEvidence() {
assertThatThrownBy(() -> controller.download("wrong", 81L, 91L, 101L))
assertThatThrownBy(() -> controller.download("wrong", 91L, 101L))
.isInstanceOf(BizException.class).hasMessageContaining("凭证无效");
verifyNoInteractions(service);
}
@Test void fallsBackForInvalidOrNonImageContentTypes() {
when(service.download(81L, 91L, 101L)).thenReturn(
when(service.download(91L, 101L)).thenReturn(
new LoadingEvidenceService.Download("door.jpg", "not a/media/type", new byte[]{1}));
when(service.download(81L, 91L, 102L)).thenReturn(
when(service.download(91L, 102L)).thenReturn(
new LoadingEvidenceService.Download("door.txt", "text/plain", new byte[]{2}));
assertThat(controller.download("internal-token", 81L, 91L, 101L).getHeaders().getContentType())
assertThat(controller.download("internal-token", 91L, 101L).getHeaders().getContentType())
.isEqualTo(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM);
assertThat(controller.download("internal-token", 81L, 91L, 102L).getHeaders().getContentType())
assertThat(controller.download("internal-token", 91L, 102L).getHeaders().getContentType())
.isEqualTo(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM);
}
}
@@ -0,0 +1,55 @@
package com.baiyue.file.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.file.service.LoadingEvidenceService;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
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 PlatformLoadingEvidenceControllerTest {
private final LoadingEvidenceService service = mock(LoadingEvidenceService.class);
private final PlatformLoadingEvidenceController controller = new PlatformLoadingEvidenceController();
PlatformLoadingEvidenceControllerTest() throws Exception {
var field = PlatformLoadingEvidenceController.class.getDeclaredField("service");
field.setAccessible(true);
field.set(controller, service);
}
@Test
void uploadsPlatformOwnedContainerEvidence() {
var file = new MockMultipartFile("file", "door.jpg", "image/jpeg", new byte[]{1});
when(service.upload(91L, 7L, "PHOTO_STEP_1", file)).thenReturn(
new LoadingEvidenceService.UploadResult(101L, "PLATFORM", 0L,
"LOADING_PHOTO_STEP_1", "91", "door.jpg", "image/jpeg", 1L));
var response = controller.upload(platform(), 91L, "PHOTO_STEP_1", file);
assertThat(response.data()).extracting(LoadingEvidenceService.UploadResult::ownerScope,
LoadingEvidenceService.UploadResult::tenantId, LoadingEvidenceService.UploadResult::businessId)
.containsExactly("PLATFORM", 0L, "91");
verify(service).upload(91L, 7L, "PHOTO_STEP_1", file);
}
@Test
void rejectsMerchantBeforeAccessingContainerEvidence() {
assertThatThrownBy(() -> controller.download(merchant(), 91L, 101L))
.isInstanceOf(BizException.class).hasMessageContaining("平台端身份");
}
private static AuthenticatedUser platform() {
return new AuthenticatedUser(7L, 0L, 0L, 7L, "operator", "PLATFORM", "PDA", Set.of());
}
private static AuthenticatedUser merchant() {
return new AuthenticatedUser(8L, 88L, 88L, 8L, "merchant", "MERCHANT", "WEB", Set.of());
}
}
@@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.ArgumentCaptor;
@ExtendWith(MockitoExtension.class)
class LoadingEvidenceServiceTest {
@@ -28,24 +29,36 @@ class LoadingEvidenceServiceTest {
@Test
void uploadsEvidenceWithDedicatedBusinessType() {
var multipart = new MockMultipartFile("file", "step.jpg", "image/jpeg", new byte[]{1});
when(storage.storeLoading(10, 20, multipart)).thenReturn(new MeasurementImageStorage.Stored(
"loading-evidence/10/20/a.jpg", Path.of("a.jpg"), "image/jpeg", 1, "hash", "step.jpg"));
var result = service.upload(10, 20, 7, "PHOTO_STEP_2", multipart);
when(storage.storeLoading(0, 20, multipart)).thenReturn(new MeasurementImageStorage.Stored(
"loading-evidence/0/20/a.jpg", Path.of("a.jpg"), "image/jpeg", 1, "hash", "step.jpg"));
var result = service.upload(20, 7, "PHOTO_STEP_2", multipart);
assertThat(result.businessType()).isEqualTo("LOADING_PHOTO_STEP_2");
verify(files).insert(any(FileObjectEntity.class));
assertThat(result.ownerScope()).isEqualTo("PLATFORM");
assertThat(result.tenantId()).isZero();
assertThat(result.businessId()).isEqualTo("20");
var object = ArgumentCaptor.forClass(FileObjectEntity.class);
verify(files).insert(object.capture());
assertThat(object.getValue()).extracting(FileObjectEntity::getTenantId,
FileObjectEntity::getBusinessId, FileObjectEntity::getBucketName)
.containsExactly(0L, "20", "loading-evidence");
}
@Test
void rejectsUnknownEvidenceType() {
var multipart = new MockMultipartFile("file", "x.jpg", "image/jpeg", new byte[]{1});
assertThatThrownBy(() -> service.upload(10, 20, 7, "UNKNOWN", multipart)).isInstanceOf(BizException.class);
assertThatThrownBy(() -> service.upload(20, 7, "UNKNOWN", multipart)).isInstanceOf(BizException.class);
}
@Test
void validatesTenantBoundFile() {
FileObjectEntity object = new FileObjectEntity();
object.setId(30L); object.setOriginalName("seal.jpg"); object.setBusinessType("LOADING_SEAL"); object.setContentType("image/jpeg");
object.setId(30L); object.setTenantId(0L); object.setBusinessId("20");
object.setOriginalName("seal.jpg"); object.setBusinessType("LOADING_SEAL"); object.setContentType("image/jpeg");
when(files.selectOne(any(Wrapper.class))).thenReturn(object);
assertThat(service.validate(10, 20, 30).fileId()).isEqualTo(30L);
var metadata = service.validate(20, 30);
assertThat(metadata.fileId()).isEqualTo(30L);
assertThat(metadata.ownerScope()).isEqualTo("PLATFORM");
assertThat(metadata.tenantId()).isZero();
assertThat(metadata.businessId()).isEqualTo("20");
}
}