mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 13:59:36 +01:00
feat(production): Recipe Aggregate als Full Vertical Slice (#26)
Domain: Recipe Aggregate Root mit create(RecipeDraft), RecipeId, RecipeName, RecipeType, RecipeStatus, RecipeError, RecipeRepository Interface. Application: CreateRecipe Use Case mit Name+Version Uniqueness-Check. Infrastructure: JPA Entity/Mapper/Repository, REST POST /api/recipes, Liquibase Migration, ProductionErrorHttpStatusMapper, Spring Config. Tests: 15 Unit Tests für Recipe Aggregate (75 total im Production BC).
This commit is contained in:
parent
24a6869faf
commit
9b9b7311d1
23 changed files with 1095 additions and 0 deletions
|
|
@ -0,0 +1,230 @@
|
|||
package de.effigenix.domain.production;
|
||||
|
||||
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;
|
||||
|
||||
@DisplayName("Recipe Aggregate")
|
||||
class RecipeTest {
|
||||
|
||||
private RecipeDraft validDraft() {
|
||||
return new RecipeDraft(
|
||||
"Bratwurst Grob", 1, RecipeType.FINISHED_PRODUCT,
|
||||
"Grobe Bratwurst nach Hausrezept", 85,
|
||||
14, "100", "KILOGRAM"
|
||||
);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("create()")
|
||||
class Create {
|
||||
|
||||
@Test
|
||||
@DisplayName("should create recipe in DRAFT status with valid inputs")
|
||||
void should_CreateInDraftStatus_When_ValidInputs() {
|
||||
var result = Recipe.create(validDraft());
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
var recipe = result.unsafeGetValue();
|
||||
assertThat(recipe.id()).isNotNull();
|
||||
assertThat(recipe.name().value()).isEqualTo("Bratwurst Grob");
|
||||
assertThat(recipe.version()).isEqualTo(1);
|
||||
assertThat(recipe.type()).isEqualTo(RecipeType.FINISHED_PRODUCT);
|
||||
assertThat(recipe.description()).isEqualTo("Grobe Bratwurst nach Hausrezept");
|
||||
assertThat(recipe.yieldPercentage().value()).isEqualTo(85);
|
||||
assertThat(recipe.shelfLifeDays()).isEqualTo(14);
|
||||
assertThat(recipe.outputQuantity().amount()).isEqualByComparingTo("100");
|
||||
assertThat(recipe.outputQuantity().uom()).isEqualTo(UnitOfMeasure.KILOGRAM);
|
||||
assertThat(recipe.status()).isEqualTo(RecipeStatus.DRAFT);
|
||||
assertThat(recipe.createdAt()).isNotNull();
|
||||
assertThat(recipe.updatedAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when name is blank")
|
||||
void should_Fail_When_NameIsBlank() {
|
||||
var draft = new RecipeDraft(
|
||||
"", 1, RecipeType.FINISHED_PRODUCT, null, 85, 14, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when name is null")
|
||||
void should_Fail_When_NameIsNull() {
|
||||
var draft = new RecipeDraft(
|
||||
null, 1, RecipeType.FINISHED_PRODUCT, null, 85, 14, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when version is less than 1")
|
||||
void should_Fail_When_VersionLessThanOne() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 0, RecipeType.FINISHED_PRODUCT, null, 85, 14, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when yield percentage is 0")
|
||||
void should_Fail_When_YieldPercentageIsZero() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 0, 14, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when yield percentage is 201")
|
||||
void should_Fail_When_YieldPercentageIs201() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 201, 14, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when shelf life is 0 for FINISHED_PRODUCT")
|
||||
void should_Fail_When_ShelfLifeZeroForFinishedProduct() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 85, 0, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.InvalidShelfLife.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when shelf life is null for FINISHED_PRODUCT")
|
||||
void should_Fail_When_ShelfLifeNullForFinishedProduct() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 85, null, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.InvalidShelfLife.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when shelf life is 0 for INTERMEDIATE")
|
||||
void should_Fail_When_ShelfLifeZeroForIntermediate() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.INTERMEDIATE, null, 85, 0, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.InvalidShelfLife.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should allow null shelf life for RAW_MATERIAL")
|
||||
void should_AllowNullShelfLife_When_RawMaterial() {
|
||||
var draft = new RecipeDraft(
|
||||
"Schweinefleisch", 1, RecipeType.RAW_MATERIAL, null, 100, null, "50", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().shelfLifeDays()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when output quantity is zero")
|
||||
void should_Fail_When_OutputQuantityZero() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 85, 14, "0", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should fail when output UoM is invalid")
|
||||
void should_Fail_When_OutputUomInvalid() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 85, 14, "100", "INVALID"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should allow description to be null")
|
||||
void should_AllowNullDescription() {
|
||||
var draft = new RecipeDraft(
|
||||
"Test", 1, RecipeType.FINISHED_PRODUCT, null, 85, 14, "100", "KILOGRAM"
|
||||
);
|
||||
|
||||
var result = Recipe.create(draft);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().description()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Equality")
|
||||
class Equality {
|
||||
|
||||
@Test
|
||||
@DisplayName("should be equal when same ID")
|
||||
void should_BeEqual_When_SameId() {
|
||||
var recipe1 = Recipe.create(validDraft()).unsafeGetValue();
|
||||
var recipe2 = Recipe.reconstitute(
|
||||
recipe1.id(), new RecipeName("Other"), 2, RecipeType.RAW_MATERIAL,
|
||||
null, new YieldPercentage(100), null,
|
||||
Quantity.of(new java.math.BigDecimal("50"), UnitOfMeasure.KILOGRAM).unsafeGetValue(),
|
||||
RecipeStatus.ACTIVE, recipe1.createdAt(), recipe1.updatedAt()
|
||||
);
|
||||
|
||||
assertThat(recipe1).isEqualTo(recipe2);
|
||||
assertThat(recipe1.hashCode()).isEqualTo(recipe2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should not be equal when different ID")
|
||||
void should_NotBeEqual_When_DifferentId() {
|
||||
var recipe1 = Recipe.create(validDraft()).unsafeGetValue();
|
||||
var recipe2 = Recipe.create(validDraft()).unsafeGetValue();
|
||||
|
||||
assertThat(recipe1).isNotEqualTo(recipe2);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue