Skip to content

Commit 44e764b

Browse files
authored
refactor: add test support error foundation (#753)
1 parent 827cb5a commit 44e764b

14 files changed

+724
-11
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.support.validation;
17+
18+
import io.flamingock.support.validation.error.ValidationError;
19+
import io.flamingock.support.validation.error.ValidationResult;
20+
21+
import java.util.List;
22+
23+
/**
24+
* Formats validation errors into a human-readable message for display
25+
* in assertion errors.
26+
*
27+
* <p>The formatter groups errors by validator and presents them in a
28+
* clear, structured format.</p>
29+
*/
30+
public class ValidationErrorFormatter {
31+
32+
private static final String HEADER = "Flamingock Test Verification Failed";
33+
private static final String BULLET = " • ";
34+
private static final String NEWLINE = "\n";
35+
36+
/**
37+
* Formats a list of validation failures into a human-readable message.
38+
*
39+
* @param failures the list of validation results that contain errors
40+
* @return a formatted error message
41+
*/
42+
public String format(List<ValidationResult> failures) {
43+
StringBuilder sb = new StringBuilder();
44+
sb.append(HEADER).append(NEWLINE);
45+
46+
for (ValidationResult result : failures) {
47+
sb.append(NEWLINE);
48+
sb.append("[").append(result.getValidatorName()).append("]").append(NEWLINE);
49+
50+
for (ValidationError error : result.getErrors()) {
51+
sb.append(BULLET).append(error.formatMessage()).append(NEWLINE);
52+
}
53+
}
54+
55+
return sb.toString().trim();
56+
}
57+
}

core/flamingock-test-support/src/main/java/io/flamingock/support/validation/ValidationHandler.java

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,27 @@
1515
*/
1616
package io.flamingock.support.validation;
1717

18+
import io.flamingock.support.validation.error.ExceptionNotExpectedError;
19+
import io.flamingock.support.validation.error.ValidationResult;
20+
21+
import java.util.ArrayList;
1822
import java.util.List;
23+
import java.util.stream.Collectors;
1924

25+
/**
26+
* Orchestrates the execution of all validators and aggregates their results.
27+
*
28+
* <p>The handler executes all registered validators, collects their results,
29+
* and throws an {@link AssertionError} if any validation fails. The error
30+
* message is formatted to display all failures grouped by validator.</p>
31+
*/
2032
public class ValidationHandler {
2133

34+
private static final String EXECUTION_VALIDATOR_NAME = "Execution";
35+
2236
private final List<Validator> validators;
2337
private final Throwable executionException;
38+
private final ValidationErrorFormatter formatter;
2439

2540
public ValidationHandler(List<Validator> validators) {
2641
this(validators, null);
@@ -29,14 +44,59 @@ public ValidationHandler(List<Validator> validators) {
2944
public ValidationHandler(List<Validator> validators, Throwable executionException) {
3045
this.validators = validators;
3146
this.executionException = executionException;
47+
this.formatter = new ValidationErrorFormatter();
3248
}
3349

34-
50+
/**
51+
* Executes all validators and throws an AssertionError if any validation fails.
52+
*
53+
* <p>This method:</p>
54+
* <ol>
55+
* <li>Executes each registered validator</li>
56+
* <li>Checks for unexpected exceptions (if no exception validator is registered)</li>
57+
* <li>Collects all validation failures</li>
58+
* <li>Throws an AssertionError with a formatted message if any failures exist</li>
59+
* </ol>
60+
*
61+
* @throws AssertionError if any validation fails
62+
*/
3563
public void validate() throws AssertionError {
64+
List<ValidationResult> results = new ArrayList<>();
3665

37-
//TODO process validator and grab the potential validation errors in a AssertionError
38-
// we probably need another class for building the validation result
66+
for (Validator validator : validators) {
67+
ValidationResult result = executeValidator(validator);
68+
if (result != null) {
69+
results.add(result);
70+
}
71+
}
3972

73+
// Check if no exception expected but one occurred
74+
if (executionException != null && !hasExceptionValidator()) {
75+
results.add(ValidationResult.failure(EXECUTION_VALIDATOR_NAME,
76+
new ExceptionNotExpectedError(executionException)));
77+
}
78+
79+
// Collect all failures
80+
List<ValidationResult> failures = results.stream()
81+
.filter(ValidationResult::hasErrors)
82+
.collect(Collectors.toList());
83+
84+
if (!failures.isEmpty()) {
85+
throw new AssertionError(formatter.format(failures));
86+
}
87+
}
88+
89+
private ValidationResult executeValidator(Validator validator) {
90+
if (validator instanceof SimpleValidator) {
91+
return ((SimpleValidator) validator).validate();
92+
} else if (validator instanceof ExceptionValidator) {
93+
return ((ExceptionValidator) validator).validate(executionException);
94+
}
95+
return null;
96+
}
4097

98+
private boolean hasExceptionValidator() {
99+
return validators.stream()
100+
.anyMatch(v -> v instanceof ExceptionValidator);
41101
}
42102
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.support.validation.error;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
21+
/**
22+
* Validation error indicating that the count of audit entries
23+
* does not match the expected count.
24+
*
25+
* <p>This error includes both the expected and actual lists of change IDs
26+
* to provide full context for debugging.</p>
27+
*/
28+
public class CountMismatchError extends ValidationError {
29+
30+
private final List<String> expectedChangeIds;
31+
private final List<String> actualChangeIds;
32+
33+
/**
34+
* Creates a new count mismatch error.
35+
*
36+
* @param expectedChangeIds the list of expected change IDs
37+
* @param actualChangeIds the list of actual change IDs found
38+
*/
39+
public CountMismatchError(List<String> expectedChangeIds, List<String> actualChangeIds) {
40+
super(ValidationErrorType.COUNT_MISMATCH);
41+
this.expectedChangeIds = expectedChangeIds != null
42+
? Collections.unmodifiableList(expectedChangeIds)
43+
: Collections.emptyList();
44+
this.actualChangeIds = actualChangeIds != null
45+
? Collections.unmodifiableList(actualChangeIds)
46+
: Collections.emptyList();
47+
}
48+
49+
public List<String> getExpectedChangeIds() {
50+
return expectedChangeIds;
51+
}
52+
53+
public List<String> getActualChangeIds() {
54+
return actualChangeIds;
55+
}
56+
57+
@Override
58+
public String formatMessage() {
59+
return String.format(
60+
"Audit entry count mismatch: expected <%d> entries but found <%d>\n" +
61+
" Expected: %s\n" +
62+
" Actual: %s",
63+
expectedChangeIds.size(),
64+
actualChangeIds.size(),
65+
expectedChangeIds,
66+
actualChangeIds);
67+
}
68+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.support.validation.error;
17+
18+
/**
19+
* Validation error indicating that a custom validation
20+
* (user-provided validator) failed.
21+
*/
22+
public class CustomValidationError extends ValidationError {
23+
24+
private final String message;
25+
private final Throwable cause;
26+
27+
/**
28+
* Creates a new custom validation error with a message.
29+
*
30+
* @param message the error message describing the validation failure
31+
*/
32+
public CustomValidationError(String message) {
33+
this(message, null);
34+
}
35+
36+
/**
37+
* Creates a new custom validation error with a message and cause.
38+
*
39+
* @param message the error message describing the validation failure
40+
* @param cause the exception thrown by the custom validator, or {@code null}
41+
*/
42+
public CustomValidationError(String message, Throwable cause) {
43+
super(ValidationErrorType.CUSTOM_VALIDATION);
44+
this.message = message;
45+
this.cause = cause;
46+
}
47+
48+
public String getMessage() {
49+
return message;
50+
}
51+
52+
public Throwable getCause() {
53+
return cause;
54+
}
55+
56+
@Override
57+
public String formatMessage() {
58+
if (cause != null) {
59+
return String.format("Custom validation failed: %s (caused by: %s - '%s')",
60+
message,
61+
cause.getClass().getSimpleName(),
62+
cause.getMessage());
63+
}
64+
return String.format("Custom validation failed: %s", message);
65+
}
66+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.support.validation.error;
17+
18+
/**
19+
* Validation error indicating that an exception was thrown
20+
* when none was expected.
21+
*/
22+
public class ExceptionNotExpectedError extends ValidationError {
23+
24+
private final Throwable actualException;
25+
26+
/**
27+
* Creates a new unexpected exception error.
28+
*
29+
* @param actualException the exception that was thrown
30+
*/
31+
public ExceptionNotExpectedError(Throwable actualException) {
32+
super(ValidationErrorType.EXCEPTION_NOT_EXPECTED);
33+
this.actualException = actualException;
34+
}
35+
36+
public Throwable getActualException() {
37+
return actualException;
38+
}
39+
40+
@Override
41+
public String formatMessage() {
42+
String exceptionName = actualException.getClass().getSimpleName();
43+
String message = actualException.getMessage();
44+
if (message != null && !message.isEmpty()) {
45+
return String.format("Unexpected exception thrown: %s - '%s'", exceptionName, message);
46+
}
47+
return String.format("Unexpected exception thrown: %s", exceptionName);
48+
}
49+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.support.validation.error;
17+
18+
/**
19+
* Validation error indicating that the thrown exception type
20+
* does not match the expected type, or no exception was thrown
21+
* when one was expected.
22+
*/
23+
public class ExceptionTypeMismatchError extends ValidationError {
24+
25+
private final Class<? extends Throwable> expectedType;
26+
private final Class<? extends Throwable> actualType;
27+
28+
/**
29+
* Creates a new exception type mismatch error.
30+
*
31+
* @param expectedType the expected exception type
32+
* @param actualType the actual exception type thrown, or {@code null} if no exception was thrown
33+
*/
34+
public ExceptionTypeMismatchError(Class<? extends Throwable> expectedType,
35+
Class<? extends Throwable> actualType) {
36+
super(ValidationErrorType.EXCEPTION_TYPE_MISMATCH);
37+
this.expectedType = expectedType;
38+
this.actualType = actualType;
39+
}
40+
41+
public Class<? extends Throwable> getExpectedType() {
42+
return expectedType;
43+
}
44+
45+
public Class<? extends Throwable> getActualType() {
46+
return actualType;
47+
}
48+
49+
@Override
50+
public String formatMessage() {
51+
if (actualType == null) {
52+
return String.format("Expected exception <%s> but none was thrown",
53+
expectedType.getSimpleName());
54+
}
55+
return String.format("Exception type mismatch: expected <%s> but was <%s>",
56+
expectedType.getSimpleName(),
57+
actualType.getSimpleName());
58+
}
59+
}

0 commit comments

Comments
 (0)