mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 13:49:36 +01:00
feat(test): Jazzer Fuzz-Tests für Production-Aggregate einführen
Coverage-guided Fuzz-Tests für ProductionOrder, Recipe und Batch. Jeder Test fuzzt create() + zufällige Sequenzen aller Mutations-Methoden, um unbehandelte Exceptions bei beliebigen Inputs aufzudecken. - jazzer-junit 0.24.0 als Test-Dependency - Maven-Profile: -Pfuzz (echtes Fuzzing), -Pfuzz-regression (Crash-Replay) - Surefire: FuzzTests im Default-Lauf ausgeschlossen, reuseForks=false - Makefile: make fuzz, make fuzz/regression, make fuzz/single - .gitignore: .cifuzz-corpus/ ausgeschlossen
This commit is contained in:
parent
74dc9a6981
commit
8a84bf5f25
6 changed files with 312 additions and 1 deletions
|
|
@ -0,0 +1,79 @@
|
|||
package de.effigenix.domain.production;
|
||||
|
||||
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
|
||||
import com.code_intelligence.jazzer.junit.FuzzTest;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Fuzz test for the Batch aggregate.
|
||||
*
|
||||
* Exercises plan() with fuzzed drafts, then applies a random sequence of
|
||||
* state-mutating operations (startProduction, recordConsumption, complete, cancel).
|
||||
* Verifies that no input combination causes an unhandled exception –
|
||||
* all invalid inputs must be caught and returned as Result.Failure.
|
||||
*
|
||||
* Run: make fuzz | make fuzz/single TEST=BatchFuzzTest
|
||||
*/
|
||||
class BatchFuzzTest {
|
||||
|
||||
@FuzzTest(maxDuration = "5m")
|
||||
void fuzzBatch(FuzzedDataProvider data) {
|
||||
var draft = new BatchDraft(
|
||||
data.consumeString(50),
|
||||
data.consumeString(30),
|
||||
data.consumeString(20),
|
||||
consumeLocalDate(data),
|
||||
consumeLocalDate(data)
|
||||
);
|
||||
|
||||
BatchNumber batchNumber;
|
||||
try {
|
||||
batchNumber = BatchNumber.generate(LocalDate.now(), 1);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Batch.plan(draft, batchNumber)) {
|
||||
case Result.Failure(var err) -> { }
|
||||
case Result.Success(var batch) -> {
|
||||
int ops = data.consumeInt(1, 10);
|
||||
for (int i = 0; i < ops; i++) {
|
||||
int op = data.consumeInt(0, 3);
|
||||
switch (op) {
|
||||
case 0 -> batch.startProduction();
|
||||
case 1 -> {
|
||||
batch.recordConsumption(new ConsumptionDraft(
|
||||
data.consumeString(50),
|
||||
data.consumeString(50),
|
||||
data.consumeString(30),
|
||||
data.consumeString(20)
|
||||
));
|
||||
}
|
||||
case 2 -> {
|
||||
batch.complete(new CompleteBatchDraft(
|
||||
data.consumeString(30),
|
||||
data.consumeString(20),
|
||||
data.consumeString(30),
|
||||
data.consumeString(20),
|
||||
data.consumeString(100)
|
||||
));
|
||||
}
|
||||
case 3 -> batch.cancel(new CancelBatchDraft(data.consumeString(600)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LocalDate consumeLocalDate(FuzzedDataProvider data) {
|
||||
if (data.consumeBoolean()) {
|
||||
return null;
|
||||
}
|
||||
int year = data.consumeInt(1900, 2100);
|
||||
int month = data.consumeInt(1, 12);
|
||||
int day = data.consumeInt(1, 28);
|
||||
return LocalDate.of(year, month, day);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package de.effigenix.domain.production;
|
||||
|
||||
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
|
||||
import com.code_intelligence.jazzer.junit.FuzzTest;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Fuzz test for the ProductionOrder aggregate.
|
||||
*
|
||||
* Exercises create() with fuzzed drafts, then applies a random sequence of
|
||||
* state-mutating operations (release, startProduction, complete, cancel, reschedule).
|
||||
* Verifies that no input combination causes an unhandled exception –
|
||||
* all invalid inputs must be caught and returned as Result.Failure.
|
||||
*
|
||||
* Run: make fuzz | make fuzz/single TEST=ProductionOrderFuzzTest
|
||||
*/
|
||||
class ProductionOrderFuzzTest {
|
||||
|
||||
@FuzzTest(maxDuration = "5m")
|
||||
void fuzzProductionOrder(FuzzedDataProvider data) {
|
||||
var draft = new ProductionOrderDraft(
|
||||
data.consumeString(50),
|
||||
data.consumeString(30),
|
||||
data.consumeString(20),
|
||||
consumeLocalDate(data),
|
||||
data.consumeString(10),
|
||||
data.consumeString(100)
|
||||
);
|
||||
|
||||
switch (ProductionOrder.create(draft)) {
|
||||
case Result.Failure(var err) -> {
|
||||
// Invalid draft – expected
|
||||
}
|
||||
case Result.Success(var order) -> {
|
||||
int ops = data.consumeInt(1, 10);
|
||||
for (int i = 0; i < ops; i++) {
|
||||
int op = data.consumeInt(0, 4);
|
||||
switch (op) {
|
||||
case 0 -> order.release();
|
||||
case 1 -> {
|
||||
try {
|
||||
order.startProduction(BatchId.of(data.consumeString(50)));
|
||||
} catch (Exception ignored) { }
|
||||
}
|
||||
case 2 -> order.complete();
|
||||
case 3 -> order.cancel(data.consumeString(50));
|
||||
case 4 -> order.reschedule(consumeLocalDate(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LocalDate consumeLocalDate(FuzzedDataProvider data) {
|
||||
if (data.consumeBoolean()) {
|
||||
return null;
|
||||
}
|
||||
int year = data.consumeInt(1900, 2100);
|
||||
int month = data.consumeInt(1, 12);
|
||||
int day = data.consumeInt(1, 28);
|
||||
return LocalDate.of(year, month, day);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package de.effigenix.domain.production;
|
||||
|
||||
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
|
||||
import com.code_intelligence.jazzer.junit.FuzzTest;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
/**
|
||||
* Fuzz test for the Recipe aggregate.
|
||||
*
|
||||
* Exercises create() with fuzzed drafts, then applies a random sequence of
|
||||
* state-mutating operations (addIngredient, addProductionStep, removeIngredient,
|
||||
* removeProductionStep, activate, archive).
|
||||
* Verifies that no input combination causes an unhandled exception –
|
||||
* all invalid inputs must be caught and returned as Result.Failure.
|
||||
*
|
||||
* Run: make fuzz | make fuzz/single TEST=RecipeFuzzTest
|
||||
*/
|
||||
class RecipeFuzzTest {
|
||||
|
||||
private static final RecipeType[] RECIPE_TYPES = RecipeType.values();
|
||||
|
||||
@FuzzTest(maxDuration = "5m")
|
||||
void fuzzRecipe(FuzzedDataProvider data) {
|
||||
var draft = new RecipeDraft(
|
||||
data.consumeString(200),
|
||||
data.consumeInt(0, 100),
|
||||
data.consumeBoolean() ? data.pickValue(RECIPE_TYPES) : null,
|
||||
data.consumeString(200),
|
||||
data.consumeInt(0, 300),
|
||||
data.consumeBoolean() ? data.consumeInt(-10, 400) : null,
|
||||
data.consumeString(30),
|
||||
data.consumeString(20),
|
||||
data.consumeString(40)
|
||||
);
|
||||
|
||||
switch (Recipe.create(draft)) {
|
||||
case Result.Failure(var err) -> { }
|
||||
case Result.Success(var recipe) -> {
|
||||
int ops = data.consumeInt(1, 12);
|
||||
for (int i = 0; i < ops; i++) {
|
||||
int op = data.consumeInt(0, 5);
|
||||
switch (op) {
|
||||
case 0 -> {
|
||||
var ingredientDraft = consumeIngredientDraft(data);
|
||||
if (ingredientDraft != null) recipe.addIngredient(ingredientDraft);
|
||||
}
|
||||
case 1 -> {
|
||||
recipe.addProductionStep(new ProductionStepDraft(
|
||||
data.consumeInt(), data.consumeString(100),
|
||||
data.consumeBoolean() ? data.consumeInt() : null,
|
||||
data.consumeBoolean() ? data.consumeInt() : null
|
||||
));
|
||||
}
|
||||
case 2 -> recipe.activate();
|
||||
case 3 -> recipe.archive();
|
||||
case 4 -> {
|
||||
if (!recipe.ingredients().isEmpty()) {
|
||||
recipe.removeIngredient(recipe.ingredients().getFirst().id());
|
||||
}
|
||||
}
|
||||
case 5 -> {
|
||||
if (!recipe.productionSteps().isEmpty()) {
|
||||
recipe.removeProductionStep(recipe.productionSteps().getFirst().stepNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IngredientDraft consumeIngredientDraft(FuzzedDataProvider data) {
|
||||
try {
|
||||
return new IngredientDraft(
|
||||
data.consumeInt(),
|
||||
data.consumeString(50),
|
||||
data.consumeString(30),
|
||||
data.consumeString(20),
|
||||
data.consumeBoolean() ? data.consumeString(40) : null,
|
||||
data.consumeBoolean()
|
||||
);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue