From c8a46a7203bf452adafc759dc18355c49e3c4f4a Mon Sep 17 00:00:00 2001
From: davidfrigolet
Date: Wed, 10 Dec 2025 20:10:27 +0000
Subject: [PATCH 1/4] feat: flamingock test support
AuditSequenceStrictValidator
---
.../impl/AuditEntryExpectation.java | 6 +-
.../impl/AuditSequenceStrictValidator.java | 80 +++++-
.../AuditSequenceStrictValidatorTest.java | 266 ++++++++++++++++++
3 files changed, 342 insertions(+), 10 deletions(-)
create mode 100644 core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java
diff --git a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditEntryExpectation.java b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditEntryExpectation.java
index f2c75a984..eb8f583b4 100644
--- a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditEntryExpectation.java
+++ b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditEntryExpectation.java
@@ -30,11 +30,11 @@
* against expected definitions. Users should not interact with this class directly;
* they should use {@link AuditEntryDefinition} instead.
*/
-class AuditEntryExpectation {
+public class AuditEntryExpectation {
private final AuditEntryDefinition definition;
- AuditEntryExpectation(AuditEntryDefinition definition) {
+ public AuditEntryExpectation(AuditEntryDefinition definition) {
this.definition = definition;
}
@@ -48,7 +48,7 @@ class AuditEntryExpectation {
* @param actual the actual audit entry to compare against
* @return list of field mismatch errors (empty if all match)
*/
- List compareWith(AuditEntry actual) {
+ public List compareWith(AuditEntry actual) {
List errors = new ArrayList<>();
// Required fields - always verified
diff --git a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java
index 972418599..3e1abe58c 100644
--- a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java
+++ b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java
@@ -15,33 +15,99 @@
*/
package io.flamingock.support.validation.impl;
+import io.flamingock.internal.common.core.audit.AuditEntry;
import io.flamingock.internal.core.store.AuditStore;
import io.flamingock.support.domain.AuditEntryDefinition;
import io.flamingock.support.validation.SimpleValidator;
+import io.flamingock.support.validation.error.CountMismatchError;
+import io.flamingock.support.validation.error.ValidationError;
import io.flamingock.support.validation.error.ValidationResult;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
+/**
+ * Validator that performs strict sequence validation of audit entries.
+ *
+ * This validator verifies that the actual audit entries match the expected
+ * sequence exactly, both in count and in field values. Checking:
+ *
+ * - Exact count match between expected and actual entries
+ * - Strict field-by-field validation for each entry at each index
+ * - Order preservation (expected[0] must match actual[0], etc.)
+ *
+ */
public class AuditSequenceStrictValidator implements SimpleValidator {
private static final String VALIDATOR_NAME = "Audit Sequence (Strict)";
- private final AuditStore> auditStore;
- private final List expectations;
-
+ private final List expectedExpectations;
+ private final List actualEntries;
+ /**
+ * Creates a strict sequence validator from an AuditStore and expected definitions.
+ *
+ * @param auditStore the audit store to read actual entries from
+ * @param definitions the expected audit entry definitions
+ */
public AuditSequenceStrictValidator(AuditStore> auditStore, AuditEntryDefinition... definitions) {
- this.auditStore = auditStore;
- this.expectations = Arrays.stream(definitions)
+ this(Arrays.asList(definitions), auditStore.getPersistence().getAuditHistory());
+ }
+
+ /**
+ * Internal constructor for direct list initialization (used by tests).
+ */
+ AuditSequenceStrictValidator(List expectedDefinitions, List actualEntries) {
+ this.expectedExpectations = expectedDefinitions.stream()
.map(AuditEntryExpectation::new)
.collect(Collectors.toList());
+ this.actualEntries = actualEntries != null ? actualEntries : new ArrayList<>();
}
@Override
public ValidationResult validate() {
- // TODO: Implement actual validation logic
- return ValidationResult.success(VALIDATOR_NAME);
+ // Check count
+ if (expectedExpectations.size() != actualEntries.size()) {
+ return ValidationResult.failure(VALIDATOR_NAME,
+ new CountMismatchError(getExpectedChangeIds(), getActualChangeIds()));
+ }
+
+ // Validate each entry in sequence
+ List allErrors = getValidationErrors(expectedExpectations, actualEntries);
+
+ if (allErrors.isEmpty()) {
+ return ValidationResult.success(VALIDATOR_NAME);
+ }
+
+ return ValidationResult.failure(VALIDATOR_NAME, allErrors.toArray(
+ new io.flamingock.support.validation.error.ValidationError[0]));
+ }
+
+ private static List getValidationErrors(List expectedExpectations, List actualEntries) {
+ List allErrors = new ArrayList<>();
+ for (int i = 0; i < expectedExpectations.size(); i++) {
+ AuditEntryExpectation expected = expectedExpectations.get(i);
+ AuditEntry actual = actualEntries.get(i);
+
+ List entryErrors = expected.compareWith(actual);
+ if (!entryErrors.isEmpty()) {
+ allErrors.addAll(entryErrors);
+ }
+ }
+ return allErrors;
+ }
+
+ private List getExpectedChangeIds() {
+ return expectedExpectations.stream()
+ .map(exp -> exp.getDefinition().getChangeId())
+ .collect(Collectors.toList());
+ }
+
+ private List getActualChangeIds() {
+ return actualEntries.stream()
+ .map(AuditEntry::getTaskId)
+ .collect(Collectors.toList());
}
}
diff --git a/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java b/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java
new file mode 100644
index 000000000..37e5e31af
--- /dev/null
+++ b/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2025 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.support.validation.impl;
+
+import io.flamingock.internal.common.core.audit.AuditEntry;
+import io.flamingock.support.domain.AuditEntryDefinition;
+import io.flamingock.support.validation.error.CountMismatchError;
+import io.flamingock.support.validation.error.FieldMismatchError;
+import io.flamingock.support.validation.error.ValidationResult;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static io.flamingock.internal.common.core.audit.AuditEntry.ExecutionType.EXECUTION;
+import static io.flamingock.internal.common.core.audit.AuditEntry.Status.APPLIED;
+import static io.flamingock.internal.common.core.audit.AuditEntry.Status.FAILED;
+import static io.flamingock.support.domain.AuditEntryDefinition.APPLIED;
+import static org.junit.jupiter.api.Assertions.*;
+
+class AuditSequenceStrictValidatorTest {
+
+ private List actualEntries;
+
+ @BeforeEach
+ void setUp() {
+ actualEntries = Arrays.asList(
+ createAuditEntry("change-1", APPLIED),
+ createAuditEntry("change-2", APPLIED),
+ createAuditEntry("change-3", FAILED)
+ );
+ }
+
+ @Test
+ void shouldPassValidation_whenEntriesMatchExactly() {
+ List expectedDefinitions = Arrays.asList(
+ APPLIED("change-1"),
+ APPLIED("change-2"),
+ AuditEntryDefinition.FAILED("change-3")
+ );
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntries);
+ ValidationResult result = validator.validate();
+
+ assertTrue(result.isSuccess());
+ }
+
+ @Test
+ void shouldFailValidation_whenCountMismatch() {
+ List expectedDefinitions = Arrays.asList(
+ APPLIED("change-1"),
+ APPLIED("change-2")
+ );
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntries);
+ ValidationResult result = validator.validate();
+
+ assertFalse(result.isSuccess());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(CountMismatchError.class, result.getErrors().get(0));
+ }
+
+ @Test
+ void shouldFailValidation_whenStatusMismatch() {
+ List expectedDefinitions = Arrays.asList(
+ APPLIED("change-1"),
+ APPLIED("change-2"),
+ APPLIED("change-3") // Expected APPLIED but actual is FAILED
+ );
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntries);
+ ValidationResult result = validator.validate();
+
+ assertFalse(result.isSuccess());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(FieldMismatchError.class, result.getErrors().get(0));
+
+ FieldMismatchError error = (FieldMismatchError) result.getErrors().get(0);
+ assertEquals("status", error.getFieldName());
+ }
+
+ @Test
+ void shouldFailValidation_whenChangeIdMismatch() {
+ List expectedDefinitions = Arrays.asList(
+ APPLIED("change-1"),
+ APPLIED("wrong-id"), // Mismatch
+ AuditEntryDefinition.FAILED("change-3")
+ );
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntries);
+ ValidationResult result = validator.validate();
+
+ assertFalse(result.isSuccess());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(FieldMismatchError.class, result.getErrors().get(0));
+
+ FieldMismatchError error = (FieldMismatchError) result.getErrors().get(0);
+ assertEquals("changeId", error.getFieldName());
+ }
+
+ @Test
+ void shouldFailValidation_whenMissingEntry() {
+ List actualEntriesSubset = Arrays.asList(
+ createAuditEntry("change-1", APPLIED),
+ createAuditEntry("change-2", APPLIED)
+ );
+
+ List expectedDefinitions = Arrays.asList(
+ APPLIED("change-1"),
+ APPLIED("change-2"),
+ AuditEntryDefinition.FAILED("change-3")
+ );
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntriesSubset);
+ ValidationResult result = validator.validate();
+
+ assertFalse(result.isSuccess());
+ assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof CountMismatchError));
+ }
+
+ @Test
+ void shouldFailValidation_whenUnexpectedEntry() {
+ List actualEntriesExtra = Arrays.asList(
+ createAuditEntry("change-1", APPLIED),
+ createAuditEntry("change-2", APPLIED),
+ createAuditEntry("change-3", FAILED),
+ createAuditEntry("change-4", APPLIED)
+ );
+
+ List expectedDefinitions = Arrays.asList(
+ APPLIED("change-1"),
+ APPLIED("change-2"),
+ AuditEntryDefinition.FAILED("change-3")
+ );
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntriesExtra);
+ ValidationResult result = validator.validate();
+
+ assertFalse(result.isSuccess());
+ assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof CountMismatchError));
+ }
+
+ @Test
+ void shouldPassValidation_whenOptionalFieldsMatch() {
+ AuditEntry actualWithOptionalFields = new AuditEntry(
+ "exec-1",
+ "stage-1",
+ "change-1",
+ "author",
+ LocalDateTime.now(),
+ APPLIED,
+ EXECUTION,
+ "com.example.Change",
+ "apply",
+ 100L,
+ "host",
+ null,
+ false,
+ null,
+ null,
+ "target-1",
+ "1",
+ null,
+ true
+ );
+
+ AuditEntryDefinition expectedWithOptionalFields = APPLIED("change-1")
+ .withAuthor("author")
+ .withClassName("com.example.Change")
+ .withMethodName("apply")
+ .withTargetSystemId("target-1")
+ .withOrder("1")
+ .withTransactional(true);
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(
+ Collections.singletonList(expectedWithOptionalFields),
+ Collections.singletonList(actualWithOptionalFields)
+ );
+
+ ValidationResult result = validator.validate();
+
+ assertTrue(result.isSuccess());
+ }
+
+ @Test
+ void shouldFailValidation_whenOptionalFieldMismatch() {
+ AuditEntry actualEntry = new AuditEntry(
+ "exec-1",
+ "stage-1",
+ "change-1",
+ "author",
+ LocalDateTime.now(),
+ APPLIED,
+ EXECUTION,
+ "com.example.Change",
+ "apply",
+ 100L,
+ "host",
+ null,
+ false,
+ null,
+ null,
+ "target-1",
+ null,
+ null,
+ null
+ );
+
+ AuditEntryDefinition expectedWithDifferentOptional = APPLIED("change-1")
+ .withTargetSystemId("different-target");
+
+ AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(
+ Collections.singletonList(expectedWithDifferentOptional),
+ Collections.singletonList(actualEntry)
+ );
+
+ ValidationResult result = validator.validate();
+
+ assertFalse(result.isSuccess());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(FieldMismatchError.class, result.getErrors().get(0));
+
+ FieldMismatchError error = (FieldMismatchError) result.getErrors().get(0);
+ assertEquals("targetSystemId", error.getFieldName());
+ }
+
+ private AuditEntry createAuditEntry(String changeId, AuditEntry.Status status) {
+ return new AuditEntry(
+ "exec-id",
+ "stage-id",
+ changeId,
+ "test-author",
+ LocalDateTime.now(),
+ status,
+ EXECUTION,
+ "com.example.TestChange",
+ "apply",
+ 100L,
+ "localhost",
+ null,
+ false,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ );
+ }
+}
From 4d8958830af3712447162a0d1b5aa684be08a63b Mon Sep 17 00:00:00 2001
From: davidfrigolet
Date: Thu, 11 Dec 2025 09:12:54 +0000
Subject: [PATCH 2/4] feat: flamingock test support
AuditSequenceStrictValidator changes after code review
---
.../impl/AuditSequenceStrictValidator.java | 52 +++++++++++++------
.../AuditSequenceStrictValidatorTest.java | 34 ++++++++++--
2 files changed, 67 insertions(+), 19 deletions(-)
diff --git a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java
index 3e1abe58c..b507ad42c 100644
--- a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java
+++ b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidator.java
@@ -20,6 +20,9 @@
import io.flamingock.support.domain.AuditEntryDefinition;
import io.flamingock.support.validation.SimpleValidator;
import io.flamingock.support.validation.error.CountMismatchError;
+import io.flamingock.support.validation.error.FieldMismatchError;
+import io.flamingock.support.validation.error.MissingEntryError;
+import io.flamingock.support.validation.error.UnexpectedEntryError;
import io.flamingock.support.validation.error.ValidationError;
import io.flamingock.support.validation.error.ValidationResult;
@@ -68,33 +71,50 @@ public AuditSequenceStrictValidator(AuditStore> auditStore, AuditEntryDefiniti
@Override
public ValidationResult validate() {
- // Check count
- if (expectedExpectations.size() != actualEntries.size()) {
- return ValidationResult.failure(VALIDATOR_NAME,
- new CountMismatchError(getExpectedChangeIds(), getActualChangeIds()));
+ List allErrors = new ArrayList<>();
+
+ int expectedSize = expectedExpectations.size();
+ int actualSize = actualEntries.size();
+
+ if (expectedSize != actualSize) {
+ allErrors.add(new CountMismatchError(getExpectedChangeIds(), getActualChangeIds()));
+ }
+
+ allErrors.addAll(getValidationErrors(expectedExpectations, actualEntries));
+
+ if (expectedSize > actualSize) {
+ for (int i = actualSize; i < expectedSize; i++) {
+ AuditEntryDefinition def = expectedExpectations.get(i).getDefinition();
+ allErrors.add(new MissingEntryError(i, def.getChangeId(), def.getState()));
+ }
}
- // Validate each entry in sequence
- List allErrors = getValidationErrors(expectedExpectations, actualEntries);
+ if (expectedSize < actualSize) {
+ for (int i = expectedSize; i < actualSize; i++) {
+ AuditEntry actual = actualEntries.get(i);
+ allErrors.add(new UnexpectedEntryError(i, actual.getTaskId(), actual.getState()));
+ }
+ }
if (allErrors.isEmpty()) {
return ValidationResult.success(VALIDATOR_NAME);
}
- return ValidationResult.failure(VALIDATOR_NAME, allErrors.toArray(
- new io.flamingock.support.validation.error.ValidationError[0]));
+ return ValidationResult.failure(VALIDATOR_NAME, allErrors.toArray(new ValidationError[0]));
}
- private static List getValidationErrors(List expectedExpectations, List actualEntries) {
+ private static List getValidationErrors(List expectedEntries, List actualEntries) {
List allErrors = new ArrayList<>();
- for (int i = 0; i < expectedExpectations.size(); i++) {
- AuditEntryExpectation expected = expectedExpectations.get(i);
+ if (expectedEntries == null || expectedEntries.isEmpty()) {
+ return allErrors;
+ }
+ int actualSize = actualEntries != null ? actualEntries.size() : 0;
+ int limit = Math.min(expectedEntries.size(), actualSize);
+ for (int i = 0; i < limit; i++) {
+ AuditEntryExpectation expected = expectedEntries.get(i);
AuditEntry actual = actualEntries.get(i);
-
- List entryErrors = expected.compareWith(actual);
- if (!entryErrors.isEmpty()) {
- allErrors.addAll(entryErrors);
- }
+ List entryErrors = expected.compareWith(actual);
+ allErrors.addAll(entryErrors);
}
return allErrors;
}
diff --git a/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java b/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java
index 37e5e31af..a1dbd189c 100644
--- a/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java
+++ b/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/AuditSequenceStrictValidatorTest.java
@@ -19,8 +19,11 @@
import io.flamingock.support.domain.AuditEntryDefinition;
import io.flamingock.support.validation.error.CountMismatchError;
import io.flamingock.support.validation.error.FieldMismatchError;
+import io.flamingock.support.validation.error.MissingEntryError;
+import io.flamingock.support.validation.error.UnexpectedEntryError;
import io.flamingock.support.validation.error.ValidationResult;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
@@ -32,6 +35,7 @@
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.APPLIED;
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.FAILED;
import static io.flamingock.support.domain.AuditEntryDefinition.APPLIED;
+import static io.flamingock.support.domain.AuditEntryDefinition.FAILED;
import static org.junit.jupiter.api.Assertions.*;
class AuditSequenceStrictValidatorTest {
@@ -48,11 +52,12 @@ void setUp() {
}
@Test
+ @DisplayName("AuditSequenceStrictValidator passes when entries match exactly")
void shouldPassValidation_whenEntriesMatchExactly() {
List expectedDefinitions = Arrays.asList(
APPLIED("change-1"),
APPLIED("change-2"),
- AuditEntryDefinition.FAILED("change-3")
+ FAILED("change-3")
);
AuditSequenceStrictValidator validator = new AuditSequenceStrictValidator(expectedDefinitions, actualEntries);
@@ -62,6 +67,7 @@ void shouldPassValidation_whenEntriesMatchExactly() {
}
@Test
+ @DisplayName("AuditSequenceStrictValidator fails when counts mismatch")
void shouldFailValidation_whenCountMismatch() {
List expectedDefinitions = Arrays.asList(
APPLIED("change-1"),
@@ -72,11 +78,14 @@ void shouldFailValidation_whenCountMismatch() {
ValidationResult result = validator.validate();
assertFalse(result.isSuccess());
- assertEquals(1, result.getErrors().size());
- assertInstanceOf(CountMismatchError.class, result.getErrors().get(0));
+ assertEquals(2, result.getErrors().size());
+ assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof CountMismatchError));
+ assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof UnexpectedEntryError));
}
+
@Test
+ @DisplayName("AuditSequenceStrictValidator fails when a field status mismatches")
void shouldFailValidation_whenStatusMismatch() {
List expectedDefinitions = Arrays.asList(
APPLIED("change-1"),
@@ -96,6 +105,7 @@ void shouldFailValidation_whenStatusMismatch() {
}
@Test
+ @DisplayName("AuditSequenceStrictValidator fails when changeId mismatches")
void shouldFailValidation_whenChangeIdMismatch() {
List expectedDefinitions = Arrays.asList(
APPLIED("change-1"),
@@ -115,6 +125,7 @@ void shouldFailValidation_whenChangeIdMismatch() {
}
@Test
+ @DisplayName("AuditSequenceStrictValidator fails when an expected entry is missing (count + missing entry error)")
void shouldFailValidation_whenMissingEntry() {
List actualEntriesSubset = Arrays.asList(
createAuditEntry("change-1", APPLIED),
@@ -132,9 +143,17 @@ void shouldFailValidation_whenMissingEntry() {
assertFalse(result.isSuccess());
assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof CountMismatchError));
+ assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof MissingEntryError));
+ MissingEntryError missing = (MissingEntryError) result.getErrors().stream()
+ .filter(e -> e instanceof MissingEntryError)
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Expected a MissingEntryError but none was found"));
+ assertEquals(2, missing.getExpectedIndex());
+ assertEquals("change-3", missing.getExpectedChangeId());
}
@Test
+ @DisplayName("AuditSequenceStrictValidator fails when there is an unexpected actual entry (count + unexpected entry error)")
void shouldFailValidation_whenUnexpectedEntry() {
List actualEntriesExtra = Arrays.asList(
createAuditEntry("change-1", APPLIED),
@@ -154,9 +173,17 @@ void shouldFailValidation_whenUnexpectedEntry() {
assertFalse(result.isSuccess());
assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof CountMismatchError));
+ assertTrue(result.getErrors().stream().anyMatch(e -> e instanceof UnexpectedEntryError));
+ UnexpectedEntryError unexpected = (UnexpectedEntryError) result.getErrors().stream()
+ .filter(e -> e instanceof UnexpectedEntryError)
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Expected an UnexpectedEntryError but none was found"));
+ assertEquals(3, unexpected.getIndex());
+ assertEquals("change-4", unexpected.getActualChangeId());
}
@Test
+ @DisplayName("AuditSequenceStrictValidator passes when optional fields match")
void shouldPassValidation_whenOptionalFieldsMatch() {
AuditEntry actualWithOptionalFields = new AuditEntry(
"exec-1",
@@ -199,6 +226,7 @@ void shouldPassValidation_whenOptionalFieldsMatch() {
}
@Test
+ @DisplayName("AuditSequenceStrictValidator fails when an optional field mismatches")
void shouldFailValidation_whenOptionalFieldMismatch() {
AuditEntry actualEntry = new AuditEntry(
"exec-1",
From 1b4541a9e9c9c54c5ee2f983a73538859434b669 Mon Sep 17 00:00:00 2001
From: davidfrigolet
Date: Thu, 11 Dec 2025 13:16:22 +0000
Subject: [PATCH 3/4] feat: flamingock test support DefaultExceptionValidator
---
.../impl/DefaultExceptionValidator.java | 42 ++++++----
.../impl/DefaultExceptionValidatorTest.java | 84 +++++++++++++++++++
2 files changed, 110 insertions(+), 16 deletions(-)
create mode 100644 core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/DefaultExceptionValidatorTest.java
diff --git a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java
index 8fc9f23df..32be70413 100644
--- a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java
+++ b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java
@@ -1,21 +1,8 @@
-/*
- * Copyright 2025 Flamingock (https://www.flamingock.io)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package io.flamingock.support.validation.impl;
import io.flamingock.support.validation.ExceptionValidator;
+import io.flamingock.support.validation.error.ExceptionNotExpectedError;
+import io.flamingock.support.validation.error.ExceptionTypeMismatchError;
import io.flamingock.support.validation.error.ValidationResult;
import java.util.function.Consumer;
@@ -40,7 +27,30 @@ public void setActualException(Throwable actualException) {
@Override
public ValidationResult validate(Throwable actualException) {
- // TODO: Implement actual validation logic
+ // No exception expected
+ if (expectedExceptionClass == null) {
+ if (actualException == null) {
+ return ValidationResult.success(VALIDATOR_NAME);
+ } else {
+ return ValidationResult.failure(VALIDATOR_NAME, new ExceptionNotExpectedError(actualException));
+ }
+ }
+
+ // An exception is expected but none was thrown
+ if (actualException == null) {
+ return ValidationResult.failure(VALIDATOR_NAME,
+ new ExceptionTypeMismatchError(expectedExceptionClass, null));
+ }
+
+ // Type mismatch
+ if (!expectedExceptionClass.isInstance(actualException)) {
+ return ValidationResult.failure(VALIDATOR_NAME,
+ new ExceptionTypeMismatchError(expectedExceptionClass, actualException.getClass()));
+ }
+
+ if (expectedExceptionConsumer != null) {
+ expectedExceptionConsumer.accept(actualException);
+ }
return ValidationResult.success(VALIDATOR_NAME);
}
}
diff --git a/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/DefaultExceptionValidatorTest.java b/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/DefaultExceptionValidatorTest.java
new file mode 100644
index 000000000..f8d98a5b7
--- /dev/null
+++ b/core/flamingock-test-support/src/test/java/io/flamingock/support/validation/impl/DefaultExceptionValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2025 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.support.validation.impl;
+
+import io.flamingock.support.validation.error.ExceptionNotExpectedError;
+import io.flamingock.support.validation.error.ExceptionTypeMismatchError;
+import io.flamingock.support.validation.error.ValidationResult;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DefaultExceptionValidatorTest {
+
+ @Test
+ @DisplayName("DefaultExceptionValidatorTest: No exception expected and none thrown — should succeed")
+ void noExceptionExpected_andNoneThrown_shouldSucceed() {
+ DefaultExceptionValidator validator = new DefaultExceptionValidator(null, null);
+ ValidationResult result = validator.validate(null);
+ assertTrue(result.isSuccess());
+ assertFalse(result.hasErrors());
+ }
+
+ @Test
+ @DisplayName("DefaultExceptionValidatorTest: No exception expected but an exception thrown — should fail with ExceptionNotExpectedError")
+ void noExceptionExpected_butExceptionThrown_shouldFailWithNotExpected() {
+ DefaultExceptionValidator validator = new DefaultExceptionValidator(null, null);
+ RuntimeException ex = new RuntimeException("message");
+ ValidationResult result = validator.validate(ex);
+ assertTrue(result.hasErrors());
+ assertFalse(result.isSuccess());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(ExceptionNotExpectedError.class, result.getErrors().get(0));
+ }
+
+ @Test
+ @DisplayName("DefaultExceptionValidatorTest: Exception expected but none thrown — should fail with ExceptionTypeMismatchError (null actual)")
+ void exceptionExpected_butNoneThrown_shouldFailWithTypeMismatch_nullActual() {
+ DefaultExceptionValidator validator = new DefaultExceptionValidator(IOException.class, null);
+ ValidationResult result = validator.validate(null);
+ assertTrue(result.hasErrors());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(ExceptionTypeMismatchError.class, result.getErrors().get(0));
+ }
+
+ @Test
+ @DisplayName("DefaultExceptionValidatorTest: Exception expected but wrong type thrown — should fail with ExceptionTypeMismatchError")
+ void exceptionExpected_butWrongTypeThrown_shouldFailWithTypeMismatch() {
+ DefaultExceptionValidator validator = new DefaultExceptionValidator(IllegalArgumentException.class, null);
+ RuntimeException ex = new RuntimeException("message");
+ ValidationResult result = validator.validate(ex);
+ assertTrue(result.hasErrors());
+ assertEquals(1, result.getErrors().size());
+ assertInstanceOf(ExceptionTypeMismatchError.class, result.getErrors().get(0));
+ }
+
+ @Test
+ @DisplayName("DefaultExceptionValidatorTest: Exception expected and correct type thrown — should succeed and invoke consumer")
+ void exceptionExpected_andCorrectTypeThrown_shouldSucceed_andInvokeConsumer() {
+ AtomicBoolean consumed = new AtomicBoolean(false);
+ DefaultExceptionValidator validator = new DefaultExceptionValidator(RuntimeException.class, e -> consumed.set(true));
+ RuntimeException ex = new RuntimeException("message");
+ ValidationResult result = validator.validate(ex);
+ assertTrue(result.isSuccess());
+ assertFalse(result.hasErrors());
+ assertTrue(consumed.get());
+ }
+}
From 1af263c557052378a7ae547a73e870faa1394db4 Mon Sep 17 00:00:00 2001
From: davidfrigolet
Date: Thu, 11 Dec 2025 13:43:23 +0000
Subject: [PATCH 4/4] feat: flamingock test support DefaultExceptionValidator
---
.../impl/DefaultExceptionValidator.java | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java
index 32be70413..e498f22dd 100644
--- a/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java
+++ b/core/flamingock-test-support/src/main/java/io/flamingock/support/validation/impl/DefaultExceptionValidator.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2025 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package io.flamingock.support.validation.impl;
import io.flamingock.support.validation.ExceptionValidator;