提交代码
This commit is contained in:
@@ -1,14 +1,35 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.file.client.IamQualificationClient;
|
||||
import com.baiyue.file.service.LocalQualificationStorage;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
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.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("企业资质文件接口契约功能")
|
||||
class QualificationFileContractTest {
|
||||
@@ -39,4 +60,139 @@ class QualificationFileContractTest {
|
||||
.anyMatch(MediaType.MULTIPART_FORM_DATA_VALUE::equals);
|
||||
assertTrue(hasMultipartPost);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformQualificationPreviewUsesTheCustomerViewPermission() throws Exception {
|
||||
var method = PlatformQualificationFileController.class.getDeclaredMethod(
|
||||
"download", AuthenticatedUser.class, long.class, String.class);
|
||||
|
||||
assertEquals("hasAuthority('platform:customer:view')", method.getAnnotation(PreAuthorize.class).value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformReplacementIsForwardedAndCleansThePreviousFileAfterIamSucceeds() {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var controller = platformController(storage, iamClient);
|
||||
var file = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
var stored = new LocalQualificationStorage.StoredFile(Path.of("new.pdf"), "new.pdf",
|
||||
"/api/merchant/v1/files/qualifications/11111111-1111-1111-1111-111111111111.pdf");
|
||||
var oldUrl = "/api/merchant/v1/files/qualifications/22222222-2222-2222-2222-222222222222.pdf";
|
||||
when(storage.store(10001L, file)).thenReturn(stored);
|
||||
when(iamClient.submitPlatform(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new IamQualificationClient.PlatformQualificationResponse(31L, null, "PROFILE_REQUIRED", oldUrl)));
|
||||
|
||||
var response = controller.upload(platformUser(), 10001L, file, "LEGAL_PERSON_ID", null,
|
||||
LocalDate.of(2030, 12, 31), true);
|
||||
|
||||
var ordered = inOrder(storage, iamClient);
|
||||
ordered.verify(storage).store(10001L, file);
|
||||
var requestCaptor = org.mockito.ArgumentCaptor.forClass(
|
||||
IamQualificationClient.PlatformQualificationRequest.class);
|
||||
ordered.verify(iamClient).submitPlatform(eq("internal-token"), requestCaptor.capture());
|
||||
ordered.verify(storage).deleteByDocumentUrl(10001L, oldUrl);
|
||||
assertTrue(requestCaptor.getValue().replace());
|
||||
assertEquals(stored.documentUrl(), requestCaptor.getValue().documentUrl());
|
||||
assertEquals(stored.documentUrl(), response.data().documentUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformReplacementNeverDeletesTheCurrentUpload() {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var controller = platformController(storage, iamClient);
|
||||
var file = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
var currentUrl = "/api/merchant/v1/files/qualifications/11111111-1111-1111-1111-111111111111.pdf";
|
||||
var stored = new LocalQualificationStorage.StoredFile(Path.of("new.pdf"), "new.pdf", currentUrl);
|
||||
when(storage.store(10001L, file)).thenReturn(stored);
|
||||
when(iamClient.submitPlatform(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new IamQualificationClient.PlatformQualificationResponse(31L, null, "PROFILE_REQUIRED", currentUrl)));
|
||||
|
||||
controller.upload(platformUser(), 10001L, file, "LEGAL_PERSON_ID", null, null, true);
|
||||
|
||||
verify(storage, never()).deleteByDocumentUrl(10001L, currentUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformTransportFailurePreservesTheFileBecauseTheIamOutcomeIsUnknown() {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var controller = platformController(storage, iamClient);
|
||||
var file = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
var stored = storedFile();
|
||||
when(storage.store(10001L, file)).thenReturn(stored);
|
||||
when(iamClient.submitPlatform(eq("internal-token"), any())).thenThrow(new RuntimeException("timeout"));
|
||||
|
||||
var exception = assertThrows(BizException.class,
|
||||
() -> controller.upload(platformUser(), 10001L, file, "LEGAL_PERSON_ID", null, null, true));
|
||||
|
||||
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
|
||||
verify(storage, never()).delete(stored);
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitPlatformBusinessRejectionDeletesTheUnreferencedFile() {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var controller = platformController(storage, iamClient);
|
||||
var file = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
var stored = storedFile();
|
||||
when(storage.store(10001L, file)).thenReturn(stored);
|
||||
when(iamClient.submitPlatform(eq("internal-token"), any())).thenReturn(
|
||||
ApiResponse.failure(ResultCode.CONFLICT, "duplicate", null, null));
|
||||
|
||||
var exception = assertThrows(BizException.class,
|
||||
() -> controller.upload(platformUser(), 10001L, file, "LEGAL_PERSON_ID", null, null, false));
|
||||
|
||||
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
|
||||
verify(storage).delete(stored);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantTransportFailureAlsoPreservesTheFileForReconciliation() {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var controller = merchantController(storage, iamClient);
|
||||
var file = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
var stored = storedFile();
|
||||
when(storage.store(10001L, file)).thenReturn(stored);
|
||||
when(iamClient.submit(eq("internal-token"), any())).thenThrow(new RuntimeException("timeout"));
|
||||
|
||||
var exception = assertThrows(BizException.class,
|
||||
() -> controller.upload(merchantUser(), file, null));
|
||||
|
||||
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
|
||||
verify(storage, never()).delete(stored);
|
||||
}
|
||||
|
||||
private static PlatformQualificationFileController platformController(
|
||||
LocalQualificationStorage storage, IamQualificationClient iamClient) {
|
||||
var controller = new PlatformQualificationFileController();
|
||||
ReflectionTestUtils.setField(controller, "storage", storage);
|
||||
ReflectionTestUtils.setField(controller, "iamClient", iamClient);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "internal-token");
|
||||
return controller;
|
||||
}
|
||||
|
||||
private static QualificationFileController merchantController(
|
||||
LocalQualificationStorage storage, IamQualificationClient iamClient) {
|
||||
var controller = new QualificationFileController();
|
||||
ReflectionTestUtils.setField(controller, "storage", storage);
|
||||
ReflectionTestUtils.setField(controller, "iamClient", iamClient);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "internal-token");
|
||||
return controller;
|
||||
}
|
||||
|
||||
private static LocalQualificationStorage.StoredFile storedFile() {
|
||||
return new LocalQualificationStorage.StoredFile(Path.of("new.pdf"), "new.pdf",
|
||||
"/api/merchant/v1/files/qualifications/11111111-1111-1111-1111-111111111111.pdf");
|
||||
}
|
||||
|
||||
private static AuthenticatedUser platformUser() {
|
||||
return new AuthenticatedUser(1L, 0L, 0L, 1L, "admin", "PLATFORM", "WEB", Set.of());
|
||||
}
|
||||
|
||||
private static AuthenticatedUser merchantUser() {
|
||||
return new AuthenticatedUser(2L, 10001L, 10001L, 2L, "merchant", "MERCHANT", "WEB", Set.of());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -63,6 +64,30 @@ class LocalQualificationStorageTest {
|
||||
storage.load(10001L, stored.fileName()).getFile().toPath().toAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletesTheReplacedQualificationByItsValidatedDocumentUrl() {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
storage.initialize();
|
||||
var upload = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4\ncontent".getBytes());
|
||||
var stored = storage.store(10001L, upload);
|
||||
|
||||
storage.deleteByDocumentUrl(10001L, stored.documentUrl());
|
||||
|
||||
assertFalse(Files.exists(stored.path()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotDeleteAQualificationForAnUntrustedDocumentUrl() {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
storage.initialize();
|
||||
var upload = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4\ncontent".getBytes());
|
||||
var stored = storage.store(10001L, upload);
|
||||
|
||||
storage.deleteByDocumentUrl(10001L, "/untrusted/" + stored.fileName());
|
||||
|
||||
assertTrue(Files.exists(stored.path()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacySeedPathCannotBePretendedToBeAStoredFile() {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
|
||||
Reference in New Issue
Block a user