This commit is contained in:
2026-10-21 16:58:30 +08:00
parent 55436e3a0a
commit 4a0e86bd42
7 changed files with 667 additions and 404 deletions
@@ -0,0 +1,76 @@
package com.baiyue.lastleg.fleet.ownedbusiness;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.lastleg.dispatch.port.DispatchDetailReader;
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessModels.*;
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessScope;
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows;
import com.baiyue.lastleg.fleet.ownedbusiness.repository.OwnedBusinessRepository;
import com.baiyue.lastleg.fleet.ownedbusiness.service.*;
import com.baiyue.lastleg.fleet.outsourcedbusiness.service.OutsourcedBusinessService;
import com.baiyue.lastleg.fleet.ownedbusiness.controller.DispatchVehicleDocumentController;
import com.baiyue.lastleg.fleet.ownedbusiness.gateway.OwnedBusinessFileGateway;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.server.ResponseStatusException;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/** 排车资料复用同一车辆照片记录,不扩大旧入口或改变履约状态。 */
class DispatchVehicleDocumentTest {
/** 无拼车和实际拼车都能在派送后读取;原车队分类和冻结装车门禁继续生效。 */
@Test void dispatchScopeAcceptsBothCargoGroupsOnlyAfterDeparture() {
var repo=mock(OwnedBusinessRepository.class);var codec=OwnedBusinessFixtures.codec();
var queries=new OwnedBusinessQueryService(repo,new OwnedBusinessProjection(codec),codec,mock(DispatchDetailReader.class));
var source=OwnedBusinessFixtures.source();when(repo.source(source.getDispatchNo())).thenReturn(source);
assertThrows(BizException.class,()->queries.requireScoped(source.getDispatchNo(),OwnedBusinessScope.DISPATCH));
for(boolean pooled:List.of(false,true))for(String stage:List.of("DELIVERING","DELIVERED")) {
source.setPooledCargo(pooled);source.setStage(stage);
assertSame(source,queries.requireScoped(source.getDispatchNo(),OwnedBusinessScope.DISPATCH));
}
assertThrows(BizException.class,()->queries.requireOwned(source.getDispatchNo()));
source.setOwnedLoadingCompletionHash("other-load");
assertThrows(BizException.class,()->queries.requireScoped(source.getDispatchNo(),OwnedBusinessScope.DISPATCH));
verify(repo,never()).update(any(),anyLong());
}
/** 排车入口只接受油耗图片;签收和装车文件仍走派送单,旧外配仍不能上传油耗。 */
@Test void scopesKeepIndependentWritePermissionsAndPhotoCategories() {
var scope=OwnedBusinessScope.DISPATCH;
assertEquals("platform:overseas-warehouse:dispatch:list",scope.listPermission());
assertEquals("platform:overseas-warehouse:dispatch:edit",scope.uploadPermission());
for(var slot:PhotoSlot.values()) {
if(slot.category().equals("FUEL")) { assertDoesNotThrow(()->scope.requireSlot(slot));assertThrows(BizException.class,()->OwnedBusinessScope.OUTSOURCED.requireSlot(slot)); }
else assertThrows(BizException.class,()->scope.requireSlot(slot));
}
var actor=new Actor("reader","只读",Set.of(scope.listPermission()));
var row=new OwnedBusinessProjection(OwnedBusinessFixtures.codec()).row(OwnedBusinessFixtures.source(),null,OwnedBusinessFixtures.cargo(),List.of(),actor,scope);
assertFalse(row.actions().upload().allowed());
}
/** 油耗与车队装车照片共存,排车补充油耗不破坏外配业务读取。 */
@Test void sharedFuelPhotosDoNotInvalidateOutsourcedProjection() {
var codec=OwnedBusinessFixtures.codec();var record=new OwnedBusinessRows.Record();record.setRecordVersion(3L);record.setArrivalsJson("[]");
record.setPhotosJson(codec.write(List.of(new Photo(PhotoSlot.FUEL_START,"FUEL","77","fuel.png","image/png","9","2026-10-21T00:00:00Z","2026-10-21T00:00:00Z"))));
var projection=new OwnedBusinessProjection(codec);var source=OwnedBusinessFixtures.source();source.setPooledCargo(true);source.setStage("DELIVERED");
var row=projection.row(source,record,OwnedBusinessFixtures.cargo(),List.of(),OwnedBusinessFixtures.actor(),OwnedBusinessScope.OUTSOURCED);
assertEquals(1,row.photoSummary().fuelCount());assertEquals("3",row.recordVersion());assertEquals(8L,source.getRecordVersion());assertEquals("DELIVERED",source.getStage());
assertEquals("77",projection.photos(record).get(0).fileId());
var queries=mock(OwnedBusinessQueryService.class);
when(queries.detail(any(),eq(source.getDispatchNo()),eq(OwnedBusinessScope.OUTSOURCED)))
.thenReturn(new Detail(row,null,List.of(),projection.photos(record)));
var outsourced=new OutsourcedBusinessService(queries,mock(OwnedBusinessRecordService.class),mock(OwnedBusinessPhotoService.class),projection,codec);
assertTrue(outsourced.detail(OwnedBusinessFixtures.actor(),source.getDispatchNo()).photos().isEmpty());
}
/** 新入口读写权限独立;服务层同样拒绝仅查看账号和车队权限绕入。 */
@Test void controllerAndPhotoServiceKeepDispatchWriteBoundary() throws Exception {
assertEquals("hasAuthority('platform:overseas-warehouse:dispatch:list')",Arrays.stream(DispatchVehicleDocumentController.class.getMethods())
.filter(m->m.getName().equals("get")).findFirst().orElseThrow().getAnnotation(PreAuthorize.class).value());
for(String name:List.of("upload","bind","recover"))assertEquals("hasAuthority('platform:overseas-warehouse:dispatch:edit')",Arrays.stream(DispatchVehicleDocumentController.class.getMethods())
.filter(m->m.getName().equals(name)).findFirst().orElseThrow().getAnnotation(PreAuthorize.class).value());
var queries=mock(OwnedBusinessQueryService.class);var repo=mock(OwnedBusinessRepository.class);var files=mock(OwnedBusinessFileGateway.class);var codec=OwnedBusinessFixtures.codec();
var service=new OwnedBusinessPhotoService(queries,mock(OwnedBusinessRecordService.class),repo,mock(OwnedBusinessCommandService.class),new OwnedBusinessProjection(codec),codec,files);
for(var actor:List.of(new Actor("read","只读",Set.of(OwnedBusinessScope.DISPATCH.listPermission())),OwnedBusinessFixtures.actor()))
assertThrows(ResponseStatusException.class,()->service.upload(actor,"PC-1",PhotoSlot.FUEL_START,"upload-original",null,OwnedBusinessScope.DISPATCH));
verifyNoInteractions(files,repo,queries);
}
}