1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 13:49:36 +01:00

feat(inventory): GET Endpoint für einzelnen Lagerort (StorageLocation by ID)

GetStorageLocation Use Case mit AuthorizationPort, GET /{id} Endpoint im Controller.
6 Unit Tests und 5 Integrationstests für alle Edge Cases.
This commit is contained in:
Sebastian Frick 2026-02-23 22:40:30 +01:00
parent df1d1dfdd3
commit 42c9ca9d19
5 changed files with 264 additions and 2 deletions

View file

@ -0,0 +1,121 @@
package de.effigenix.application.inventory;
import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("GetStorageLocation Use Case")
class GetStorageLocationTest {
@Mock private StorageLocationRepository storageLocationRepository;
@Mock private AuthorizationPort authPort;
private GetStorageLocation getStorageLocation;
private ActorId performedBy;
@BeforeEach
void setUp() {
getStorageLocation = new GetStorageLocation(storageLocationRepository, authPort);
performedBy = ActorId.of("admin-user");
}
private StorageLocation existingLocation(String id) {
return StorageLocation.reconstitute(
StorageLocationId.of(id),
new StorageLocationName("Kühlraum 1"),
StorageType.COLD_ROOM,
null,
true
);
}
@Test
@DisplayName("should return storage location when found")
void shouldReturnStorageLocationWhenFound() {
var locationId = "location-1";
when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
when(storageLocationRepository.findById(StorageLocationId.of(locationId)))
.thenReturn(Result.success(Optional.of(existingLocation(locationId))));
var result = getStorageLocation.execute(locationId, performedBy);
assertThat(result.isSuccess()).isTrue();
assertThat(result.unsafeGetValue().id().value()).isEqualTo(locationId);
}
@Test
@DisplayName("should fail with StorageLocationNotFound when not found")
void shouldFailWhenNotFound() {
when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
when(storageLocationRepository.findById(StorageLocationId.of("nonexistent")))
.thenReturn(Result.success(Optional.empty()));
var result = getStorageLocation.execute("nonexistent", performedBy);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StorageLocationNotFound.class);
}
@Test
@DisplayName("should fail with Unauthorized when actor lacks permission")
void shouldFailWhenUnauthorized() {
when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(false);
var result = getStorageLocation.execute("location-1", performedBy);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.Unauthorized.class);
verify(storageLocationRepository, never()).findById(any());
}
@Test
@DisplayName("should fail with RepositoryFailure when repository fails")
void shouldFailWhenRepositoryFails() {
when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
when(storageLocationRepository.findById(StorageLocationId.of("location-1")))
.thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));
var result = getStorageLocation.execute("location-1", performedBy);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.RepositoryFailure.class);
}
@Test
@DisplayName("should fail with StorageLocationNotFound when id is null")
void shouldFailWhenIdIsNull() {
when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
var result = getStorageLocation.execute(null, performedBy);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StorageLocationNotFound.class);
verify(storageLocationRepository, never()).findById(any());
}
@Test
@DisplayName("should fail with StorageLocationNotFound when id is blank")
void shouldFailWhenIdIsBlank() {
when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
var result = getStorageLocation.execute(" ", performedBy);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StorageLocationNotFound.class);
verify(storageLocationRepository, never()).findById(any());
}
}

View file

@ -3,6 +3,7 @@ package de.effigenix.infrastructure.inventory.web;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStockRequest;
import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
@ -225,6 +226,60 @@ class StorageLocationControllerIntegrationTest extends AbstractIntegrationTest {
}
}
// ==================== Einzelnen Lagerort abfragen ====================
@Test
@DisplayName("Lagerort per ID abfragen → 200")
void getStorageLocation_existing_returns200() throws Exception {
String id = createAndReturnId("Detail Test", "COLD_ROOM", "-2", "8");
mockMvc.perform(get("/api/inventory/storage-locations/" + id)
.header("Authorization", "Bearer " + adminToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(id))
.andExpect(jsonPath("$.name").value("Detail Test"))
.andExpect(jsonPath("$.storageType").value("COLD_ROOM"))
.andExpect(jsonPath("$.temperatureRange.minTemperature").value(-2))
.andExpect(jsonPath("$.temperatureRange.maxTemperature").value(8))
.andExpect(jsonPath("$.active").value(true));
}
@Test
@DisplayName("Nicht existierenden Lagerort per ID abfragen → 404")
void getStorageLocation_notFound_returns404() throws Exception {
mockMvc.perform(get("/api/inventory/storage-locations/" + UUID.randomUUID())
.header("Authorization", "Bearer " + adminToken))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
}
@Test
@DisplayName("Lagerort per ID abfragen mit STOCK_READ → 200")
void getStorageLocation_withReaderToken_returns200() throws Exception {
String id = createAndReturnId("Reader Detail", "DRY_STORAGE", null, null);
mockMvc.perform(get("/api/inventory/storage-locations/" + id)
.header("Authorization", "Bearer " + readerToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(id))
.andExpect(jsonPath("$.name").value("Reader Detail"));
}
@Test
@DisplayName("Lagerort per ID abfragen ohne passende Berechtigung → 403")
void getStorageLocation_withViewerToken_returns403() throws Exception {
mockMvc.perform(get("/api/inventory/storage-locations/" + UUID.randomUUID())
.header("Authorization", "Bearer " + viewerToken))
.andExpect(status().isForbidden());
}
@Test
@DisplayName("Lagerort per ID abfragen ohne Token → 401")
void getStorageLocation_withoutToken_returns401() throws Exception {
mockMvc.perform(get("/api/inventory/storage-locations/" + UUID.randomUUID()))
.andExpect(status().isUnauthorized());
}
// ==================== Lagerort bearbeiten (Story 1.2) ====================
@Test
@ -346,6 +401,26 @@ class StorageLocationControllerIntegrationTest extends AbstractIntegrationTest {
.andExpect(jsonPath("$.active").value(false));
}
@Test
@DisplayName("Lagerort mit Bestand deaktivieren → 409")
void deactivateStorageLocation_withStock_returns409() throws Exception {
String id = createAndReturnId("Stock Location", "DRY_STORAGE", null, null);
// Stock an diesem Lagerort anlegen
var stockRequest = new CreateStockRequest(
UUID.randomUUID().toString(), id, null, null, null);
mockMvc.perform(post("/api/inventory/stocks")
.header("Authorization", "Bearer " + adminToken)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(stockRequest)))
.andExpect(status().isCreated());
mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
.header("Authorization", "Bearer " + adminToken))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.code").value("STOCK_EXISTS_AT_LOCATION"));
}
@Test
@DisplayName("Bereits inaktiven Lagerort deaktivieren → 409")
void deactivateStorageLocation_alreadyInactive_returns409() throws Exception {