mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 13:59:36 +01:00
feat(inventory): StorageLocation bearbeiten und (de-)aktivieren (#2)
Use Cases: UpdateStorageLocation, DeactivateStorageLocation, ActivateStorageLocation
Endpoints: PUT /{id}, PATCH /{id}/deactivate, PATCH /{id}/activate
Repository: existsByNameAndIdNot für Uniqueness-Check bei Update
Tests: 14 neue Integrationstests (25 gesamt)
This commit is contained in:
parent
05878b1ce9
commit
24a6869faf
11 changed files with 493 additions and 2 deletions
|
|
@ -0,0 +1,47 @@
|
||||||
|
package de.effigenix.application.inventory;
|
||||||
|
|
||||||
|
import de.effigenix.domain.inventory.*;
|
||||||
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.security.ActorId;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public class ActivateStorageLocation {
|
||||||
|
|
||||||
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
|
||||||
|
public ActivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||||
|
// 1. Laden
|
||||||
|
var locationId = StorageLocationId.of(storageLocationId);
|
||||||
|
StorageLocation location;
|
||||||
|
switch (storageLocationRepository.findById(locationId)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var opt) -> {
|
||||||
|
if (opt.isEmpty()) {
|
||||||
|
return Result.failure(new StorageLocationError.StorageLocationNotFound(storageLocationId));
|
||||||
|
}
|
||||||
|
location = opt.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Aktivieren
|
||||||
|
switch (location.activate()) {
|
||||||
|
case Result.Failure(var err) -> { return Result.failure(err); }
|
||||||
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Speichern
|
||||||
|
switch (storageLocationRepository.save(location)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package de.effigenix.application.inventory;
|
||||||
|
|
||||||
|
import de.effigenix.domain.inventory.*;
|
||||||
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.security.ActorId;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public class DeactivateStorageLocation {
|
||||||
|
|
||||||
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
|
||||||
|
public DeactivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||||
|
// 1. Laden
|
||||||
|
var locationId = StorageLocationId.of(storageLocationId);
|
||||||
|
StorageLocation location;
|
||||||
|
switch (storageLocationRepository.findById(locationId)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var opt) -> {
|
||||||
|
if (opt.isEmpty()) {
|
||||||
|
return Result.failure(new StorageLocationError.StorageLocationNotFound(storageLocationId));
|
||||||
|
}
|
||||||
|
location = opt.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Stock-Existenz prüfen, wenn Stock BC implementiert ist
|
||||||
|
// Akzeptanzkriterium: Deaktivierung schlägt fehl, wenn Stock am Lagerort existiert
|
||||||
|
|
||||||
|
// 2. Deaktivieren
|
||||||
|
switch (location.deactivate()) {
|
||||||
|
case Result.Failure(var err) -> { return Result.failure(err); }
|
||||||
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Speichern
|
||||||
|
switch (storageLocationRepository.save(location)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package de.effigenix.application.inventory;
|
||||||
|
|
||||||
|
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
||||||
|
import de.effigenix.domain.inventory.*;
|
||||||
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.security.ActorId;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public class UpdateStorageLocation {
|
||||||
|
|
||||||
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
|
||||||
|
public UpdateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<StorageLocationError, StorageLocation> execute(UpdateStorageLocationCommand cmd, ActorId performedBy) {
|
||||||
|
// 1. Laden
|
||||||
|
var locationId = StorageLocationId.of(cmd.storageLocationId());
|
||||||
|
StorageLocation location;
|
||||||
|
switch (storageLocationRepository.findById(locationId)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var opt) -> {
|
||||||
|
if (opt.isEmpty()) {
|
||||||
|
return Result.failure(new StorageLocationError.StorageLocationNotFound(cmd.storageLocationId()));
|
||||||
|
}
|
||||||
|
location = opt.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Draft bauen + Aggregate validieren lassen
|
||||||
|
var draft = new StorageLocationUpdateDraft(cmd.name(), cmd.minTemperature(), cmd.maxTemperature());
|
||||||
|
switch (location.update(draft)) {
|
||||||
|
case Result.Failure(var err) -> { return Result.failure(err); }
|
||||||
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Uniqueness-Check (nur wenn Name geändert wurde)
|
||||||
|
if (cmd.name() != null) {
|
||||||
|
switch (storageLocationRepository.existsByNameAndIdNot(location.name(), locationId)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var exists) -> {
|
||||||
|
if (exists) {
|
||||||
|
return Result.failure(new StorageLocationError.NameAlreadyExists(cmd.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Speichern
|
||||||
|
switch (storageLocationRepository.save(location)) {
|
||||||
|
case Result.Failure(var err) ->
|
||||||
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package de.effigenix.application.inventory.command;
|
||||||
|
|
||||||
|
public record UpdateStorageLocationCommand(
|
||||||
|
String storageLocationId,
|
||||||
|
String name,
|
||||||
|
String minTemperature,
|
||||||
|
String maxTemperature
|
||||||
|
) {}
|
||||||
|
|
@ -18,5 +18,7 @@ public interface StorageLocationRepository {
|
||||||
|
|
||||||
Result<RepositoryError, Boolean> existsByName(StorageLocationName name);
|
Result<RepositoryError, Boolean> existsByName(StorageLocationName name);
|
||||||
|
|
||||||
|
Result<RepositoryError, Boolean> existsByNameAndIdNot(StorageLocationName name, StorageLocationId id);
|
||||||
|
|
||||||
Result<RepositoryError, Void> save(StorageLocation location);
|
Result<RepositoryError, Void> save(StorageLocation location);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package de.effigenix.infrastructure.config;
|
package de.effigenix.infrastructure.config;
|
||||||
|
|
||||||
|
import de.effigenix.application.inventory.ActivateStorageLocation;
|
||||||
import de.effigenix.application.inventory.CreateStorageLocation;
|
import de.effigenix.application.inventory.CreateStorageLocation;
|
||||||
|
import de.effigenix.application.inventory.DeactivateStorageLocation;
|
||||||
|
import de.effigenix.application.inventory.UpdateStorageLocation;
|
||||||
import de.effigenix.domain.inventory.StorageLocationRepository;
|
import de.effigenix.domain.inventory.StorageLocationRepository;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
@ -14,4 +17,19 @@ public class InventoryUseCaseConfiguration {
|
||||||
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
return new CreateStorageLocation(storageLocationRepository);
|
return new CreateStorageLocation(storageLocationRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UpdateStorageLocation updateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
|
return new UpdateStorageLocation(storageLocationRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DeactivateStorageLocation deactivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
|
return new DeactivateStorageLocation(storageLocationRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ActivateStorageLocation activateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||||
|
return new ActivateStorageLocation(storageLocationRepository);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,16 @@ public class JpaStorageLocationRepository implements StorageLocationRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Boolean> existsByNameAndIdNot(StorageLocationName name, StorageLocationId id) {
|
||||||
|
try {
|
||||||
|
return Result.success(jpaRepository.existsByNameAndIdNot(name.value(), id.value()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByNameAndIdNot", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<RepositoryError, Void> save(StorageLocation location) {
|
public Result<RepositoryError, Void> save(StorageLocation location) {
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,6 @@ public interface StorageLocationJpaRepository extends JpaRepository<StorageLocat
|
||||||
List<StorageLocationEntity> findByActiveTrue();
|
List<StorageLocationEntity> findByActiveTrue();
|
||||||
|
|
||||||
boolean existsByName(String name);
|
boolean existsByName(String name);
|
||||||
|
|
||||||
|
boolean existsByNameAndIdNot(String name, String id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
package de.effigenix.infrastructure.inventory.web.controller;
|
package de.effigenix.infrastructure.inventory.web.controller;
|
||||||
|
|
||||||
|
import de.effigenix.application.inventory.ActivateStorageLocation;
|
||||||
import de.effigenix.application.inventory.CreateStorageLocation;
|
import de.effigenix.application.inventory.CreateStorageLocation;
|
||||||
|
import de.effigenix.application.inventory.DeactivateStorageLocation;
|
||||||
|
import de.effigenix.application.inventory.UpdateStorageLocation;
|
||||||
import de.effigenix.application.inventory.command.CreateStorageLocationCommand;
|
import de.effigenix.application.inventory.command.CreateStorageLocationCommand;
|
||||||
|
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
||||||
import de.effigenix.domain.inventory.StorageLocationError;
|
import de.effigenix.domain.inventory.StorageLocationError;
|
||||||
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
|
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
|
||||||
import de.effigenix.infrastructure.inventory.web.dto.StorageLocationResponse;
|
import de.effigenix.infrastructure.inventory.web.dto.StorageLocationResponse;
|
||||||
|
import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
@ -26,9 +31,20 @@ public class StorageLocationController {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(StorageLocationController.class);
|
private static final Logger logger = LoggerFactory.getLogger(StorageLocationController.class);
|
||||||
|
|
||||||
private final CreateStorageLocation createStorageLocation;
|
private final CreateStorageLocation createStorageLocation;
|
||||||
|
private final UpdateStorageLocation updateStorageLocation;
|
||||||
|
private final DeactivateStorageLocation deactivateStorageLocation;
|
||||||
|
private final ActivateStorageLocation activateStorageLocation;
|
||||||
|
|
||||||
public StorageLocationController(CreateStorageLocation createStorageLocation) {
|
public StorageLocationController(
|
||||||
|
CreateStorageLocation createStorageLocation,
|
||||||
|
UpdateStorageLocation updateStorageLocation,
|
||||||
|
DeactivateStorageLocation deactivateStorageLocation,
|
||||||
|
ActivateStorageLocation activateStorageLocation
|
||||||
|
) {
|
||||||
this.createStorageLocation = createStorageLocation;
|
this.createStorageLocation = createStorageLocation;
|
||||||
|
this.updateStorageLocation = updateStorageLocation;
|
||||||
|
this.deactivateStorageLocation = deactivateStorageLocation;
|
||||||
|
this.activateStorageLocation = activateStorageLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
|
@ -55,6 +71,68 @@ public class StorageLocationController {
|
||||||
.body(StorageLocationResponse.from(result.unsafeGetValue()));
|
.body(StorageLocationResponse.from(result.unsafeGetValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAuthority('STOCK_WRITE')")
|
||||||
|
public ResponseEntity<StorageLocationResponse> updateStorageLocation(
|
||||||
|
@PathVariable("id") String storageLocationId,
|
||||||
|
@RequestBody UpdateStorageLocationRequest request,
|
||||||
|
Authentication authentication
|
||||||
|
) {
|
||||||
|
var actorId = extractActorId(authentication);
|
||||||
|
logger.info("Updating storage location: {} by actor: {}", storageLocationId, actorId.value());
|
||||||
|
|
||||||
|
var cmd = new UpdateStorageLocationCommand(
|
||||||
|
storageLocationId, request.name(),
|
||||||
|
request.minTemperature(), request.maxTemperature()
|
||||||
|
);
|
||||||
|
var result = updateStorageLocation.execute(cmd, actorId);
|
||||||
|
|
||||||
|
if (result.isFailure()) {
|
||||||
|
throw new StorageLocationDomainErrorException(result.unsafeGetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Storage location updated: {}", storageLocationId);
|
||||||
|
return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}/deactivate")
|
||||||
|
@PreAuthorize("hasAuthority('STOCK_WRITE')")
|
||||||
|
public ResponseEntity<StorageLocationResponse> deactivateStorageLocation(
|
||||||
|
@PathVariable("id") String storageLocationId,
|
||||||
|
Authentication authentication
|
||||||
|
) {
|
||||||
|
var actorId = extractActorId(authentication);
|
||||||
|
logger.info("Deactivating storage location: {} by actor: {}", storageLocationId, actorId.value());
|
||||||
|
|
||||||
|
var result = deactivateStorageLocation.execute(storageLocationId, actorId);
|
||||||
|
|
||||||
|
if (result.isFailure()) {
|
||||||
|
throw new StorageLocationDomainErrorException(result.unsafeGetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Storage location deactivated: {}", storageLocationId);
|
||||||
|
return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}/activate")
|
||||||
|
@PreAuthorize("hasAuthority('STOCK_WRITE')")
|
||||||
|
public ResponseEntity<StorageLocationResponse> activateStorageLocation(
|
||||||
|
@PathVariable("id") String storageLocationId,
|
||||||
|
Authentication authentication
|
||||||
|
) {
|
||||||
|
var actorId = extractActorId(authentication);
|
||||||
|
logger.info("Activating storage location: {} by actor: {}", storageLocationId, actorId.value());
|
||||||
|
|
||||||
|
var result = activateStorageLocation.execute(storageLocationId, actorId);
|
||||||
|
|
||||||
|
if (result.isFailure()) {
|
||||||
|
throw new StorageLocationDomainErrorException(result.unsafeGetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Storage location activated: {}", storageLocationId);
|
||||||
|
return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue()));
|
||||||
|
}
|
||||||
|
|
||||||
private ActorId extractActorId(Authentication authentication) {
|
private ActorId extractActorId(Authentication authentication) {
|
||||||
if (authentication == null || authentication.getName() == null) {
|
if (authentication == null || authentication.getName() == null) {
|
||||||
throw new IllegalStateException("No authentication found in SecurityContext");
|
throw new IllegalStateException("No authentication found in SecurityContext");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package de.effigenix.infrastructure.inventory.web.dto;
|
||||||
|
|
||||||
|
public record UpdateStorageLocationRequest(
|
||||||
|
String name,
|
||||||
|
String minTemperature,
|
||||||
|
String maxTemperature
|
||||||
|
) {}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import de.effigenix.domain.usermanagement.RoleName;
|
import de.effigenix.domain.usermanagement.RoleName;
|
||||||
import de.effigenix.domain.usermanagement.UserStatus;
|
import de.effigenix.domain.usermanagement.UserStatus;
|
||||||
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
|
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
|
||||||
|
import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest;
|
||||||
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
|
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
|
||||||
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
|
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
|
||||||
import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository;
|
import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository;
|
||||||
|
|
@ -35,7 +36,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
/**
|
/**
|
||||||
* Integrationstests für StorageLocationController.
|
* Integrationstests für StorageLocationController.
|
||||||
*
|
*
|
||||||
* Abgedeckte Testfälle: Story 1.1 – Lagerort anlegen
|
* Abgedeckte Testfälle:
|
||||||
|
* - Story 1.1 – Lagerort anlegen
|
||||||
|
* - Story 1.2 – Lagerort bearbeiten und (de-)aktivieren
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
|
|
@ -273,8 +276,212 @@ class StorageLocationControllerIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Lagerort bearbeiten (Story 1.2) ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort Name ändern → 200")
|
||||||
|
void updateStorageLocation_changeName_returns200() throws Exception {
|
||||||
|
String id = createAndReturnId("Update Test", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
var request = new UpdateStorageLocationRequest("Neuer Name", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + id)
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.id").value(id))
|
||||||
|
.andExpect(jsonPath("$.name").value("Neuer Name"))
|
||||||
|
.andExpect(jsonPath("$.storageType").value("DRY_STORAGE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort Temperaturbereich ändern → 200")
|
||||||
|
void updateStorageLocation_changeTemperatureRange_returns200() throws Exception {
|
||||||
|
String id = createAndReturnId("Temp Update", "COLD_ROOM", "-2", "8");
|
||||||
|
|
||||||
|
var request = new UpdateStorageLocationRequest(null, "-5", "12");
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + id)
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.name").value("Temp Update"))
|
||||||
|
.andExpect(jsonPath("$.temperatureRange.minTemperature").value(-5))
|
||||||
|
.andExpect(jsonPath("$.temperatureRange.maxTemperature").value(12));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort mit ungültigem Namen aktualisieren → 400")
|
||||||
|
void updateStorageLocation_withBlankName_returns400() throws Exception {
|
||||||
|
String id = createAndReturnId("Blank Update", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
var request = new UpdateStorageLocationRequest("", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + id)
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.code").value("INVALID_STORAGE_LOCATION_NAME"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort mit doppeltem Namen aktualisieren → 409")
|
||||||
|
void updateStorageLocation_withDuplicateName_returns409() throws Exception {
|
||||||
|
createAndReturnId("Existing Name", "DRY_STORAGE", null, null);
|
||||||
|
String id = createAndReturnId("To Rename", "COLD_ROOM", null, null);
|
||||||
|
|
||||||
|
var request = new UpdateStorageLocationRequest("Existing Name", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + id)
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isConflict())
|
||||||
|
.andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NAME_EXISTS"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort mit eigenem Namen aktualisieren → 200 (kein Duplikat)")
|
||||||
|
void updateStorageLocation_withOwnName_returns200() throws Exception {
|
||||||
|
String id = createAndReturnId("Same Name", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
var request = new UpdateStorageLocationRequest("Same Name", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + id)
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.name").value("Same Name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Nicht existierenden Lagerort aktualisieren → 404")
|
||||||
|
void updateStorageLocation_notFound_returns404() throws Exception {
|
||||||
|
var request = new UpdateStorageLocationRequest("New Name", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + UUID.randomUUID())
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort aktualisieren ohne STOCK_WRITE → 403")
|
||||||
|
void updateStorageLocation_withViewerToken_returns403() throws Exception {
|
||||||
|
var request = new UpdateStorageLocationRequest("Name", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/inventory/storage-locations/" + UUID.randomUUID())
|
||||||
|
.header("Authorization", "Bearer " + viewerToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Lagerort deaktivieren (Story 1.2) ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Aktiven Lagerort deaktivieren → 200")
|
||||||
|
void deactivateStorageLocation_active_returns200() throws Exception {
|
||||||
|
String id = createAndReturnId("Deactivate Test", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.id").value(id))
|
||||||
|
.andExpect(jsonPath("$.active").value(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Bereits inaktiven Lagerort deaktivieren → 409")
|
||||||
|
void deactivateStorageLocation_alreadyInactive_returns409() throws Exception {
|
||||||
|
String id = createAndReturnId("Double Deactivate", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isConflict())
|
||||||
|
.andExpect(jsonPath("$.code").value("ALREADY_INACTIVE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Nicht existierenden Lagerort deaktivieren → 404")
|
||||||
|
void deactivateStorageLocation_notFound_returns404() throws Exception {
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + UUID.randomUUID() + "/deactivate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Lagerort deaktivieren ohne STOCK_WRITE → 403")
|
||||||
|
void deactivateStorageLocation_withViewerToken_returns403() throws Exception {
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + UUID.randomUUID() + "/deactivate")
|
||||||
|
.header("Authorization", "Bearer " + viewerToken))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Lagerort aktivieren (Story 1.2) ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Inaktiven Lagerort aktivieren → 200")
|
||||||
|
void activateStorageLocation_inactive_returns200() throws Exception {
|
||||||
|
String id = createAndReturnId("Activate Test", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/activate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.id").value(id))
|
||||||
|
.andExpect(jsonPath("$.active").value(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Bereits aktiven Lagerort aktivieren → 409")
|
||||||
|
void activateStorageLocation_alreadyActive_returns409() throws Exception {
|
||||||
|
String id = createAndReturnId("Double Activate", "DRY_STORAGE", null, null);
|
||||||
|
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/activate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isConflict())
|
||||||
|
.andExpect(jsonPath("$.code").value("ALREADY_ACTIVE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Nicht existierenden Lagerort aktivieren → 404")
|
||||||
|
void activateStorageLocation_notFound_returns404() throws Exception {
|
||||||
|
mockMvc.perform(patch("/api/inventory/storage-locations/" + UUID.randomUUID() + "/activate")
|
||||||
|
.header("Authorization", "Bearer " + adminToken))
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Hilfsmethoden ====================
|
// ==================== Hilfsmethoden ====================
|
||||||
|
|
||||||
|
private String createAndReturnId(String name, String storageType, String minTemp, String maxTemp) throws Exception {
|
||||||
|
var request = new CreateStorageLocationRequest(name, storageType, minTemp, maxTemp);
|
||||||
|
|
||||||
|
var result = mockMvc.perform(post("/api/inventory/storage-locations")
|
||||||
|
.header("Authorization", "Bearer " + adminToken)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText();
|
||||||
|
}
|
||||||
|
|
||||||
private String generateToken(String userId, String username, String permissions) {
|
private String generateToken(String userId, String username, String permissions) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(
|
javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue