mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 13:59:36 +01:00
feat(inventory): StorageLocation Aggregate implementieren (#1)
Vertikaler Slice für Story 1.1 – Lagerort anlegen: Domain: StorageLocation Aggregate mit VOs (StorageLocationId, StorageLocationName, StorageType, TemperatureRange), StorageLocationError (sealed interface), Draft-Records und Repository Interface. Application: CreateStorageLocation UseCase mit Uniqueness-Check. Infrastructure: JPA Entity, Mapper, Repository, REST Controller (POST /api/inventory/storage-locations), Liquibase Migration (009), InventoryErrorHttpStatusMapper, InventoryUseCaseConfiguration. Tests: 28 Domain-Unit-Tests, 11 Integration-Tests. Closes #1
This commit is contained in:
parent
554185a012
commit
c474388f32
25 changed files with 1527 additions and 0 deletions
|
|
@ -0,0 +1,371 @@
|
|||
package de.effigenix.domain.inventory;
|
||||
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StorageLocationTest {
|
||||
|
||||
// ==================== Create ====================
|
||||
|
||||
@Nested
|
||||
@DisplayName("create()")
|
||||
class Create {
|
||||
|
||||
@Test
|
||||
@DisplayName("should create StorageLocation with valid data")
|
||||
void shouldCreateWithValidData() {
|
||||
var draft = new StorageLocationDraft("Kühlraum 1", "COLD_ROOM", "-2", "8");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
var location = result.unsafeGetValue();
|
||||
assertThat(location.id()).isNotNull();
|
||||
assertThat(location.name().value()).isEqualTo("Kühlraum 1");
|
||||
assertThat(location.storageType()).isEqualTo(StorageType.COLD_ROOM);
|
||||
assertThat(location.temperatureRange()).isNotNull();
|
||||
assertThat(location.temperatureRange().minTemperature().intValue()).isEqualTo(-2);
|
||||
assertThat(location.temperatureRange().maxTemperature().intValue()).isEqualTo(8);
|
||||
assertThat(location.active()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should create StorageLocation without temperature range")
|
||||
void shouldCreateWithoutTemperatureRange() {
|
||||
var draft = new StorageLocationDraft("Trockenlager", "DRY_STORAGE", null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
var location = result.unsafeGetValue();
|
||||
assertThat(location.temperatureRange()).isNull();
|
||||
assertThat(location.isTemperatureControlled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when name is blank")
|
||||
void shouldFailWhenNameBlank() {
|
||||
var draft = new StorageLocationDraft("", "COLD_ROOM", null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when name is null")
|
||||
void shouldFailWhenNameNull() {
|
||||
var draft = new StorageLocationDraft(null, "COLD_ROOM", null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when name exceeds 100 chars")
|
||||
void shouldFailWhenNameTooLong() {
|
||||
var longName = "A".repeat(101);
|
||||
var draft = new StorageLocationDraft(longName, "COLD_ROOM", null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should accept name with exactly 100 chars")
|
||||
void shouldAcceptNameWith100Chars() {
|
||||
var name = "A".repeat(100);
|
||||
var draft = new StorageLocationDraft(name, "DRY_STORAGE", null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when storageType is invalid")
|
||||
void shouldFailWhenStorageTypeInvalid() {
|
||||
var draft = new StorageLocationDraft("Test", "INVALID_TYPE", null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidStorageType.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when storageType is null")
|
||||
void shouldFailWhenStorageTypeNull() {
|
||||
var draft = new StorageLocationDraft("Test", null, null, null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidStorageType.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when min >= max temperature")
|
||||
void shouldFailWhenMinGreaterThanMax() {
|
||||
var draft = new StorageLocationDraft("Test", "COLD_ROOM", "10", "5");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when min == max temperature")
|
||||
void shouldFailWhenMinEqualsMax() {
|
||||
var draft = new StorageLocationDraft("Test", "COLD_ROOM", "5", "5");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when temperature below -50")
|
||||
void shouldFailWhenTemperatureBelowMinus50() {
|
||||
var draft = new StorageLocationDraft("Test", "FREEZER", "-51", "-20");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when temperature above 80")
|
||||
void shouldFailWhenTemperatureAbove80() {
|
||||
var draft = new StorageLocationDraft("Test", "DRY_STORAGE", "20", "81");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should accept boundary temperatures -50 and +80")
|
||||
void shouldAcceptBoundaryTemperatures() {
|
||||
var draft = new StorageLocationDraft("Test", "DRY_STORAGE", "-50", "80");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when temperature is not a number")
|
||||
void shouldFailWhenTemperatureNotNumber() {
|
||||
var draft = new StorageLocationDraft("Test", "COLD_ROOM", "abc", "10");
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when only min temperature provided")
|
||||
void shouldFailWhenOnlyMinTemperature() {
|
||||
var draft = new StorageLocationDraft("Test", "COLD_ROOM", "5", null);
|
||||
|
||||
var result = StorageLocation.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should create all storage types")
|
||||
void shouldCreateAllStorageTypes() {
|
||||
for (StorageType type : StorageType.values()) {
|
||||
var draft = new StorageLocationDraft("Test " + type, type.name(), null, null);
|
||||
var result = StorageLocation.create(draft);
|
||||
assertThat(result.isSuccess()).as("StorageType %s should be valid", type).isTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Update ====================
|
||||
|
||||
@Nested
|
||||
@DisplayName("update()")
|
||||
class Update {
|
||||
|
||||
@Test
|
||||
@DisplayName("should update name")
|
||||
void shouldUpdateName() {
|
||||
var location = createValidLocation();
|
||||
var updateDraft = new StorageLocationUpdateDraft("Neuer Name", null, null);
|
||||
|
||||
var result = location.update(updateDraft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(location.name().value()).isEqualTo("Neuer Name");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should update temperature range")
|
||||
void shouldUpdateTemperatureRange() {
|
||||
var location = createValidLocation();
|
||||
var updateDraft = new StorageLocationUpdateDraft(null, "-5", "12");
|
||||
|
||||
var result = location.update(updateDraft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(location.temperatureRange().minTemperature().intValue()).isEqualTo(-5);
|
||||
assertThat(location.temperatureRange().maxTemperature().intValue()).isEqualTo(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail update when name is blank")
|
||||
void shouldFailUpdateWhenNameBlank() {
|
||||
var location = createValidLocation();
|
||||
var updateDraft = new StorageLocationUpdateDraft("", null, null);
|
||||
|
||||
var result = location.update(updateDraft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should not change name when null in draft")
|
||||
void shouldNotChangeNameWhenNull() {
|
||||
var location = createValidLocation();
|
||||
var originalName = location.name().value();
|
||||
var updateDraft = new StorageLocationUpdateDraft(null, null, null);
|
||||
|
||||
var result = location.update(updateDraft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(location.name().value()).isEqualTo(originalName);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Deactivate / Activate ====================
|
||||
|
||||
@Nested
|
||||
@DisplayName("deactivate() / activate()")
|
||||
class DeactivateActivate {
|
||||
|
||||
@Test
|
||||
@DisplayName("should deactivate active location")
|
||||
void shouldDeactivate() {
|
||||
var location = createValidLocation();
|
||||
|
||||
var result = location.deactivate();
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(location.active()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail deactivating already inactive location")
|
||||
void shouldFailDeactivatingInactive() {
|
||||
var location = createValidLocation();
|
||||
location.deactivate();
|
||||
|
||||
var result = location.deactivate();
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.AlreadyInactive.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should activate inactive location")
|
||||
void shouldActivate() {
|
||||
var location = createValidLocation();
|
||||
location.deactivate();
|
||||
|
||||
var result = location.activate();
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(location.active()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail activating already active location")
|
||||
void shouldFailActivatingActive() {
|
||||
var location = createValidLocation();
|
||||
|
||||
var result = location.activate();
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.AlreadyActive.class);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Query Methods ====================
|
||||
|
||||
@Nested
|
||||
@DisplayName("query methods")
|
||||
class QueryMethods {
|
||||
|
||||
@Test
|
||||
@DisplayName("isTemperatureControlled should return true when range is set")
|
||||
void shouldReturnTrueWhenRangeSet() {
|
||||
var draft = new StorageLocationDraft("Kühlraum", "COLD_ROOM", "-2", "8");
|
||||
var location = StorageLocation.create(draft).unsafeGetValue();
|
||||
|
||||
assertThat(location.isTemperatureControlled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isTemperatureControlled should return false when range is null")
|
||||
void shouldReturnFalseWhenRangeNull() {
|
||||
var draft = new StorageLocationDraft("Trockenlager", "DRY_STORAGE", null, null);
|
||||
var location = StorageLocation.create(draft).unsafeGetValue();
|
||||
|
||||
assertThat(location.isTemperatureControlled()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Equality ====================
|
||||
|
||||
@Nested
|
||||
@DisplayName("equals / hashCode")
|
||||
class Equality {
|
||||
|
||||
@Test
|
||||
@DisplayName("should be equal if same ID")
|
||||
void shouldBeEqualBySameId() {
|
||||
var id = StorageLocationId.generate();
|
||||
var loc1 = StorageLocation.reconstitute(id, new StorageLocationName("A"), StorageType.COLD_ROOM, null, true);
|
||||
var loc2 = StorageLocation.reconstitute(id, new StorageLocationName("B"), StorageType.DRY_STORAGE, null, false);
|
||||
|
||||
assertThat(loc1).isEqualTo(loc2);
|
||||
assertThat(loc1.hashCode()).isEqualTo(loc2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should not be equal if different ID")
|
||||
void shouldNotBeEqualByDifferentId() {
|
||||
var loc1 = createValidLocation();
|
||||
var loc2 = createValidLocation();
|
||||
|
||||
assertThat(loc1).isNotEqualTo(loc2);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Helpers ====================
|
||||
|
||||
private StorageLocation createValidLocation() {
|
||||
var draft = new StorageLocationDraft("Kühlraum 1", "COLD_ROOM", "-2", "8");
|
||||
return StorageLocation.create(draft).unsafeGetValue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
package de.effigenix.infrastructure.inventory.web;
|
||||
|
||||
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.usermanagement.persistence.entity.RoleEntity;
|
||||
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
|
||||
import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository;
|
||||
import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* Integrationstests für StorageLocationController.
|
||||
*
|
||||
* Abgedeckte Testfälle: Story 1.1 – Lagerort anlegen
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
@Transactional
|
||||
@DisplayName("StorageLocation Controller Integration Tests")
|
||||
class StorageLocationControllerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
private UserJpaRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private RoleJpaRepository roleRepository;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String jwtSecret;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private long jwtExpiration;
|
||||
|
||||
private String adminToken;
|
||||
private String viewerToken;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
RoleEntity adminRole = new RoleEntity(
|
||||
UUID.randomUUID().toString(), RoleName.ADMIN, Set.of(), "Admin");
|
||||
roleRepository.save(adminRole);
|
||||
|
||||
RoleEntity viewerRole = new RoleEntity(
|
||||
UUID.randomUUID().toString(), RoleName.PRODUCTION_WORKER, Set.of(), "Viewer");
|
||||
roleRepository.save(viewerRole);
|
||||
|
||||
String adminId = UUID.randomUUID().toString();
|
||||
userRepository.save(new UserEntity(
|
||||
adminId, "inv.admin", "inv.admin@test.com",
|
||||
passwordEncoder.encode("Pass123"), Set.of(adminRole),
|
||||
"BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null));
|
||||
|
||||
String viewerId = UUID.randomUUID().toString();
|
||||
userRepository.save(new UserEntity(
|
||||
viewerId, "inv.viewer", "inv.viewer@test.com",
|
||||
passwordEncoder.encode("Pass123"), Set.of(viewerRole),
|
||||
"BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null));
|
||||
|
||||
adminToken = generateToken(adminId, "inv.admin", "STOCK_WRITE");
|
||||
viewerToken = generateToken(viewerId, "inv.viewer", "USER_READ");
|
||||
}
|
||||
|
||||
// ==================== Lagerort anlegen – Pflichtfelder ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort mit Pflichtfeldern erstellen → 201")
|
||||
void createStorageLocation_withRequiredFields_returns201() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Trockenlager 1", "DRY_STORAGE", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.id").isNotEmpty())
|
||||
.andExpect(jsonPath("$.name").value("Trockenlager 1"))
|
||||
.andExpect(jsonPath("$.storageType").value("DRY_STORAGE"))
|
||||
.andExpect(jsonPath("$.temperatureRange").isEmpty())
|
||||
.andExpect(jsonPath("$.active").value(true));
|
||||
}
|
||||
|
||||
// ==================== Lagerort mit Temperaturbereich ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort mit Temperaturbereich erstellen → 201")
|
||||
void createStorageLocation_withTemperatureRange_returns201() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Kühlraum 1", "COLD_ROOM", "-2", "8");
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.name").value("Kühlraum 1"))
|
||||
.andExpect(jsonPath("$.storageType").value("COLD_ROOM"))
|
||||
.andExpect(jsonPath("$.temperatureRange.minTemperature").value(-2))
|
||||
.andExpect(jsonPath("$.temperatureRange.maxTemperature").value(8))
|
||||
.andExpect(jsonPath("$.active").value(true));
|
||||
}
|
||||
|
||||
// ==================== Validierungsfehler ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort ohne Namen → 400")
|
||||
void createStorageLocation_withBlankName_returns400() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"", "COLD_ROOM", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort ohne StorageType → 400")
|
||||
void createStorageLocation_withBlankStorageType_returns400() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Test", "", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort mit ungültigem StorageType → 400")
|
||||
void createStorageLocation_withInvalidStorageType_returns400() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Test", "INVALID_TYPE", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("INVALID_STORAGE_TYPE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort mit min >= max Temperatur → 400")
|
||||
void createStorageLocation_withInvalidTemperatureRange_returns400() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Test", "COLD_ROOM", "10", "5");
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("INVALID_TEMPERATURE_RANGE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort mit Temperatur außerhalb des Bereichs → 400")
|
||||
void createStorageLocation_withTemperatureOutOfRange_returns400() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Test", "FREEZER", "-51", "-20");
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("INVALID_TEMPERATURE_RANGE"));
|
||||
}
|
||||
|
||||
// ==================== Duplikat-Name ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort mit doppeltem Namen → 409")
|
||||
void createStorageLocation_withDuplicateName_returns409() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Kühlraum Unique", "COLD_ROOM", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NAME_EXISTS"));
|
||||
}
|
||||
|
||||
// ==================== Autorisierung ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort erstellen ohne STOCK_WRITE → 403")
|
||||
void createStorageLocation_withViewerToken_returns403() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Kein Zugriff", "DRY_STORAGE", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + viewerToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Lagerort erstellen ohne Token → 401")
|
||||
void createStorageLocation_withoutToken_returns401() throws Exception {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Kein Token", "DRY_STORAGE", null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
// ==================== Alle StorageTypes ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("Alle StorageTypes erstellen → jeweils 201")
|
||||
void createStorageLocation_allTypes_returns201() throws Exception {
|
||||
String[] types = {"COLD_ROOM", "FREEZER", "DRY_STORAGE", "DISPLAY_COUNTER", "PRODUCTION_AREA"};
|
||||
|
||||
for (String type : types) {
|
||||
var request = new CreateStorageLocationRequest(
|
||||
"Lager " + type, type, null, null);
|
||||
|
||||
mockMvc.perform(post("/api/inventory/storage-locations")
|
||||
.header("Authorization", "Bearer " + adminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.storageType").value(type));
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Hilfsmethoden ====================
|
||||
|
||||
private String generateToken(String userId, String username, String permissions) {
|
||||
long now = System.currentTimeMillis();
|
||||
javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(
|
||||
jwtSecret.getBytes(StandardCharsets.UTF_8));
|
||||
return Jwts.builder()
|
||||
.subject(userId)
|
||||
.claim("username", username)
|
||||
.claim("permissions", permissions)
|
||||
.issuedAt(new Date(now))
|
||||
.expiration(new Date(now + jwtExpiration))
|
||||
.signWith(key)
|
||||
.compact();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue