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

fix(inventory): Audit-Logging, testbare Zeitlogik und defensive Null-Prüfung für blockBatch/unblockBatch

- AuditEvents STOCK_BATCH_BLOCKED/UNBLOCKED hinzugefügt, reason wird als Audit-Details geloggt
- LocalDate.now() aus Stock.unblockBatch() entfernt, referenceDate als Parameter (Application Layer übergibt)
- Defensive Null-Prüfung für expiryDate in unblockBatch MHD-Check
- Ticket 003 erstellt zur Klärung ob reason im Domain-Model persistiert werden soll
This commit is contained in:
Sebastian Frick 2026-02-20 00:17:03 +01:00
parent e7c3258f07
commit d963d7fccc
10 changed files with 112 additions and 33 deletions

View file

@ -1,11 +1,13 @@
package de.effigenix.application.inventory;
import de.effigenix.application.inventory.command.BlockStockBatchCommand;
import de.effigenix.application.usermanagement.AuditLogger;
import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.UnitOfMeasure;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -29,15 +31,17 @@ import static org.mockito.Mockito.*;
class BlockStockBatchTest {
@Mock private StockRepository stockRepository;
@Mock private AuditLogger auditLogger;
private BlockStockBatch blockStockBatch;
private StockBatchId batchId;
private Stock existingStock;
private BlockStockBatchCommand validCommand;
private final ActorId actor = ActorId.of("test-user");
@BeforeEach
void setUp() {
blockStockBatch = new BlockStockBatch(stockRepository);
blockStockBatch = new BlockStockBatch(stockRepository, auditLogger);
batchId = StockBatchId.of("batch-1");
var batch = StockBatch.reconstitute(
@ -67,11 +71,12 @@ class BlockStockBatchTest {
.thenReturn(Result.success(Optional.of(existingStock)));
when(stockRepository.save(any())).thenReturn(Result.success(null));
var result = blockStockBatch.execute(validCommand);
var result = blockStockBatch.execute(validCommand, actor);
assertThat(result.isSuccess()).isTrue();
verify(stockRepository).save(existingStock);
assertThat(existingStock.batches().getFirst().status()).isEqualTo(StockBatchStatus.BLOCKED);
verify(auditLogger).log(any(), eq("batch-1"), eq("Reason: Quality issue"), eq(actor));
}
@Test
@ -80,11 +85,12 @@ class BlockStockBatchTest {
when(stockRepository.findById(StockId.of("stock-1")))
.thenReturn(Result.success(Optional.empty()));
var result = blockStockBatch.execute(validCommand);
var result = blockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.StockNotFound.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
@Test
@ -93,11 +99,12 @@ class BlockStockBatchTest {
when(stockRepository.findById(StockId.of("stock-1")))
.thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));
var result = blockStockBatch.execute(validCommand);
var result = blockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.RepositoryFailure.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
@Test
@ -108,10 +115,11 @@ class BlockStockBatchTest {
when(stockRepository.save(any()))
.thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));
var result = blockStockBatch.execute(validCommand);
var result = blockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.RepositoryFailure.class);
verifyNoInteractions(auditLogger);
}
@Test
@ -121,11 +129,12 @@ class BlockStockBatchTest {
.thenReturn(Result.success(Optional.of(existingStock)));
var cmd = new BlockStockBatchCommand("stock-1", "nonexistent", "Quality issue");
var result = blockStockBatch.execute(cmd);
var result = blockStockBatch.execute(cmd, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.BatchNotFound.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
@Test
@ -149,10 +158,11 @@ class BlockStockBatchTest {
when(stockRepository.findById(StockId.of("stock-1")))
.thenReturn(Result.success(Optional.of(stock)));
var result = blockStockBatch.execute(validCommand);
var result = blockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.BatchAlreadyBlocked.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
}

View file

@ -1,11 +1,13 @@
package de.effigenix.application.inventory;
import de.effigenix.application.inventory.command.UnblockStockBatchCommand;
import de.effigenix.application.usermanagement.AuditLogger;
import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.UnitOfMeasure;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -29,15 +31,17 @@ import static org.mockito.Mockito.*;
class UnblockStockBatchTest {
@Mock private StockRepository stockRepository;
@Mock private AuditLogger auditLogger;
private UnblockStockBatch unblockStockBatch;
private StockBatchId batchId;
private Stock existingStock;
private UnblockStockBatchCommand validCommand;
private final ActorId actor = ActorId.of("test-user");
@BeforeEach
void setUp() {
unblockStockBatch = new UnblockStockBatch(stockRepository);
unblockStockBatch = new UnblockStockBatch(stockRepository, auditLogger);
batchId = StockBatchId.of("batch-1");
var batch = StockBatch.reconstitute(
@ -67,11 +71,12 @@ class UnblockStockBatchTest {
.thenReturn(Result.success(Optional.of(existingStock)));
when(stockRepository.save(any())).thenReturn(Result.success(null));
var result = unblockStockBatch.execute(validCommand);
var result = unblockStockBatch.execute(validCommand, actor);
assertThat(result.isSuccess()).isTrue();
verify(stockRepository).save(existingStock);
assertThat(existingStock.batches().getFirst().status()).isEqualTo(StockBatchStatus.AVAILABLE);
verify(auditLogger).log(any(), eq("batch-1"), eq(actor));
}
@Test
@ -80,11 +85,12 @@ class UnblockStockBatchTest {
when(stockRepository.findById(StockId.of("stock-1")))
.thenReturn(Result.success(Optional.empty()));
var result = unblockStockBatch.execute(validCommand);
var result = unblockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.StockNotFound.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
@Test
@ -93,11 +99,12 @@ class UnblockStockBatchTest {
when(stockRepository.findById(StockId.of("stock-1")))
.thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));
var result = unblockStockBatch.execute(validCommand);
var result = unblockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.RepositoryFailure.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
@Test
@ -108,10 +115,11 @@ class UnblockStockBatchTest {
when(stockRepository.save(any()))
.thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));
var result = unblockStockBatch.execute(validCommand);
var result = unblockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.RepositoryFailure.class);
verifyNoInteractions(auditLogger);
}
@Test
@ -121,11 +129,12 @@ class UnblockStockBatchTest {
.thenReturn(Result.success(Optional.of(existingStock)));
var cmd = new UnblockStockBatchCommand("stock-1", "nonexistent");
var result = unblockStockBatch.execute(cmd);
var result = unblockStockBatch.execute(cmd, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.BatchNotFound.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
@Test
@ -149,10 +158,11 @@ class UnblockStockBatchTest {
when(stockRepository.findById(StockId.of("stock-1")))
.thenReturn(Result.success(Optional.of(stock)));
var result = unblockStockBatch.execute(validCommand);
var result = unblockStockBatch.execute(validCommand, actor);
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.BatchNotBlocked.class);
verify(stockRepository, never()).save(any());
verifyNoInteractions(auditLogger);
}
}

View file

@ -513,7 +513,7 @@ class StockTest {
var stock = createStockWithBatch("10", UnitOfMeasure.KILOGRAM, StockBatchStatus.BLOCKED);
var batchId = stock.batches().getFirst().id();
var result = stock.unblockBatch(batchId);
var result = stock.unblockBatch(batchId, LocalDate.now());
assertThat(result.isSuccess()).isTrue();
assertThat(stock.batches().getFirst().status()).isEqualTo(StockBatchStatus.AVAILABLE);
@ -539,7 +539,7 @@ class StockTest {
);
var batchId = stock.batches().getFirst().id();
var result = stock.unblockBatch(batchId);
var result = stock.unblockBatch(batchId, LocalDate.now());
assertThat(result.isSuccess()).isTrue();
assertThat(stock.batches().getFirst().status()).isEqualTo(StockBatchStatus.EXPIRING_SOON);
@ -565,7 +565,7 @@ class StockTest {
);
var batchId = stock.batches().getFirst().id();
var result = stock.unblockBatch(batchId);
var result = stock.unblockBatch(batchId, LocalDate.now());
assertThat(result.isSuccess()).isTrue();
assertThat(stock.batches().getFirst().status()).isEqualTo(StockBatchStatus.AVAILABLE);
@ -577,7 +577,7 @@ class StockTest {
var stock = createStockWithBatch("10", UnitOfMeasure.KILOGRAM, StockBatchStatus.AVAILABLE);
var batchId = stock.batches().getFirst().id();
var result = stock.unblockBatch(batchId);
var result = stock.unblockBatch(batchId, LocalDate.now());
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.BatchNotBlocked.class);
@ -588,7 +588,7 @@ class StockTest {
void shouldFailWhenBatchNotFound() {
var stock = createValidStock();
var result = stock.unblockBatch(StockBatchId.of("nonexistent"));
var result = stock.unblockBatch(StockBatchId.of("nonexistent"), LocalDate.now());
assertThat(result.isFailure()).isTrue();
assertThat(result.unsafeGetError()).isInstanceOf(StockError.BatchNotFound.class);