fix(lastleg): restore dispatch returns from immutable pick source and expose customer receipts
This commit is contained in:
+50
@@ -3,6 +3,8 @@ package com.baiyue.lastleg.delivery.controller;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.service.DeliveryTransferService;
|
||||
import com.baiyue.lastleg.delivery.service.DeliveryDocumentService;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryDocumentDtos;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import jakarta.validation.Validation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -29,9 +31,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class MerchantDeliveryControllerTest {
|
||||
private final DeliveryTransferService service = mock(DeliveryTransferService.class);
|
||||
private final DeliveryDocumentService documents = mock(DeliveryDocumentService.class);
|
||||
private final MerchantDeliveryController controller = controller();
|
||||
private final DeliveryRequests.Query query = new DeliveryRequests.Query(
|
||||
1, 20, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
@@ -46,14 +50,20 @@ class MerchantDeliveryControllerTest {
|
||||
Method create = MerchantDeliveryController.class.getMethod("create", AuthenticatedUser.class,
|
||||
String.class, DeliveryRequests.Transfer.class);
|
||||
Method detail = MerchantDeliveryController.class.getMethod("detail", AuthenticatedUser.class, String.class);
|
||||
Method receipts = MerchantDeliveryController.class.getMethod("receipts", AuthenticatedUser.class, String.class);
|
||||
Method download = MerchantDeliveryController.class.getMethod("receiptDownload", AuthenticatedUser.class, String.class, long.class);
|
||||
|
||||
assertPermission(page, OverseasPermissions.MERCHANT_DELIVERY_LIST);
|
||||
assertPermission(detail, OverseasPermissions.MERCHANT_DELIVERY_LIST);
|
||||
assertPermission(receipts, OverseasPermissions.MERCHANT_DELIVERY_LIST);
|
||||
assertPermission(download, OverseasPermissions.MERCHANT_DELIVERY_LIST);
|
||||
assertPermission(create, OverseasPermissions.MERCHANT_DELIVERY_CREATE);
|
||||
assertNotEquals(OverseasPermissions.MERCHANT_DELIVERY_LIST,
|
||||
OverseasPermissions.MERCHANT_DELIVERY_CREATE);
|
||||
assertTrue(Arrays.stream(page.getParameters()).noneMatch(this::isCustomerParameter));
|
||||
assertTrue(Arrays.stream(detail.getParameters()).noneMatch(this::isCustomerParameter));
|
||||
assertTrue(Arrays.stream(receipts.getParameters()).noneMatch(this::isCustomerParameter));
|
||||
assertTrue(Arrays.stream(download.getParameters()).noneMatch(this::isCustomerParameter));
|
||||
assertTrue(Arrays.stream(create.getParameters()).noneMatch(this::isCustomerParameter));
|
||||
assertTrue(Arrays.stream(DeliveryRequests.Transfer.class.getRecordComponents())
|
||||
.noneMatch(component -> "customerId".equals(component.getName())));
|
||||
@@ -80,6 +90,36 @@ class MerchantDeliveryControllerTest {
|
||||
assertThrows(ResponseStatusException.class, () -> controller.page(platform, query));
|
||||
assertThrows(ResponseStatusException.class, () -> controller.detail(platform, "DEL-100"));
|
||||
assertThrows(ResponseStatusException.class, () -> controller.create(platform, "DSP-100", transferRequest()));
|
||||
assertThrows(ResponseStatusException.class, () -> controller.receipts(platform, "DEL-100"));
|
||||
assertThrows(ResponseStatusException.class, () -> controller.receiptDownload(platform, "DEL-100", 13));
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantReceiptDownloadKeepsTokenScopeAndReturnsSafeAttachmentBytes() {
|
||||
var file = new DeliveryDocumentDtos.File(13L, "签收.pdf", "application/pdf", 2, LocalDateTime.now());
|
||||
when(documents.merchantReceiptDownload("100","DEL-100",13))
|
||||
.thenReturn(new DeliveryDocumentService.Download(file, new byte[]{3,4}));
|
||||
controller.receipts(user(100L,"MERCHANT"),"DEL-100");
|
||||
var response = controller.receiptDownload(user(100L,"MERCHANT"),"DEL-100",13);
|
||||
verify(documents).merchantReceipts("100","DEL-100");
|
||||
verify(documents).merchantReceiptDownload("100","DEL-100",13);
|
||||
assertTrue(Arrays.equals(new byte[]{3,4},response.getBody()));
|
||||
assertEquals("nosniff",response.getHeaders().getFirst("X-Content-Type-Options"));
|
||||
assertEquals("no-store",response.getHeaders().getCacheControl());
|
||||
assertTrue(response.getHeaders().getContentDisposition().isAttachment());
|
||||
}
|
||||
|
||||
@Test
|
||||
void receiptReadsRejectMissingIdentityInvalidCustomerAndNonMerchantBeforeServiceAccess() {
|
||||
assertEquals(401, assertThrows(ResponseStatusException.class,
|
||||
() -> controller.receipts(null,"DEL-100")).getStatusCode().value());
|
||||
for (AuthenticatedUser invalid : List.of(user(-1L,"MERCHANT"), user(100L,"PLATFORM"), user(100L,"PDA"))) {
|
||||
assertEquals(403, assertThrows(ResponseStatusException.class,
|
||||
() -> controller.receipts(invalid,"DEL-100")).getStatusCode().value());
|
||||
assertEquals(403, assertThrows(ResponseStatusException.class,
|
||||
() -> controller.receiptDownload(invalid,"DEL-100",13)).getStatusCode().value());
|
||||
}
|
||||
org.mockito.Mockito.verifyNoInteractions(documents);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -101,6 +141,15 @@ class MerchantDeliveryControllerTest {
|
||||
assertTrue(contract.contains("merchant:warehouse:delivery:list"));
|
||||
assertTrue(contract.contains("merchant:warehouse:delivery:create"));
|
||||
assertFalse(contract.contains("name: customerId"));
|
||||
String receiptContract = Files.readString(Path.of("src/main/resources/openapi/delivery-documents-v1.yaml"));
|
||||
String merchantPaths = slice(receiptContract,
|
||||
" /api/merchant/v1/last-leg/delivery-orders/{deliveryNo}/documents/receipt:",
|
||||
" /api/platform/v1/last-leg/delivery-orders/{deliveryNo}/documents:");
|
||||
assertTrue(merchantPaths.contains("merchant:warehouse:delivery:list"));
|
||||
assertTrue(merchantPaths.contains("/documents/receipt/files/{fileId}/download:"));
|
||||
assertFalse(merchantPaths.contains("#/components/parameters/CustomerId"));
|
||||
assertFalse(merchantPaths.contains("\n post:"));
|
||||
assertFalse(merchantPaths.contains("\n put:"));
|
||||
}
|
||||
|
||||
private boolean isCustomerParameter(java.lang.reflect.Parameter parameter) {
|
||||
@@ -128,6 +177,7 @@ class MerchantDeliveryControllerTest {
|
||||
private MerchantDeliveryController controller() {
|
||||
MerchantDeliveryController value = new MerchantDeliveryController();
|
||||
ReflectionTestUtils.setField(value, "service", service);
|
||||
ReflectionTestUtils.setField(value, "documents", documents);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
+38
@@ -151,6 +151,44 @@ class DeliveryDocumentServiceTest {
|
||||
assertThat(operation.getAllValues()).extracting(DeliveryOperation::getOperationNode)
|
||||
.containsExactly("SAVE_DOCUMENTS", "RECEIPT_AUTO_COMPLETE");
|
||||
}
|
||||
@Test void merchantReadsOnlyCurrentReceiptFilesAndDownloadsWithAggregateOwnershipChecks() throws Exception {
|
||||
order.setDocumentsJson(new ObjectMapper().findAndRegisterModules().writeValueAsString(List.of(
|
||||
new Category(BOL,"UPLOADED",1,List.of(file(11))),
|
||||
new Category(RECEIPT,"UPLOADED",1,List.of(file(13))))));
|
||||
when(orders.findByNo("100","DL-1")).thenReturn(order);
|
||||
when(files.metadata("100",10,RECEIPT,13)).thenReturn(file(13));
|
||||
when(files.download("100",10,RECEIPT,13)).thenReturn(new byte[]{3,4});
|
||||
assertThat(service.merchantReceipts("100","DL-1").files()).containsExactly(file(13));
|
||||
assertThat(service.merchantReceiptDownload("100","DL-1",13).content()).containsExactly((byte)3,(byte)4);
|
||||
assertThatThrownBy(()->service.merchantReceiptDownload("100","DL-1",11))
|
||||
.isInstanceOf(ResponseStatusException.class);
|
||||
assertThatThrownBy(()->service.merchantReceiptDownload("100","DL-1",99))
|
||||
.isInstanceOf(ResponseStatusException.class);
|
||||
assertThatThrownBy(()->service.merchantReceipts("200","DL-1"))
|
||||
.isInstanceOf(ResponseStatusException.class);
|
||||
assertThatThrownBy(()->service.merchantReceiptDownload("200","DL-1",13))
|
||||
.isInstanceOf(ResponseStatusException.class);
|
||||
verify(files,never()).download("100",10,RECEIPT,11);
|
||||
verifyNoInteractions(revisions,operations);
|
||||
}
|
||||
@Test void merchantDoesNotExposeUploadedUnboundOrHistoricalRemovedReceipts() throws Exception {
|
||||
when(orders.findByNo("100","DL-1")).thenReturn(order);
|
||||
assertThat(service.merchantReceipts("100","DL-1").files()).isEmpty();
|
||||
assertThatThrownBy(()->service.merchantReceiptDownload("100","DL-1",13))
|
||||
.isInstanceOf(ResponseStatusException.class);
|
||||
verifyNoInteractions(files,revisions,operations);
|
||||
}
|
||||
@Test void merchantReceiptDownloadRejectsFileOwnershipFailureBeforeFetchingBytes() throws Exception {
|
||||
order.setDocumentsJson(new ObjectMapper().findAndRegisterModules().writeValueAsString(List.of(
|
||||
new Category(RECEIPT,"UPLOADED",1,List.of(file(13))))));
|
||||
when(orders.findByNo("100","DL-1")).thenReturn(order);
|
||||
when(files.metadata("100",10,RECEIPT,13)).thenThrow(
|
||||
new ResponseStatusException(HttpStatus.BAD_REQUEST,"文件不属于当前客户、派送单或资料类别"));
|
||||
assertThatThrownBy(()->service.merchantReceiptDownload("100","DL-1",13))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("当前客户");
|
||||
verify(files,never()).download(anyString(),anyLong(),any(),anyLong());
|
||||
verifyNoInteractions(revisions,operations);
|
||||
}
|
||||
private Save request(long version,String key,List<Binding> bindings) { return new Save(version,key,"BOL-1",bindings); }
|
||||
private File file(long id) { return new File(id,"a.pdf","application/pdf",1,LocalDateTime.of(2026,9,9,8,0)); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user