feat(lastleg): reuse fleet photos and support multiple loading images

This commit is contained in:
2026-10-26 17:12:36 +08:00
parent 5ad599bd66
commit 5eabfa395b
45 changed files with 961 additions and 35 deletions
@@ -639,6 +639,9 @@ public class DispatchAggregatePersistenceTest {
var dispatchEvidence=mock(com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.class);
when(dispatchEvidence.metadata(anyLong(),any(),anyLong())).thenAnswer(call->new com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File(call.getArgument(2),"照片.png","image/png",10));
org.springframework.test.util.ReflectionTestUtils.setField(service,"evidence",dispatchEvidence);
var profilePhotos=mock(com.baiyue.lastleg.fleet.photo.FleetResourcePhotoService.class);
when(profilePhotos.current(any(),anyLong())).thenReturn(new com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Detail(0,null));
org.springframework.test.util.ReflectionTestUtils.setField(service,"masterPhotos",new DispatchMasterPhotoService(profilePhotos,dispatchEvidence));
org.springframework.test.util.ReflectionTestUtils.setField(service,"ownedBusinessOwnership",ownedBusinessOwnership);
org.springframework.test.util.ReflectionTestUtils.setField(gateway,"workflow",service);
org.springframework.test.util.ReflectionTestUtils.setField(service,"appointments",new DispatchAppointmentAllocations(mock(com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.class)));
@@ -17,8 +17,9 @@ import static org.mockito.Mockito.*;
/** 排车反馈直接回归:图片送审门禁与正式预约的多货件投影。 */
class DispatchFeedbackRegressionTest {
/** 主档PDF与本趟补传图片分属各自文件域,补传不能被主档重建清空。 */
@Test void masterLicenseKeepsAdditionalDispatchPhotoAndItsOwner() throws Exception {
/** 主档PDF可以单独通过驾驶证校验,也可与本趟补传图片共存;司机照片自动带入。 */
@ParameterizedTest @ValueSource(booleans={false,true})
void masterLicenseKeepsAdditionalDispatchPhotoAndItsOwner(boolean supplement) throws Exception {
var f=new DispatchAggregatePersistenceTest.Fixture();
var created=f.tx.execute(s->f.service.create(f.actor,f.configured("master-photo",List.of(f.source(1,"101",501,"ONT8",3)))));
f.db.execute("CREATE TABLE ll_fleet_driver(id BIGINT PRIMARY KEY)");f.db.update("INSERT INTO ll_fleet_driver VALUES(71)");
@@ -30,15 +31,23 @@ class DispatchFeedbackRegressionTest {
when(details.licenseNumber()).thenReturn("LICENSE");when(details.licenseExpiresOn()).thenReturn(java.time.LocalDate.of(2099,1,1));
when(drivers.requireDispatchable(eq(71L),any())).thenReturn(driver);
when(drivers.download(71L,81L)).thenReturn(new com.baiyue.lastleg.fleet.driver.DriverModels.Download(new com.baiyue.lastleg.fleet.driver.DriverModels.File("81","主档证照.pdf","application/pdf",3),new byte[]{1,2,3}));
var profilePhotos=mock(com.baiyue.lastleg.fleet.photo.FleetResourcePhotoService.class);
var photo=new com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Photo("88","司机.png","image/png",3);
when(profilePhotos.current(com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Kind.DRIVER,71)).thenReturn(new com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Detail(1,photo));
when(profilePhotos.download(com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Kind.DRIVER,71,88)).thenReturn(new com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Download(photo,new byte[]{1,2,3}));
var evidence=(DispatchEvidenceGateway)ReflectionTestUtils.getField(f.service,"evidence");
when(evidence.upload(anyLong(),eq(DispatchEvidenceGateway.Kind.DRIVER_PHOTO),anyString(),any())).thenReturn(new DispatchEvidenceGateway.File(303L,"司机.png","image/png",3));
ReflectionTestUtils.setField(f.service,"masterPhotos",new DispatchMasterPhotoService(profilePhotos,evidence));
var input=f.configured("update",List.of());
var update=new DispatchCommands.Update(created.recordVersion(),"save-master",input.vehicle(),
new DispatchCommands.Driver(ResourceMode.MASTER,71L,null,null,new DispatchContracts.DriverEvidence(null,null,List.of(91L),input.driver().evidence().expectedTaskEndAt(),List.of(303L))),
new DispatchCommands.Driver(ResourceMode.MASTER,71L,null,null,new DispatchContracts.DriverEvidence(null,null,supplement?List.of(91L):List.of(),input.driver().evidence().expectedTaskEndAt(),List.of())),
input.plannedDepartureAt(),null,null,null);
var saved=f.tx.execute(s->f.service.update(f.actor,created.dispatchNo(),update));
f.prepareAppointmentNumbers(created.dispatchNo());
var proof=f.service.detail(f.actor,created.dispatchNo()).vehicleInfo().driver().evidence();
assertThat(proof.fileIds()).containsExactly(81L,91L);assertThat(proof.masterLicenseFileId()).isEqualTo(81L);
assertThat(f.service.evidenceMetadata(f.actor,created.dispatchNo(),DispatchEvidenceGateway.Kind.DRIVER,91L).contentType()).isEqualTo("image/png");
assertThat(proof.fileIds()).containsExactlyElementsOf(supplement?List.of(81L,91L):List.of(81L));assertThat(proof.masterLicenseFileId()).isEqualTo(81L);
assertThat(proof.photoFileIds()).containsExactly(303L);
if(supplement)assertThat(f.service.evidenceMetadata(f.actor,created.dispatchNo(),DispatchEvidenceGateway.Kind.DRIVER,91L).contentType()).isEqualTo("image/png");
assertThat(f.tx.execute(s->f.service.submitReview(f.actor,created.dispatchNo(),new DispatchCommands.SubmitReview(saved.recordVersion(),"submit-master",null))).recordVersion()).isGreaterThan(saved.recordVersion());
verify(drivers,never()).download(71L,91L);
}
@@ -183,6 +183,33 @@ class DispatchLoadingDeparturePersistenceTest {
verify(f.sourceWorkflow,times(1)).departWholeSources(any(),any(),any(),any());
}
/** 同类可保存五张但不能充当五类;追加、替换、版本与幂等共同保护原照片。 */
@Test void multiplePhotosPerCategoryPreserveReferencesAndDepartureGate() throws Exception {
authorize();var f=new Fixture();
for(int i=0;i<5;i++) {
var request=new BindPhoto(Integer.toString(i),"multi-photo-"+i,Long.toString(2000+i));
assertThat(f.f.tx.execute(s->f.documents.bind(f.f.actor,f.number,Slot.LOADING_SCENE,request)).complete()).isFalse();
}
assertThat(f.documents.get(f.f.actor,f.number).photos()).hasSize(5);
assertThatThrownBy(()->f.f.tx.execute(s->f.documents.bind(f.f.actor,f.number,Slot.LOADING_SCENE,new BindPhoto("5","too-many-photo","3000")))).hasMessageContaining("最多5张");
assertThat(f.documents.get(f.f.actor,f.number).recordVersion()).isEqualTo("5");
var replace=new BindPhoto("5","replace-photo","3000","2002");
var result=f.f.tx.execute(s->f.documents.bind(f.f.actor,f.number,Slot.LOADING_SCENE,replace));
assertThat(result.photos()).extracting(p->p.fileId()).containsExactly("2000","2001","3000","2003","2004");
var replay=f.f.tx.execute(s->f.documents.bind(f.f.actor,f.number,Slot.LOADING_SCENE,replace));
assertThat(replay).isEqualTo(result);
assertThatThrownBy(()->f.f.tx.execute(s->f.documents.bind(f.f.actor,f.number,Slot.LOADING_LIST,new BindPhoto("6","wrong-photo-slot","4000","2000")))).hasMessageContaining("待替换照片已变化");
for(int i=1;i<5;i++) {
var slot=DispatchLoadingDocumentService.SLOTS.get(i);var request=new BindPhoto(Integer.toString(5+i),"other-category-"+i,Long.toString(4000+i));
f.f.tx.execute(s->f.documents.bind(f.f.actor,f.number,slot,request));
}
assertThat(f.documents.get(f.f.actor,f.number).photos()).hasSize(9);
assertThat(f.documents.get(f.f.actor,f.number).complete()).isTrue();
verify(f.delivery,never()).prepareForDeparture(any(),any(),anyString());
assertThat(f.f.tx.execute(s->f.service.complete(f.f.actor,f.number,f.request("multi-ready"))).stage()).isEqualTo(Stage.DELIVERING);
verify(f.delivery,times(1)).prepareForDeparture(any(),any(),anyString());
}
/** 配置资料与装车权限及PDA既有角色,不授予人工派送、发车权限。 */
private static void authorize(){SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("operator","",
"platform:overseas-warehouse:dispatch:list","platform:overseas-warehouse:dispatch:edit","platform:overseas-warehouse:dispatch:loading-complete","lastleg:pda:operate","ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR"));}
@@ -0,0 +1,52 @@
package com.baiyue.lastleg.dispatch.service;
import com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway;
import com.baiyue.lastleg.dispatch.port.DispatchContracts.*;
import com.baiyue.lastleg.dispatch.port.DispatchTypes.ResourceMode;
import com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.*;
import com.baiyue.lastleg.fleet.photo.FleetResourcePhotoService;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/** 自动带入不复用跨排车文件ID,也不覆盖已有当趟照片。 */
class DispatchMasterPhotoServiceTest {
final FleetResourcePhotoService photos=mock(FleetResourcePhotoService.class);
final DispatchEvidenceGateway evidence=mock(DispatchEvidenceGateway.class);
final DispatchMasterPhotoService service=new DispatchMasterPhotoService(photos,evidence);
@Test void copiesBothProfilesAndKeepsFrozenPhotosWithoutReadingChangedMaster() throws Exception {
var photo=new Photo("81","profile.png","image/png",3);
when(photos.current(any(),anyLong())).thenReturn(new Detail(1,photo));
when(photos.download(any(),anyLong(),eq(81L))).thenReturn(new Download(photo,new byte[]{1,2,3}));
when(evidence.upload(eq(9L),eq(DispatchEvidenceGateway.Kind.VEHICLE),eq("op"),any())).thenReturn(new DispatchEvidenceGateway.File(101L,"vehicle.png","image/png",3));
when(evidence.upload(eq(9L),eq(DispatchEvidenceGateway.Kind.DRIVER_PHOTO),eq("op"),any())).thenReturn(new DispatchEvidenceGateway.File(102L,"driver.png","image/png",3));
var resources=new Resources(new VehicleSnapshot(ResourceMode.MASTER,1L,"TEST",null,"IN_SERVICE",null,List.of()),
new DriverSnapshot(ResourceMode.MASTER,2L,"司机","电话",new DriverEvidence(null,null,List.of(8L),null,List.of(),8L)),null);
var saved=service.populate(9,"op",resources);
assertThat(saved.vehicle().evidenceFileIds()).containsExactly(101L);
assertThat(saved.driver().evidence().photoFileIds()).containsExactly(102L);
assertThat(saved.driver().evidence().masterLicenseFileId()).isEqualTo(8L);
var file=ArgumentCaptor.forClass(MultipartFile.class);
verify(evidence).upload(eq(9L),eq(DispatchEvidenceGateway.Kind.VEHICLE),eq("op"),file.capture());
assertThat(file.getValue().getBytes()).containsExactly(1,2,3);
clearInvocations(photos,evidence);
assertThat(service.populate(9,"op",saved)).isEqualTo(saved);
verifyNoInteractions(photos,evidence);
}
@Test void missingProfileRemainsMissingAndCopyFailureIsNotHidden() {
when(photos.current(Kind.VEHICLE,1)).thenReturn(new Detail(0,null));
assertThat(service.populate(9,"op",Kind.VEHICLE,1,List.of())).isEmpty();
verifyNoInteractions(evidence);
when(photos.current(Kind.VEHICLE,1)).thenThrow(new IllegalStateException("File unavailable"));
assertThatThrownBy(() -> service.populate(9,"op",Kind.VEHICLE,1,List.of())).hasMessage("File unavailable");
}
@Test void externalResourcesNeverReadOrCreateMasterProfiles() {
var resources=new Resources(new VehicleSnapshot(ResourceMode.EXTERNAL,null,"TEST",null,null,null,List.of()),
new DriverSnapshot(ResourceMode.EXTERNAL,null,"司机","电话",new DriverEvidence(null,null,List.of(),null,List.of())),null);
assertThat(service.populate(9,"op",resources)).isEqualTo(resources);
verifyNoInteractions(photos,evidence);
}
}
@@ -148,13 +148,13 @@ class OwnedBusinessPersistenceTest {
var original=new BindPhoto("0","bind-original","9");
var bound=service.bind(OwnedBusinessFixtures.actor(),"DS-OWN-ONE",PhotoSlot.LOADING_SCENE,original,first);
assertEquals("1",bound.row().recordVersion());assertNotNull(bound.photos().get(0).savedAt());
assertEquals("保存装车现场照片",repository.revision(1,1).getReason());
assertEquals("保存司机驾驶证照片",repository.revision(1,1).getReason());
assertEquals("1",service.bind(OwnedBusinessFixtures.actor(),"DS-OWN-ONE",PhotoSlot.LOADING_SCENE,original,first).row().recordVersion());
var replacement=new Photo(PhotoSlot.LOADING_SCENE,"LOADING","10","TEST-two.png","image/png","9","2026-10-20T07:31:00",null);
service.bind(OwnedBusinessFixtures.actor(),"DS-OWN-ONE",PhotoSlot.LOADING_SCENE,new BindPhoto("1","bind-replacement","10"),replacement);
service.bind(OwnedBusinessFixtures.actor(),"DS-OWN-ONE",PhotoSlot.LOADING_SCENE,new BindPhoto("1","bind-replacement","10","9"),replacement);
assertEquals("10",projection.photos(repository.record(1)).get(0).fileId());
assertEquals("9",codec.read(repository.revision(1,1).getSnapshotJson(),Snapshot.class).photos().get(0).fileId());
assertEquals("保存装车现场照片",repository.revision(1,2).getReason());
assertEquals("保存司机驾驶证照片",repository.revision(1,2).getReason());
var file=mock(com.baiyue.lastleg.fleet.ownedbusiness.gateway.OwnedBusinessFileGateway.class);
when(file.content(1,PhotoSlot.LOADING_SCENE,9)).thenReturn(new byte[]{1,2});
var photoService=new OwnedBusinessPhotoService(queries,service,repository,commands,projection,codec,file);
@@ -247,7 +247,7 @@ class OwnedBusinessScopePersistenceTest {
}
assertEquals(5,outsourced.detail(actor(),"DS-OUT-2").row().photoSummary().loadingCount());
var replacement=outsourced.upload(actor(),"DS-OUT-2",OutsourcedBusinessModels.PhotoSlot.LOADING_SCENE,"out-replacement-upload",new MockMultipartFile("file","TEST.png","image/png",png));
outsourced.bind(actor(),"DS-OUT-2",OutsourcedBusinessModels.PhotoSlot.LOADING_SCENE,new OutsourcedBusinessModels.BindPhoto(version,"out-replacement-bind",replacement.fileId()));
outsourced.bind(actor(),"DS-OUT-2",OutsourcedBusinessModels.PhotoSlot.LOADING_SCENE,new OutsourcedBusinessModels.BindPhoto(version,"out-replacement-bind",replacement.fileId(),firstFile));
final long oldId=Long.parseLong(firstFile);
assertThrows(BizException.class,()->outsourced.content(actor(),"DS-OUT-2",OutsourcedBusinessModels.PhotoSlot.LOADING_SCENE,oldId,null));
assertArrayEquals(png,outsourced.content(actor(),"DS-OUT-2",OutsourcedBusinessModels.PhotoSlot.LOADING_SCENE,oldId,1L).content());
@@ -0,0 +1,25 @@
package com.baiyue.lastleg.fleet.ownedbusiness.service;
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessModels.*;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
class OwnedBusinessPhotoBindingsTest {
/** 保存油耗时不得压缩PDA同类多张装车照片。 */
@Test void fuelReplacementPreservesEveryLoadingPhoto() {
var existing=List.of(photo(PhotoSlot.LOADING_SCENE,"1"),photo(PhotoSlot.LOADING_SCENE,"2"),photo(PhotoSlot.LOADING_LIST,"3"),photo(PhotoSlot.FUEL_START,"4"));
var saved=OwnedBusinessPhotoBindings.bind(existing,photo(PhotoSlot.FUEL_START,"5"),"4");
assertThat(saved).extracting(Photo::fileId).containsExactly("1","2","3","5");
assertThat(OwnedBusinessPhotoBindings.bind(saved,photo(PhotoSlot.FUEL_START,"6"),null)).extracting(Photo::fileId).containsExactly("1","2","3","6");
}
/** 同一文件不能重复绑定或借替换覆盖另一个类别。 */
@Test void rejectsDuplicateAndStaleReplacement() {
var existing=List.of(photo(PhotoSlot.LOADING_SCENE,"1"),photo(PhotoSlot.LOADING_SCENE,"2"));
assertThatThrownBy(()->OwnedBusinessPhotoBindings.bind(existing,photo(PhotoSlot.LOADING_SCENE,"1"),null)).hasMessageContaining("重复");
assertThatThrownBy(()->OwnedBusinessPhotoBindings.bind(existing,photo(PhotoSlot.LOADING_SCENE,"2"),"1")).hasMessageContaining("重复");
assertThatThrownBy(()->OwnedBusinessPhotoBindings.bind(existing,photo(PhotoSlot.LOADING_LIST,"3"),"1")).hasMessageContaining("待替换");
}
/** 使用不含真实用户资料的固定文件元数据。 */
private Photo photo(PhotoSlot slot,String id) { return new Photo(slot,slot.category(),id,"test.png","image/png","5","2026-10-26T00:00:00Z","2026-10-26T00:00:00Z"); }
}
@@ -0,0 +1,39 @@
package com.baiyue.lastleg.fleet.photo;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.UUID;
import static com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.*;
import static org.assertj.core.api.Assertions.*;
/** 使用真实建表定义与Mapper验证类型隔离、最新版本及不可变历史。 */
class FleetResourcePhotoPersistenceTest {
@Test void preservesHistoryAndRejectsTwoWritesToSameVersion() throws Exception {
var source=new JdbcDataSource();source.setURL("jdbc:h2:mem:photos_"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1");
var schema=Files.readString(Path.of("src/main/resources/db/lastleg.sql"));
var ddl=schema.substring(schema.indexOf("CREATE TABLE IF NOT EXISTS ll_fleet_resource_photo ("));
try(var connection=source.getConnection();var statement=connection.createStatement()) { statement.execute(ddl); }
var config=new Configuration(new Environment("photos",new JdbcTransactionFactory(),source));
config.setMapUnderscoreToCamelCase(true);config.addMapper(FleetResourcePhotoMapper.class);
try(var session=new SqlSessionFactoryBuilder().build(config).openSession(true)) {
var repository=new FleetResourcePhotoRepository(session.getMapper(FleetResourcePhotoMapper.class));
assertThat(repository.current(Kind.DRIVER,7)).isNull();
var at=LocalDateTime.of(2026,10,26,0,0);
repository.append(new Stored("DRIVER",7,1,81,"first.png","image/png",3,"op",at));
repository.append(new Stored("DRIVER",7,2,82,"second.png","image/png",4,"op",at));
assertThat(repository.current(Kind.DRIVER,7).fileId()).isEqualTo(82);
assertThat(repository.file(Kind.DRIVER,7,81).fileName()).isEqualTo("first.png");
assertThat(repository.file(Kind.VEHICLE,7,81)).isNull();
assertThat(repository.file(Kind.DRIVER,8,81)).isNull();
assertThatThrownBy(() -> repository.append(new Stored("DRIVER",7,2,83,"conflict.png","image/png",3,"op",at))).isInstanceOf(RuntimeException.class);
assertThat(repository.current(Kind.DRIVER,7).fileId()).isEqualTo(82);
}
}
}
@@ -0,0 +1,39 @@
package com.baiyue.lastleg.fleet.photo;
import com.baiyue.lastleg.fleet.driver.DriverService;
import com.baiyue.lastleg.masterdata.vehicle.application.port.out.VehicleRepository;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.mock.web.MockMultipartFile;
import java.time.LocalDateTime;
import static com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
class FleetResourcePhotoServiceTest {
final FleetResourcePhotoRepository repository=mock(FleetResourcePhotoRepository.class);
final FleetResourcePhotoGateway files=mock(FleetResourcePhotoGateway.class);
final DriverService drivers=mock(DriverService.class);
final FleetResourcePhotoService service=new FleetResourcePhotoService(repository,files,mock(VehicleRepository.class),drivers);
final Photo photo=new Photo("81","driver.png","image/png",3);
final MockMultipartFile upload=new MockMultipartFile("file","driver.png","image/png",new byte[]{1,2,3});
@Test void staleVersionCannotUploadAndConcurrentReplacementCannotOverwrite() {
when(repository.current(Kind.DRIVER,2)).thenReturn(new Stored("DRIVER",2,1,81,"driver.png","image/png",3,"op",LocalDateTime.now()));
assertThatThrownBy(() -> service.upload(Kind.DRIVER,2,0,"op",upload)).hasMessageContaining("已变化");
verifyNoInteractions(files);
when(files.upload(Kind.DRIVER,2,"op",upload)).thenReturn(photo);
doThrow(new DuplicateKeyException("version")).when(repository).append(any());
assertThatThrownBy(() -> service.upload(Kind.DRIVER,2,1,"op",upload)).hasMessageContaining("已变化");
}
@Test void fileFromAnotherProfileIsRejectedBeforeFileDownload() {
assertThatThrownBy(() -> service.download(Kind.DRIVER,2,81)).hasMessageContaining("不存在");
verifyNoInteractions(files);
}
@Test void firstUploadCreatesIndependentVersionAndDoesNotChangeDriverReview() {
when(files.upload(Kind.DRIVER,2,"op",upload)).thenReturn(photo);
assertThat(service.upload(Kind.DRIVER,2,0,"op",upload)).isEqualTo(new Detail(1,photo));
verify(drivers).detail(2,null);
verifyNoMoreInteractions(drivers);
verify(repository).append(argThat(row -> row.recordVersion()==1 && row.resourceId()==2 && row.fileId()==81));
}
}
@@ -0,0 +1,21 @@
package com.baiyue.lastleg.fleet.photo;
import org.junit.jupiter.api.Test;
import static com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.Kind.*;
import static org.assertj.core.api.Assertions.*;
class HttpFleetResourcePhotoGatewayTest {
@Test void validatesResourceKindOwnerIdentityAndImageType() {
var data=new HttpFleetResourcePhotoGateway.Metadata(81L,0L,"FLEET_DRIVER_PHOTO","7","photo.png","image/png",3L);
var envelope=new HttpFleetResourcePhotoGateway.Envelope("200",data);
assertThat(HttpFleetResourcePhotoGateway.verified(envelope,DRIVER,7,81L).fileId()).isEqualTo("81");
assertThatThrownBy(() -> HttpFleetResourcePhotoGateway.verified(envelope,VEHICLE,7,81L)).hasMessageContaining("不属于");
assertThatThrownBy(() -> HttpFleetResourcePhotoGateway.verified(envelope,DRIVER,8,81L)).hasMessageContaining("不属于");
assertThatThrownBy(() -> HttpFleetResourcePhotoGateway.verified(envelope,DRIVER,7,82L)).hasMessageContaining("不属于");
for(var invalid : new HttpFleetResourcePhotoGateway.Metadata[]{
new HttpFleetResourcePhotoGateway.Metadata(81L,1L,"FLEET_DRIVER_PHOTO","7","photo.png","image/png",3L),
new HttpFleetResourcePhotoGateway.Metadata(81L,0L,"FLEET_DRIVER_PHOTO","7","photo.pdf","application/pdf",3L)}) {
assertThatThrownBy(() -> HttpFleetResourcePhotoGateway.verified(new HttpFleetResourcePhotoGateway.Envelope("200",invalid),DRIVER,7,81L)).hasMessageContaining("不属于");
}
}
}
@@ -0,0 +1,36 @@
package com.baiyue.lastleg.fleet.photo;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;
import static com.baiyue.lastleg.fleet.photo.FleetResourcePhotoModels.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/** 使用真实方法鉴权代理验证资源类型不能借用另一类档案权限。 */
class PlatformFleetResourcePhotoControllerTest {
@Configuration @EnableMethodSecurity
static class Config {
@Bean FleetResourcePhotoService service() { return mock(FleetResourcePhotoService.class); }
@Bean PlatformFleetResourcePhotoController controller(FleetResourcePhotoService service) { return new PlatformFleetResourcePhotoController(service); }
}
@Test void listPermissionOnlyReadsItsOwnResourceKindAndCannotUpload() {
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
var service=context.getBean(FleetResourcePhotoService.class);
var controller=context.getBean(PlatformFleetResourcePhotoController.class);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("test","",List.of(new SimpleGrantedAuthority("platform:overseas-master-data:vehicle:list"))));
when(service.current(Kind.VEHICLE,7)).thenReturn(new Detail(0,null));
controller.current(Kind.VEHICLE,7);
verify(service).current(Kind.VEHICLE,7);
assertThatThrownBy(() -> controller.current(Kind.DRIVER,7)).isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> controller.upload(Kind.VEHICLE,7,0,null,null)).isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> controller.upload(Kind.DRIVER,7,0,null,null)).isInstanceOf(AccessDeniedException.class);
verifyNoMoreInteractions(service);
} finally { SecurityContextHolder.clearContext(); }
}
}