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;