1
0
Fork 0
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:
Sebastian Frick 2026-02-19 10:10:57 +01:00
parent 05878b1ce9
commit 24a6869faf
11 changed files with 493 additions and 2 deletions

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.domain.usermanagement.UserStatus;
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.UserEntity;
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.
*
* 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)
@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 ====================
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) {
long now = System.currentTimeMillis();
javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(