优化
This commit is contained in:
@@ -19,6 +19,16 @@ public interface IamQualificationClient {
|
||||
@RequestHeader("X-Internal-Service-Token") String internalToken,
|
||||
@RequestBody PlatformQualificationRequest request);
|
||||
|
||||
@PostMapping("/required-qualification-packages")
|
||||
ApiResponse<RequiredPackageResponse> submitRequiredPackage(
|
||||
@RequestHeader("X-Internal-Service-Token") String internalToken,
|
||||
@RequestBody RequiredPackageRequest request);
|
||||
|
||||
@PostMapping("/platform-required-qualification-packages")
|
||||
ApiResponse<RequiredPackageResponse> submitPlatformRequiredPackage(
|
||||
@RequestHeader("X-Internal-Service-Token") String internalToken,
|
||||
@RequestBody RequiredPackageRequest request);
|
||||
|
||||
record QualificationRequest(Long tenantId, String qualificationType,
|
||||
String certificateNo, String documentUrl,
|
||||
java.time.LocalDate validTo, boolean replace) { }
|
||||
@@ -29,4 +39,16 @@ public interface IamQualificationClient {
|
||||
record PlatformQualificationRequest(Long tenantId, String qualificationType,
|
||||
String certificateNo, String documentUrl,
|
||||
java.time.LocalDate validTo, boolean replace) { }
|
||||
record RequiredPackageRequest(Long tenantId, String unifiedSocialCreditCode,
|
||||
String businessLicenseDocumentUrl,
|
||||
java.time.LocalDate businessLicenseValidTo,
|
||||
boolean replaceBusinessLicense,
|
||||
String legalPersonIdDocumentUrl,
|
||||
java.time.LocalDate legalPersonIdValidTo,
|
||||
boolean replaceLegalPersonId) { }
|
||||
record RequiredPackageResponse(Long businessLicenseQualificationId,
|
||||
Long legalPersonIdQualificationId,
|
||||
String tenantStatus,
|
||||
String replacedBusinessLicenseDocumentUrl,
|
||||
String replacedLegalPersonIdDocumentUrl) { }
|
||||
}
|
||||
|
||||
+21
-1
@@ -6,6 +6,7 @@ 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 com.baiyue.file.service.QualificationPackageService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -32,6 +33,7 @@ public class PlatformQualificationFileController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PlatformQualificationFileController.class);
|
||||
@Resource private LocalQualificationStorage storage;
|
||||
@Resource private IamQualificationClient iamClient;
|
||||
@Resource private QualificationPackageService packageService;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
@PostMapping(value = "/{tenantId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@@ -45,7 +47,7 @@ public class PlatformQualificationFileController {
|
||||
@RequestParam(required = false) LocalDate validTo,
|
||||
@RequestParam(defaultValue = "false") boolean replace) {
|
||||
requirePlatform(user);
|
||||
qualificationType = QualificationTypes.requireSupported(qualificationType);
|
||||
qualificationType = QualificationTypes.requireOptional(qualificationType);
|
||||
var stored = storage.store(tenantId, file);
|
||||
try {
|
||||
var response = iamClient.submitPlatform(internalToken,
|
||||
@@ -79,6 +81,24 @@ public class PlatformQualificationFileController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{tenantId}/required-package", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PreAuthorize("hasAnyAuthority('platform:customer:create','platform:customer:edit')")
|
||||
public ApiResponse<QualificationPackageService.PackageUploadResult> uploadRequiredPackage(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable long tenantId,
|
||||
@RequestPart("businessLicense") MultipartFile businessLicense,
|
||||
@RequestPart("legalPersonId") MultipartFile legalPersonId,
|
||||
@RequestParam String unifiedSocialCreditCode,
|
||||
@RequestParam(required = false) LocalDate businessLicenseValidTo,
|
||||
@RequestParam(required = false) LocalDate legalPersonIdValidTo,
|
||||
@RequestParam(defaultValue = "false") boolean replaceBusinessLicense,
|
||||
@RequestParam(defaultValue = "false") boolean replaceLegalPersonId) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(packageService.upload(tenantId, businessLicense, legalPersonId,
|
||||
unifiedSocialCreditCode, businessLicenseValidTo, legalPersonIdValidTo,
|
||||
replaceBusinessLicense, replaceLegalPersonId, true));
|
||||
}
|
||||
|
||||
@GetMapping("/{tenantId}/{fileName}")
|
||||
@PreAuthorize("hasAuthority('platform:customer:view')")
|
||||
public ResponseEntity<org.springframework.core.io.Resource> download(
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 com.baiyue.file.service.QualificationPackageService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -26,6 +27,7 @@ public class QualificationFileController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QualificationFileController.class);
|
||||
@Resource private LocalQualificationStorage storage;
|
||||
@Resource private IamQualificationClient iamClient;
|
||||
@Resource private QualificationPackageService packageService;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@@ -38,7 +40,7 @@ public class QualificationFileController {
|
||||
@RequestParam(required = false) LocalDate validTo,
|
||||
@RequestParam(defaultValue = "false") boolean replace) {
|
||||
long tenantId = merchantTenant(user);
|
||||
qualificationType = QualificationTypes.requireSupported(qualificationType);
|
||||
qualificationType = QualificationTypes.requireOptional(qualificationType);
|
||||
var stored = storage.store(tenantId, file);
|
||||
try {
|
||||
var response = iamClient.submit(internalToken,
|
||||
@@ -72,6 +74,22 @@ public class QualificationFileController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/required-package", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PreAuthorize("hasAuthority('merchant:qualification:upload')")
|
||||
public ApiResponse<QualificationPackageService.PackageUploadResult> uploadRequiredPackage(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestPart("businessLicense") MultipartFile businessLicense,
|
||||
@RequestPart("legalPersonId") MultipartFile legalPersonId,
|
||||
@RequestParam String unifiedSocialCreditCode,
|
||||
@RequestParam(required = false) LocalDate businessLicenseValidTo,
|
||||
@RequestParam(required = false) LocalDate legalPersonIdValidTo,
|
||||
@RequestParam(defaultValue = "false") boolean replaceBusinessLicense,
|
||||
@RequestParam(defaultValue = "false") boolean replaceLegalPersonId) {
|
||||
return ApiResponse.success(packageService.upload(merchantTenant(user), businessLicense, legalPersonId,
|
||||
unifiedSocialCreditCode, businessLicenseValidTo, legalPersonIdValidTo,
|
||||
replaceBusinessLicense, replaceLegalPersonId, false));
|
||||
}
|
||||
|
||||
@GetMapping("/{fileName}")
|
||||
public ResponseEntity<org.springframework.core.io.Resource> download(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String fileName) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -19,4 +20,13 @@ final class QualificationTypes {
|
||||
}
|
||||
return qualificationType;
|
||||
}
|
||||
|
||||
static String requireOptional(String qualificationType) {
|
||||
String type = requireSupported(qualificationType);
|
||||
if ("BUSINESS_LICENSE".equals(type) || "LEGAL_PERSON_ID".equals(type)) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST,
|
||||
"Required qualifications must be uploaded as one package / 营业执照、统一社会信用代码和法人身份证必须整包上传");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.baiyue.file.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.file.client.IamQualificationClient;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
public class QualificationPackageService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QualificationPackageService.class);
|
||||
@Resource private LocalQualificationStorage storage;
|
||||
@Resource private IamQualificationClient iamClient;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
public PackageUploadResult upload(long tenantId, MultipartFile businessLicense,
|
||||
MultipartFile legalPersonId, String creditCode,
|
||||
LocalDate businessLicenseValidTo, LocalDate legalPersonIdValidTo,
|
||||
boolean replaceBusinessLicense, boolean replaceLegalPersonId,
|
||||
boolean platform) {
|
||||
String normalizedCreditCode = creditCode == null ? "" : creditCode.trim().toUpperCase();
|
||||
if (!normalizedCreditCode.matches("[0-9A-Z]{18}")) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "请输入有效的18位统一社会信用代码");
|
||||
}
|
||||
LocalQualificationStorage.StoredFile storedBusinessLicense = storage.store(tenantId, businessLicense);
|
||||
LocalQualificationStorage.StoredFile storedLegalPersonId;
|
||||
try {
|
||||
storedLegalPersonId = storage.store(tenantId, legalPersonId);
|
||||
} catch (RuntimeException exception) {
|
||||
storage.delete(storedBusinessLicense);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
var request = new IamQualificationClient.RequiredPackageRequest(
|
||||
tenantId, normalizedCreditCode, storedBusinessLicense.documentUrl(), businessLicenseValidTo,
|
||||
replaceBusinessLicense, storedLegalPersonId.documentUrl(), legalPersonIdValidTo, replaceLegalPersonId);
|
||||
try {
|
||||
var response = platform
|
||||
? iamClient.submitPlatformRequiredPackage(internalToken, request)
|
||||
: iamClient.submitRequiredPackage(internalToken, request);
|
||||
if (response == null) throw new IllegalStateException("IAM required qualification package response is empty");
|
||||
if (!response.successful()) {
|
||||
ResultCode code = ResultCode.of(response.code());
|
||||
String message = response.message() == null || response.message().isBlank()
|
||||
? code.message() : response.message();
|
||||
storage.delete(storedBusinessLicense);
|
||||
storage.delete(storedLegalPersonId);
|
||||
throw new BizException(code, message);
|
||||
}
|
||||
if (response.data() == null) {
|
||||
throw new IllegalStateException("IAM required qualification package response data is empty");
|
||||
}
|
||||
var data = response.data();
|
||||
deleteReplacedFile(tenantId, data.replacedBusinessLicenseDocumentUrl(), storedBusinessLicense.documentUrl());
|
||||
deleteReplacedFile(tenantId, data.replacedLegalPersonIdDocumentUrl(), storedLegalPersonId.documentUrl());
|
||||
return new PackageUploadResult(data.businessLicenseQualificationId(), data.legalPersonIdQualificationId(),
|
||||
data.tenantStatus(), storedBusinessLicense.documentUrl(), storedLegalPersonId.documentUrl());
|
||||
} catch (BizException exception) {
|
||||
throw exception;
|
||||
} catch (RuntimeException exception) {
|
||||
LOGGER.error("IAM 必填资质包登记结果未知,保留已写入文件等待重试或对账,tenantId={}, businessLicenseUrl={}, legalPersonIdUrl={}",
|
||||
tenantId, storedBusinessLicense.documentUrl(), storedLegalPersonId.documentUrl(), exception);
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM qualification service is unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteReplacedFile(long tenantId, String replacedUrl, String currentUrl) {
|
||||
if (!currentUrl.equals(replacedUrl)) storage.deleteByDocumentUrl(tenantId, replacedUrl);
|
||||
}
|
||||
|
||||
public record PackageUploadResult(Long businessLicenseQualificationId,
|
||||
Long legalPersonIdQualificationId,
|
||||
String tenantStatus,
|
||||
String businessLicenseDocumentUrl,
|
||||
String legalPersonIdDocumentUrl) { }
|
||||
}
|
||||
@@ -61,6 +61,13 @@ class QualificationFileContractTest {
|
||||
assertTrue(hasMultipartPost);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantAndPlatformExposeDedicatedRequiredPackageRoutes() {
|
||||
assertTrue(hasMultipartRoute(QualificationFileController.class, "/required-package"));
|
||||
assertTrue(hasMultipartRoute(PlatformQualificationFileController.class,
|
||||
"/{tenantId}/required-package"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformQualificationPreviewUsesTheCustomerViewPermission() throws Exception {
|
||||
var method = PlatformQualificationFileController.class.getDeclaredMethod(
|
||||
@@ -82,7 +89,7 @@ class QualificationFileContractTest {
|
||||
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,
|
||||
var response = controller.upload(platformUser(), 10001L, file, "BANK_ACCOUNT_LICENSE", null,
|
||||
LocalDate.of(2030, 12, 31), true);
|
||||
|
||||
var ordered = inOrder(storage, iamClient);
|
||||
@@ -108,7 +115,7 @@ class QualificationFileContractTest {
|
||||
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);
|
||||
controller.upload(platformUser(), 10001L, file, "BANK_ACCOUNT_LICENSE", null, null, true);
|
||||
|
||||
verify(storage, never()).deleteByDocumentUrl(10001L, currentUrl);
|
||||
}
|
||||
@@ -124,7 +131,7 @@ class QualificationFileContractTest {
|
||||
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));
|
||||
() -> controller.upload(platformUser(), 10001L, file, "BANK_ACCOUNT_LICENSE", null, null, true));
|
||||
|
||||
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
|
||||
verify(storage, never()).delete(stored);
|
||||
@@ -142,7 +149,7 @@ class QualificationFileContractTest {
|
||||
ApiResponse.failure(ResultCode.CONFLICT, "duplicate", null, null));
|
||||
|
||||
var exception = assertThrows(BizException.class,
|
||||
() -> controller.upload(platformUser(), 10001L, file, "LEGAL_PERSON_ID", null, null, false));
|
||||
() -> controller.upload(platformUser(), 10001L, file, "BANK_ACCOUNT_LICENSE", null, null, false));
|
||||
|
||||
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
|
||||
verify(storage).delete(stored);
|
||||
@@ -159,7 +166,7 @@ class QualificationFileContractTest {
|
||||
when(iamClient.submit(eq("internal-token"), any())).thenThrow(new RuntimeException("timeout"));
|
||||
|
||||
var exception = assertThrows(BizException.class,
|
||||
() -> controller.upload(merchantUser(), file, "BUSINESS_LICENSE", null, null, false));
|
||||
() -> controller.upload(merchantUser(), file, "FREIGHT_FORWARDER_CERTIFICATE", null, null, false));
|
||||
|
||||
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
|
||||
verify(storage, never()).delete(stored);
|
||||
@@ -177,12 +184,12 @@ class QualificationFileContractTest {
|
||||
when(iamClient.submit(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new IamQualificationClient.QualificationResponse(31L, null, "PROFILE_REQUIRED", oldUrl)));
|
||||
|
||||
controller.upload(merchantUser(), file, "LEGAL_PERSON_ID", null, LocalDate.of(2030, 1, 1), true);
|
||||
controller.upload(merchantUser(), file, "FREIGHT_FORWARDER_CERTIFICATE", null, LocalDate.of(2030, 1, 1), true);
|
||||
|
||||
var requestCaptor = org.mockito.ArgumentCaptor.forClass(IamQualificationClient.QualificationRequest.class);
|
||||
verify(iamClient).submit(eq("internal-token"), requestCaptor.capture());
|
||||
verify(storage).deleteByDocumentUrl(10001L, oldUrl);
|
||||
assertEquals("LEGAL_PERSON_ID", requestCaptor.getValue().qualificationType());
|
||||
assertEquals("FREIGHT_FORWARDER_CERTIFICATE", requestCaptor.getValue().qualificationType());
|
||||
assertTrue(requestCaptor.getValue().replace());
|
||||
}
|
||||
|
||||
@@ -203,9 +210,8 @@ class QualificationFileContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyRequiredQualificationTypeIsAcceptedAndForwarded() {
|
||||
void everyOptionalQualificationTypeIsAcceptedAndForwarded() {
|
||||
for (String qualificationType : Set.of(
|
||||
"BUSINESS_LICENSE", "LEGAL_PERSON_ID",
|
||||
"FREIGHT_FORWARDER_CERTIFICATE", "BANK_ACCOUNT_LICENSE")) {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
@@ -224,6 +230,22 @@ class QualificationFileContractTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacySingleUploadRejectsBothRequiredTypesBeforeWritingTheFile() {
|
||||
for (String qualificationType : Set.of("BUSINESS_LICENSE", "LEGAL_PERSON_ID")) {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var controller = merchantController(storage, iamClient);
|
||||
var file = new MockMultipartFile("file", "qualification.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
|
||||
assertThrows(BizException.class,
|
||||
() -> controller.upload(merchantUser(), file, qualificationType, null, null, false));
|
||||
|
||||
verify(storage, never()).store(any(Long.class), any());
|
||||
verify(iamClient, never()).submit(any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
private static PlatformQualificationFileController platformController(
|
||||
LocalQualificationStorage storage, IamQualificationClient iamClient) {
|
||||
var controller = new PlatformQualificationFileController();
|
||||
@@ -233,6 +255,14 @@ class QualificationFileContractTest {
|
||||
return controller;
|
||||
}
|
||||
|
||||
private static boolean hasMultipartRoute(Class<?> controllerType, String route) {
|
||||
return Arrays.stream(controllerType.getDeclaredMethods())
|
||||
.map(method -> method.getAnnotation(PostMapping.class))
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.anyMatch(mapping -> Arrays.asList(mapping.value()).contains(route)
|
||||
&& Arrays.asList(mapping.consumes()).contains(MediaType.MULTIPART_FORM_DATA_VALUE));
|
||||
}
|
||||
|
||||
private static QualificationFileController merchantController(
|
||||
LocalQualificationStorage storage, IamQualificationClient iamClient) {
|
||||
var controller = new QualificationFileController();
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.baiyue.file.service;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.file.client.IamQualificationClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
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;
|
||||
|
||||
class QualificationPackageServiceTest {
|
||||
private static final long TENANT_ID = 10001L;
|
||||
private static final String BUSINESS_URL =
|
||||
"/api/merchant/v1/files/qualifications/11111111-1111-1111-1111-111111111111.pdf";
|
||||
private static final String LEGAL_PERSON_URL =
|
||||
"/api/merchant/v1/files/qualifications/22222222-2222-2222-2222-222222222222.png";
|
||||
|
||||
@Test
|
||||
void storesBothFilesBeforeRegisteringOneIamPackage() {
|
||||
var fixture = fixture();
|
||||
when(fixture.storage.store(TENANT_ID, fixture.businessLicense)).thenReturn(fixture.businessStored);
|
||||
when(fixture.storage.store(TENANT_ID, fixture.legalPersonId)).thenReturn(fixture.legalPersonStored);
|
||||
when(fixture.iamClient.submitRequiredPackage(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new IamQualificationClient.RequiredPackageResponse(31L, 32L, "PROFILE_REQUIRED", null, null)));
|
||||
|
||||
var result = fixture.service.upload(TENANT_ID, fixture.businessLicense, fixture.legalPersonId,
|
||||
"91310101abcde12345", null, null, false, false, false);
|
||||
|
||||
var ordered = inOrder(fixture.storage, fixture.iamClient);
|
||||
ordered.verify(fixture.storage).store(TENANT_ID, fixture.businessLicense);
|
||||
ordered.verify(fixture.storage).store(TENANT_ID, fixture.legalPersonId);
|
||||
var captor = ArgumentCaptor.forClass(IamQualificationClient.RequiredPackageRequest.class);
|
||||
ordered.verify(fixture.iamClient).submitRequiredPackage(eq("internal-token"), captor.capture());
|
||||
assertEquals("91310101ABCDE12345", captor.getValue().unifiedSocialCreditCode());
|
||||
assertEquals(BUSINESS_URL, result.businessLicenseDocumentUrl());
|
||||
assertEquals(LEGAL_PERSON_URL, result.legalPersonIdDocumentUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondStorageFailureDeletesTheFirstFileAndNeverCallsIam() {
|
||||
var fixture = fixture();
|
||||
when(fixture.storage.store(TENANT_ID, fixture.businessLicense)).thenReturn(fixture.businessStored);
|
||||
when(fixture.storage.store(TENANT_ID, fixture.legalPersonId)).thenThrow(new BizException("disk full"));
|
||||
|
||||
assertThrows(BizException.class, () -> fixture.service.upload(TENANT_ID,
|
||||
fixture.businessLicense, fixture.legalPersonId, "91310101ABCDE12345",
|
||||
null, null, false, false, false));
|
||||
|
||||
verify(fixture.storage).delete(fixture.businessStored);
|
||||
verify(fixture.iamClient, never()).submitRequiredPackage(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitIamRejectionDeletesBothUnreferencedFiles() {
|
||||
var fixture = storedFixture();
|
||||
when(fixture.iamClient.submitRequiredPackage(eq("internal-token"), any())).thenReturn(
|
||||
ApiResponse.failure(ResultCode.CONFLICT, "duplicate", null, null));
|
||||
|
||||
var exception = assertThrows(BizException.class, () -> fixture.service.upload(TENANT_ID,
|
||||
fixture.businessLicense, fixture.legalPersonId, "91310101ABCDE12345",
|
||||
null, null, false, false, false));
|
||||
|
||||
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
|
||||
verify(fixture.storage).delete(fixture.businessStored);
|
||||
verify(fixture.storage).delete(fixture.legalPersonStored);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownIamOutcomePreservesBothFilesForReconciliation() {
|
||||
var fixture = storedFixture();
|
||||
when(fixture.iamClient.submitRequiredPackage(eq("internal-token"), any()))
|
||||
.thenThrow(new RuntimeException("timeout"));
|
||||
|
||||
var exception = assertThrows(BizException.class, () -> fixture.service.upload(TENANT_ID,
|
||||
fixture.businessLicense, fixture.legalPersonId, "91310101ABCDE12345",
|
||||
null, null, false, false, false));
|
||||
|
||||
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
|
||||
verify(fixture.storage, never()).delete(fixture.businessStored);
|
||||
verify(fixture.storage, never()).delete(fixture.legalPersonStored);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformSuccessCleansReplacedFilesOnlyAfterIamCommit() {
|
||||
var fixture = storedFixture();
|
||||
var oldBusinessUrl = "/api/merchant/v1/files/qualifications/33333333-3333-3333-3333-333333333333.pdf";
|
||||
var oldLegalPersonUrl = "/api/merchant/v1/files/qualifications/44444444-4444-4444-4444-444444444444.png";
|
||||
when(fixture.iamClient.submitPlatformRequiredPackage(eq("internal-token"), any())).thenReturn(
|
||||
ApiResponse.success(new IamQualificationClient.RequiredPackageResponse(
|
||||
31L, 32L, "PROFILE_REQUIRED", oldBusinessUrl, oldLegalPersonUrl)));
|
||||
|
||||
fixture.service.upload(TENANT_ID, fixture.businessLicense, fixture.legalPersonId,
|
||||
"91310101ABCDE12345", null, null, true, true, true);
|
||||
|
||||
var ordered = inOrder(fixture.iamClient, fixture.storage);
|
||||
ordered.verify(fixture.iamClient).submitPlatformRequiredPackage(eq("internal-token"), any());
|
||||
ordered.verify(fixture.storage).deleteByDocumentUrl(TENANT_ID, oldBusinessUrl);
|
||||
ordered.verify(fixture.storage).deleteByDocumentUrl(TENANT_ID, oldLegalPersonUrl);
|
||||
}
|
||||
|
||||
private static Fixture storedFixture() {
|
||||
var fixture = fixture();
|
||||
when(fixture.storage.store(TENANT_ID, fixture.businessLicense)).thenReturn(fixture.businessStored);
|
||||
when(fixture.storage.store(TENANT_ID, fixture.legalPersonId)).thenReturn(fixture.legalPersonStored);
|
||||
return fixture;
|
||||
}
|
||||
|
||||
private static Fixture fixture() {
|
||||
var storage = mock(LocalQualificationStorage.class);
|
||||
var iamClient = mock(IamQualificationClient.class);
|
||||
var service = new QualificationPackageService();
|
||||
ReflectionTestUtils.setField(service, "storage", storage);
|
||||
ReflectionTestUtils.setField(service, "iamClient", iamClient);
|
||||
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
||||
var businessLicense = new MockMultipartFile(
|
||||
"businessLicense", "license.pdf", "application/pdf", "%PDF-1.4".getBytes());
|
||||
var legalPersonId = new MockMultipartFile(
|
||||
"legalPersonId", "legal-person.png", "image/png", new byte[] {1, 2, 3});
|
||||
var businessStored = new LocalQualificationStorage.StoredFile(
|
||||
Path.of("business.pdf"), "business.pdf", BUSINESS_URL);
|
||||
var legalPersonStored = new LocalQualificationStorage.StoredFile(
|
||||
Path.of("legal-person.png"), "legal-person.png", LEGAL_PERSON_URL);
|
||||
return new Fixture(service, storage, iamClient, businessLicense, legalPersonId,
|
||||
businessStored, legalPersonStored);
|
||||
}
|
||||
|
||||
private record Fixture(QualificationPackageService service,
|
||||
LocalQualificationStorage storage,
|
||||
IamQualificationClient iamClient,
|
||||
MockMultipartFile businessLicense,
|
||||
MockMultipartFile legalPersonId,
|
||||
LocalQualificationStorage.StoredFile businessStored,
|
||||
LocalQualificationStorage.StoredFile legalPersonStored) { }
|
||||
}
|
||||
Reference in New Issue
Block a user