暂存提交
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseLocationEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WarehouseApplicationServiceTest {
|
||||
@Mock WarehouseRepository repository;
|
||||
private WarehouseApplicationService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new WarehouseApplicationService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void warehouseCreationAlsoCreatesActiveDefaultVirtualLocation() {
|
||||
doAnswer(call -> {
|
||||
((WarehouseEntity) call.getArgument(0)).setId(20001L);
|
||||
return null;
|
||||
}).when(repository).insertWarehouse(any());
|
||||
|
||||
service.create(request());
|
||||
|
||||
ArgumentCaptor<WarehouseLocationEntity> location = ArgumentCaptor.forClass(WarehouseLocationEntity.class);
|
||||
verify(repository).insertLocation(location.capture());
|
||||
assertThat(location.getValue().getCustomerId()).isEqualTo(10001L);
|
||||
assertThat(location.getValue().getWarehouseId()).isEqualTo(20001L);
|
||||
assertThat(location.getValue().getLocationCode()).isEqualTo("LOC-US-LAX-01-DEFAULT");
|
||||
assertThat(location.getValue().getLocationType()).isEqualTo("VIRTUAL");
|
||||
assertThat(location.getValue().getStatus()).isEqualTo("ACTIVE");
|
||||
}
|
||||
|
||||
private static WarehouseRequests.SaveRequest request() {
|
||||
WarehouseRequests.AddressRequest address = new WarehouseRequests.AddressRequest(
|
||||
"WAREHOUSE", "US", "CA", "Los Angeles", null, "90001",
|
||||
"1 Logistics Ave", null, null, null);
|
||||
return new WarehouseRequests.SaveRequest(10001L, "US-LAX-01", "LAX Warehouse",
|
||||
"OVERSEAS", "US", "America/Los_Angeles", null, null, null,
|
||||
"ACTIVE", null, address, List.of());
|
||||
}
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseResourceLockRequests;
|
||||
import com.baiyue.warehouse.entity.WarehouseLocationEntity;
|
||||
import com.baiyue.warehouse.entity.WarehousePalletEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseResourceLockEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseResourceLockRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WarehouseResourceLockServiceTest {
|
||||
private static final long TENANT = 101L;
|
||||
private static final long WAREHOUSE = 201L;
|
||||
private static final long PALLET = 301L;
|
||||
private static final long LOCATION = 401L;
|
||||
|
||||
@Mock WarehouseResourceLockRepository repository;
|
||||
private WarehouseResourceLockService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new WarehouseResourceLockService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicallyOccupiesIdlePalletAndLocation() {
|
||||
var request = request("reserve-1");
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, PALLET)).thenReturn(pallet("IDLE"));
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("IDLE"));
|
||||
when(repository.occupyPallet(TENANT, WAREHOUSE, PALLET, LOCATION)).thenReturn(1);
|
||||
when(repository.occupyLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(1);
|
||||
doAnswer(call -> {
|
||||
((WarehouseResourceLockEntity) call.getArgument(0)).setId(501L);
|
||||
return null;
|
||||
}).when(repository).insert(any());
|
||||
|
||||
var result = service.reserve(request);
|
||||
|
||||
assertThat(result.lockId()).isEqualTo(501L);
|
||||
assertThat(result.status()).isEqualTo("ACTIVE");
|
||||
ArgumentCaptor<WarehouseResourceLockEntity> lock = ArgumentCaptor.forClass(WarehouseResourceLockEntity.class);
|
||||
verify(repository).insert(lock.capture());
|
||||
assertThat(lock.getValue().getReservationKey()).isEqualTo("reserve-1");
|
||||
assertThat(lock.getValue().getPalletId()).isEqualTo(PALLET);
|
||||
assertThat(lock.getValue().getLocationId()).isEqualTo(LOCATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateReservationReturnsExistingLockWithoutMutatingResources() {
|
||||
WarehouseResourceLockEntity existing = activeLock();
|
||||
when(repository.findByReservationKey(TENANT, "reserve-1")).thenReturn(existing);
|
||||
|
||||
var result = service.reserve(request("reserve-1"));
|
||||
|
||||
assertThat(result.lockId()).isEqualTo(existing.getId());
|
||||
verify(repository, never()).occupyPallet(TENANT, WAREHOUSE, PALLET, LOCATION);
|
||||
verify(repository, never()).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentDuplicateReturnsExistingAfterResourcesBecomeOccupied() {
|
||||
WarehouseResourceLockEntity existing = activeLock();
|
||||
when(repository.findByReservationKey(TENANT, "reserve-1")).thenReturn(null, existing);
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, PALLET)).thenReturn(pallet("OCCUPIED"));
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("OCCUPIED"));
|
||||
|
||||
var result = service.reserve(request("reserve-1"));
|
||||
|
||||
assertThat(result.lockId()).isEqualTo(existing.getId());
|
||||
verify(repository, never()).occupyPallet(TENANT, WAREHOUSE, PALLET, LOCATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reservationKeyCannotBeReusedForDifferentResources() {
|
||||
WarehouseResourceLockEntity existing = activeLock();
|
||||
existing.setPalletId(999L);
|
||||
when(repository.findByReservationKey(TENANT, "reserve-1")).thenReturn(existing);
|
||||
|
||||
assertThatThrownBy(() -> service.reserve(request("reserve-1")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("different resources");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsResourceOutsideTenantWarehouse() {
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, PALLET)).thenReturn(null);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("IDLE"));
|
||||
|
||||
assertThatThrownBy(() -> service.reserve(request("reserve-1")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("tenant warehouse");
|
||||
verify(repository, never()).occupyPallet(TENANT, WAREHOUSE, PALLET, LOCATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsNonIdleResources() {
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, PALLET)).thenReturn(pallet("OCCUPIED"));
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("IDLE"));
|
||||
|
||||
assertThatThrownBy(() -> service.reserve(request("reserve-1")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("Only IDLE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseRestoresBothResourcesAndClosesLock() {
|
||||
WarehouseResourceLockEntity lock = activeLock();
|
||||
when(repository.find(TENANT, lock.getId())).thenReturn(lock);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("OCCUPIED"));
|
||||
when(repository.releasePallet(lock)).thenReturn(1);
|
||||
when(repository.releaseLocation(lock)).thenReturn(1);
|
||||
when(repository.close(lock, "RELEASED")).thenReturn(1);
|
||||
|
||||
var result = service.release(TENANT, lock.getId());
|
||||
|
||||
assertThat(result.status()).isEqualTo("RELEASED");
|
||||
verify(repository).releasePallet(lock);
|
||||
verify(repository).releaseLocation(lock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrapDiscardsPalletAndReleasesLocation() {
|
||||
WarehouseResourceLockEntity lock = activeLock();
|
||||
when(repository.find(TENANT, lock.getId())).thenReturn(lock);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("OCCUPIED"));
|
||||
when(repository.scrapPallet(lock)).thenReturn(1);
|
||||
when(repository.releaseLocation(lock)).thenReturn(1);
|
||||
when(repository.close(lock, "SCRAPPED")).thenReturn(1);
|
||||
|
||||
var result = service.scrap(TENANT, lock.getId());
|
||||
|
||||
assertThat(result.status()).isEqualTo("SCRAPPED");
|
||||
verify(repository).scrapPallet(lock);
|
||||
verify(repository).releaseLocation(lock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentLocationClaimFailsWholeOperation() {
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, PALLET)).thenReturn(pallet("IDLE"));
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(location("IDLE"));
|
||||
when(repository.occupyPallet(TENANT, WAREHOUSE, PALLET, LOCATION)).thenReturn(1);
|
||||
when(repository.occupyLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(0);
|
||||
|
||||
assertThatThrownBy(() -> service.reserve(request("reserve-1")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("no longer IDLE");
|
||||
verify(repository, never()).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void virtualLocationIsNeverOccupiedOrReleased() {
|
||||
WarehouseLocationEntity virtual = location("ACTIVE");
|
||||
virtual.setLocationType("VIRTUAL");
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, PALLET)).thenReturn(pallet("IDLE"));
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(virtual);
|
||||
when(repository.occupyPallet(TENANT, WAREHOUSE, PALLET, LOCATION)).thenReturn(1);
|
||||
doAnswer(call -> {
|
||||
((WarehouseResourceLockEntity) call.getArgument(0)).setId(502L);
|
||||
return null;
|
||||
}).when(repository).insert(any());
|
||||
|
||||
service.reserve(request("virtual-reserve"));
|
||||
|
||||
verify(repository, never()).occupyLocation(TENANT, WAREHOUSE, LOCATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrappingAtVirtualLocationKeepsLocationUntouched() {
|
||||
WarehouseResourceLockEntity lock = activeLock();
|
||||
WarehouseLocationEntity virtual = location("ACTIVE");
|
||||
virtual.setLocationType("VIRTUAL");
|
||||
when(repository.find(TENANT, lock.getId())).thenReturn(lock);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(virtual);
|
||||
when(repository.scrapPalletAtVirtualLocation(lock)).thenReturn(1);
|
||||
when(repository.close(lock, "SCRAPPED")).thenReturn(1);
|
||||
|
||||
var result = service.scrap(TENANT, lock.getId());
|
||||
|
||||
assertThat(result.status()).isEqualTo("SCRAPPED");
|
||||
verify(repository).scrapPalletAtVirtualLocation(lock);
|
||||
verify(repository, never()).releaseLocation(lock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrapByPalletClosesItsActiveLockAndKeepsLocationReference() {
|
||||
WarehousePalletEntity pallet = pallet("OCCUPIED");
|
||||
pallet.setWarehouseId(WAREHOUSE);
|
||||
pallet.setLocationId(LOCATION);
|
||||
WarehouseResourceLockEntity lock = activeLock();
|
||||
when(repository.findPallet(TENANT, PALLET)).thenReturn(pallet);
|
||||
when(repository.findActiveByPallet(TENANT, PALLET)).thenReturn(lock);
|
||||
when(repository.find(TENANT, lock.getId())).thenReturn(lock);
|
||||
WarehouseLocationEntity virtual = location("ACTIVE");
|
||||
virtual.setLocationType("VIRTUAL");
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, LOCATION)).thenReturn(virtual);
|
||||
when(repository.scrapPalletAtVirtualLocation(lock)).thenReturn(1);
|
||||
when(repository.close(lock, "SCRAPPED")).thenReturn(1);
|
||||
|
||||
var result = service.scrapByPallet(TENANT, PALLET);
|
||||
|
||||
assertThat(result.status()).isEqualTo("SCRAPPED");
|
||||
assertThat(result.locationId()).isEqualTo(LOCATION);
|
||||
verify(repository).scrapPalletAtVirtualLocation(lock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrapByPalletIsIdempotentWhenAlreadyScrapped() {
|
||||
WarehousePalletEntity pallet = pallet("SCRAPPED");
|
||||
pallet.setWarehouseId(WAREHOUSE);
|
||||
pallet.setLocationId(LOCATION);
|
||||
when(repository.findPallet(TENANT, PALLET)).thenReturn(pallet);
|
||||
|
||||
assertThat(service.scrapByPallet(TENANT, PALLET).status()).isEqualTo("SCRAPPED");
|
||||
verify(repository, never()).findActiveByPallet(TENANT, PALLET);
|
||||
}
|
||||
|
||||
private static WarehouseResourceLockRequests.ReserveRequest request(String key) {
|
||||
return new WarehouseResourceLockRequests.ReserveRequest(TENANT, WAREHOUSE, PALLET, LOCATION,
|
||||
"INBOUND_PLAN_PALLET", "plan-pallet-1", key);
|
||||
}
|
||||
|
||||
private static WarehousePalletEntity pallet(String status) {
|
||||
WarehousePalletEntity entity = new WarehousePalletEntity();
|
||||
entity.setStatus(status);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static WarehouseLocationEntity location(String status) {
|
||||
WarehouseLocationEntity entity = new WarehouseLocationEntity();
|
||||
entity.setStatus(status);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static WarehouseResourceLockEntity activeLock() {
|
||||
WarehouseResourceLockEntity lock = new WarehouseResourceLockEntity();
|
||||
lock.setId(501L);
|
||||
lock.setCustomerId(TENANT);
|
||||
lock.setWarehouseId(WAREHOUSE);
|
||||
lock.setPalletId(PALLET);
|
||||
lock.setLocationId(LOCATION);
|
||||
lock.setBusinessType("INBOUND_PLAN_PALLET");
|
||||
lock.setBusinessId("plan-pallet-1");
|
||||
lock.setReservationKey("reserve-1");
|
||||
lock.setStatus("ACTIVE");
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.LocationRequests;
|
||||
import com.baiyue.warehouse.dto.PalletRequests;
|
||||
import com.baiyue.warehouse.entity.WarehouseLocationEntity;
|
||||
import com.baiyue.warehouse.entity.WarehousePalletEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseResourceRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WarehouseResourceServiceTest {
|
||||
private static final long TENANT = 10001L;
|
||||
private static final long WAREHOUSE = 20001L;
|
||||
|
||||
@Mock
|
||||
private WarehouseResourceRepository repository;
|
||||
|
||||
private WarehouseResourceService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new WarehouseResourceService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceIdsUseMybatisSnowflakeAssignment() throws NoSuchFieldException {
|
||||
assertThat(WarehouseLocationEntity.class.getDeclaredField("id").getAnnotation(TableId.class).type())
|
||||
.isEqualTo(IdType.ASSIGN_ID);
|
||||
assertThat(WarehousePalletEntity.class.getDeclaredField("id").getAnnotation(TableId.class).type())
|
||||
.isEqualTo(IdType.ASSIGN_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsLocationWithSnowflakeAssignedByMapper() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
doAnswer(invocation -> {
|
||||
((WarehouseLocationEntity) invocation.getArgument(0)).setId(2089000000000000001L);
|
||||
return null;
|
||||
}).when(repository).insertLocation(org.mockito.ArgumentMatchers.any());
|
||||
|
||||
Long id = service.createLocation(TENANT, WAREHOUSE, locationRequest("A-01"));
|
||||
|
||||
assertThat(id).isEqualTo(2089000000000000001L);
|
||||
ArgumentCaptor<WarehouseLocationEntity> captor = ArgumentCaptor.forClass(WarehouseLocationEntity.class);
|
||||
verify(repository).insertLocation(captor.capture());
|
||||
assertThat(captor.getValue().getCustomerId()).isEqualTo(TENANT);
|
||||
assertThat(captor.getValue().getWarehouseId()).isEqualTo(WAREHOUSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsResourceAccessWhenWarehouseDoesNotBelongToTenant() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(false);
|
||||
|
||||
assertThatThrownBy(() -> service.createLocation(TENANT, WAREHOUSE, locationRequest("A-01")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("仓库不存在");
|
||||
verify(repository, never()).insertLocation(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateLocationCodeWithinWarehouse() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
when(repository.locationCodeExists(TENANT, WAREHOUSE, "A-01", null)).thenReturn(true);
|
||||
|
||||
assertThatThrownBy(() -> service.createLocation(TENANT, WAREHOUSE, locationRequest("A-01")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("库位编码已存在");
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletDefaultsToTwoCubicMetresAndBindsOwnedLocation() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
when(repository.findWarehouse(TENANT, WAREHOUSE)).thenReturn(warehouse());
|
||||
WarehouseLocationEntity location = new WarehouseLocationEntity();
|
||||
location.setId(30001L);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, 30001L)).thenReturn(location);
|
||||
doAnswer(invocation -> {
|
||||
((WarehousePalletEntity) invocation.getArgument(0)).setId(2089000000000000002L);
|
||||
return null;
|
||||
}).when(repository).insertPallet(org.mockito.ArgumentMatchers.any());
|
||||
|
||||
Long id = service.createPallet(TENANT, WAREHOUSE,
|
||||
new PalletRequests.SaveRequest("PLT-01", "STANDARD", null,
|
||||
new BigDecimal("12.50"), "IDLE", 30001L, null));
|
||||
|
||||
assertThat(id).isEqualTo(2089000000000000002L);
|
||||
ArgumentCaptor<WarehousePalletEntity> captor = ArgumentCaptor.forClass(WarehousePalletEntity.class);
|
||||
verify(repository).insertPallet(captor.capture());
|
||||
assertThat(captor.getValue().getCapacityCbm()).isEqualByComparingTo("2.0000");
|
||||
assertThat(captor.getValue().getLocationId()).isEqualTo(30001L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsLocationFromAnotherWarehouseWhenBindingPallet() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
when(repository.findWarehouse(TENANT, WAREHOUSE)).thenReturn(warehouse());
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, 30002L)).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.createPallet(TENANT, WAREHOUSE,
|
||||
new PalletRequests.SaveRequest("PLT-01", "STANDARD", null,
|
||||
BigDecimal.ONE, "IDLE", 30002L, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("库位不存在");
|
||||
verify(repository, never()).insertPallet(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void preventsDeletingLocationThatStillHasPallets() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, 30001L)).thenReturn(new WarehouseLocationEntity());
|
||||
when(repository.locationHasPallets(TENANT, WAREHOUSE, 30001L)).thenReturn(true);
|
||||
|
||||
assertThatThrownBy(() -> service.deleteLocation(TENANT, WAREHOUSE, 30001L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("仍绑定托盘");
|
||||
verify(repository, never()).deleteLocation(30001L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preventsDeletingOccupiedPallet() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
WarehousePalletEntity pallet = new WarehousePalletEntity();
|
||||
pallet.setStatus("OCCUPIED");
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, 40001L)).thenReturn(pallet);
|
||||
|
||||
assertThatThrownBy(() -> service.deletePallet(TENANT, WAREHOUSE, 40001L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("占用中的托盘");
|
||||
verify(repository, never()).deletePallet(40001L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preventsDeletingScrappedPalletSoItsIdentityCannotBeReused() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
WarehousePalletEntity pallet = new WarehousePalletEntity();
|
||||
pallet.setStatus("SCRAPPED");
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, 40003L)).thenReturn(pallet);
|
||||
|
||||
assertThatThrownBy(() -> service.deletePallet(TENANT, WAREHOUSE, 40003L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("cannot be reused");
|
||||
verify(repository, never()).deletePallet(40003L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnknownPalletStatus() {
|
||||
when(repository.findWarehouse(TENANT, WAREHOUSE)).thenReturn(warehouse());
|
||||
|
||||
assertThatThrownBy(() -> service.createPallet(TENANT, WAREHOUSE,
|
||||
new PalletRequests.SaveRequest("PLT-01", "STANDARD", null,
|
||||
BigDecimal.ONE, "BROKEN", null, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("IDLE、OCCUPIED、SCRAPPED");
|
||||
}
|
||||
|
||||
private static LocationRequests.SaveRequest locationRequest(String code) {
|
||||
return new LocationRequests.SaveRequest("A", code, "STORAGE",
|
||||
new BigDecimal("1000.000"), "ACTIVE", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatesRestartSafePalletCodeAndUsesDefaultVirtualLocation() {
|
||||
when(repository.findWarehouse(TENANT, WAREHOUSE)).thenReturn(warehouse());
|
||||
WarehouseLocationEntity location = new WarehouseLocationEntity();
|
||||
location.setId(30003L);
|
||||
location.setLocationType("VIRTUAL");
|
||||
location.setStatus("ACTIVE");
|
||||
when(repository.findLocationByCode(TENANT, WAREHOUSE, "LOC-US-LAX-01-DEFAULT"))
|
||||
.thenReturn(location);
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
when(repository.findLocation(TENANT, WAREHOUSE, 30003L)).thenReturn(location);
|
||||
|
||||
service.createPallet(TENANT, WAREHOUSE,
|
||||
new PalletRequests.SaveRequest(null, "STANDARD", null, BigDecimal.ONE, "IDLE", null, null));
|
||||
|
||||
ArgumentCaptor<WarehousePalletEntity> captor = ArgumentCaptor.forClass(WarehousePalletEntity.class);
|
||||
verify(repository).insertPallet(captor.capture());
|
||||
assertThat(captor.getValue().getPalletCode()).matches("PLT-US-LAX-01-[0-9]{8}-[0-9]+");
|
||||
assertThat(captor.getValue().getLocationId()).isEqualTo(30003L);
|
||||
assertThat(captor.getValue().getId()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultVirtualLocationIsInsertedIdempotentlyForAnExistingWarehouse() {
|
||||
when(repository.findWarehouse(TENANT, WAREHOUSE)).thenReturn(warehouse());
|
||||
WarehouseLocationEntity resolved = new WarehouseLocationEntity();
|
||||
resolved.setId(30004L);
|
||||
resolved.setLocationType("VIRTUAL");
|
||||
resolved.setStatus("ACTIVE");
|
||||
when(repository.findLocationByCode(TENANT, WAREHOUSE, "LOC-US-LAX-01-DEFAULT"))
|
||||
.thenReturn(null, resolved);
|
||||
|
||||
WarehouseLocationEntity result = service.ensureDefaultVirtualLocation(TENANT, WAREHOUSE);
|
||||
|
||||
assertThat(result.getId()).isEqualTo(30004L);
|
||||
ArgumentCaptor<WarehouseLocationEntity> location = ArgumentCaptor.forClass(WarehouseLocationEntity.class);
|
||||
verify(repository).insertDefaultVirtualLocationIgnore(location.capture());
|
||||
assertThat(location.getValue().getLocationCode()).isEqualTo("LOC-US-LAX-01-DEFAULT");
|
||||
assertThat(location.getValue().getLocationType()).isEqualTo("VIRTUAL");
|
||||
assertThat(location.getValue().getStatus()).isEqualTo("ACTIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsAConflictingDefaultLocationCode() {
|
||||
when(repository.findWarehouse(TENANT, WAREHOUSE)).thenReturn(warehouse());
|
||||
WarehouseLocationEntity conflict = new WarehouseLocationEntity();
|
||||
conflict.setLocationType("STORAGE");
|
||||
conflict.setStatus("ACTIVE");
|
||||
when(repository.findLocationByCode(TENANT, WAREHOUSE, "LOC-US-LAX-01-DEFAULT"))
|
||||
.thenReturn(conflict);
|
||||
|
||||
assertThatThrownBy(() -> service.ensureDefaultVirtualLocation(TENANT, WAREHOUSE))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("Default virtual location code");
|
||||
verify(repository, never()).insertDefaultVirtualLocationIgnore(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrappedPalletCannotBeReactivated() {
|
||||
when(repository.warehouseOwned(TENANT, WAREHOUSE)).thenReturn(true);
|
||||
WarehousePalletEntity existing = new WarehousePalletEntity();
|
||||
existing.setId(40002L);
|
||||
existing.setPalletCode("PLT-OLD");
|
||||
existing.setStatus("SCRAPPED");
|
||||
when(repository.findPallet(TENANT, WAREHOUSE, 40002L)).thenReturn(existing);
|
||||
|
||||
assertThatThrownBy(() -> service.updatePallet(TENANT, WAREHOUSE, 40002L,
|
||||
new PalletRequests.SaveRequest(null, "STANDARD", null, BigDecimal.ONE, "IDLE", null, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("cannot be reused");
|
||||
}
|
||||
|
||||
private static WarehouseEntity warehouse() {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setId(WAREHOUSE);
|
||||
warehouse.setCustomerId(TENANT);
|
||||
warehouse.setWarehouseCode("US-LAX-01");
|
||||
return warehouse;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user