Skip to content

Commit 2dec48b

Browse files
feat: flamingock test support new validatorArgs and integration test (#766)
1 parent 07bb838 commit 2dec48b

17 files changed

+686
-27
lines changed

core/flamingock-test-support/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
dependencies {
22
api(project(":core:flamingock-core"))
33

4+
45
testImplementation(platform("org.junit:junit-bom:5.10.0"))
56
testImplementation("org.junit.jupiter:junit-jupiter")
67
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
8+
9+
// Add test utilities from the repository so tests can use InMemoryTestKit and pipeline helpers
10+
testImplementation(project(":utils:test-util"))
11+
testImplementation(project(":core:target-systems:nontransactional-target-system"))
12+
api("org.mockito:mockito-inline:4.11.0")
713
}
814

915
description = "Test support module for Flamingock framework"

core/flamingock-test-support/src/main/java/io/flamingock/support/domain/AuditEntryDefinition.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@
2929
import java.lang.reflect.Method;
3030
import java.time.LocalDateTime;
3131

32-
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.APPLIED;
33-
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.FAILED;
34-
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.ROLLBACK_FAILED;
35-
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.ROLLED_BACK;
32+
import static io.flamingock.internal.common.core.audit.AuditEntry.Status.*;
3633

3734
/**
3835
* Defines an audit entry for use in BDD-style tests.

core/flamingock-test-support/src/main/java/io/flamingock/support/stages/ThenStageImpl.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,53 @@
1919
import io.flamingock.support.domain.AuditEntryDefinition;
2020
import io.flamingock.support.precondition.PreconditionInserter;
2121
import io.flamingock.support.validation.ValidationHandler;
22-
import io.flamingock.support.validation.Validator;
23-
import io.flamingock.support.validation.ValidatorFactory;
22+
import io.flamingock.support.validation.ValidatorArgs;
23+
import io.flamingock.support.validation.impl.AuditSequenceStrictValidator;
24+
import io.flamingock.support.validation.impl.DefaultExceptionValidator;
2425

2526
import java.util.ArrayList;
2627
import java.util.List;
2728
import java.util.function.Consumer;
29+
import java.util.Arrays;
30+
import java.util.Collections;
2831

2932
final class ThenStageImpl implements ThenStage {
3033

31-
private final List<Validator> validators = new ArrayList<>();
32-
private final ValidatorFactory validatorFactory;
34+
private final List<ValidatorArgs> validators = new ArrayList<>();
3335
private final TestContext testContext;
3436

3537
ThenStageImpl(TestContext testContext) {
3638
this.testContext = testContext;
37-
validatorFactory = new ValidatorFactory(testContext.getAuditReader());
3839
}
3940

4041
@Override
4142
public ThenStage andExpectAuditSequenceStrict(AuditEntryDefinition... definitions) {
42-
validators.add(validatorFactory.getAuditSeqStrictValidator(definitions));
43+
List<AuditEntryDefinition> definitionsList = definitions != null ? Arrays.asList(definitions) : Collections.<AuditEntryDefinition>emptyList();
44+
validators.add(new AuditSequenceStrictValidator.Args(definitionsList));
4345
return this;
4446
}
4547

4648
@Override
4749
public ThenStage andExpectException(Class<? extends Throwable> exceptionClass, Consumer<Throwable> exceptionConsumer) {
48-
validators.add(validatorFactory.getExceptionValidator(exceptionClass, exceptionConsumer));
50+
validators.add(new DefaultExceptionValidator.Args(exceptionClass, exceptionConsumer));
4951
return this;
5052
}
5153

5254
@Override
5355
public void verify() throws AssertionError {
54-
// Insert preconditions first
55-
PreconditionInserter preconditionInserter = new PreconditionInserter(testContext.getAuditWriter());
56-
preconditionInserter.insert(testContext.getPreconditions());
57-
5856
ValidationHandler validationHandler;
5957
try {
6058
testContext.run();
61-
validationHandler = new ValidationHandler(validators);
59+
validationHandler = new ValidationHandler(testContext, validators);
6260

6361
} catch (Throwable actualException) {
64-
validationHandler = new ValidationHandler(validators, actualException);
62+
validationHandler = new ValidationHandler(testContext, validators, actualException);
6563

6664
}
6765

6866
validationHandler.validate();
67+
68+
PreconditionInserter preconditionInserter = new PreconditionInserter(testContext.getAuditWriter());
69+
preconditionInserter.insert(testContext.getPreconditions());
6970
}
7071
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.flamingock.support.validation;
1717

18+
import io.flamingock.support.context.TestContext;
1819
import io.flamingock.support.validation.error.ExceptionNotExpectedError;
1920
import io.flamingock.support.validation.error.ValidationResult;
2021

@@ -47,6 +48,25 @@ public ValidationHandler(List<Validator> validators, Throwable executionExceptio
4748
this.formatter = new ValidationErrorFormatter();
4849
}
4950

51+
// TestContext and deferred ValidatorArgs
52+
public ValidationHandler(TestContext testContext, List<ValidatorArgs> args) {
53+
this(testContext, args, null);
54+
}
55+
56+
public ValidationHandler(TestContext testContext, List<ValidatorArgs> args, Throwable executionException) {
57+
this.executionException = executionException;
58+
this.formatter = new ValidationErrorFormatter();
59+
// Build actual validators now that we have access to the TestContext
60+
ValidatorFactory factory = new ValidatorFactory(testContext.getAuditReader());
61+
List<Validator> built = new ArrayList<>();
62+
if (args != null) {
63+
for (ValidatorArgs a : args) {
64+
built.add(factory.getValidator(a));
65+
}
66+
}
67+
this.validators = built;
68+
}
69+
5070
/**
5171
* Executes all validators and throws an AssertionError if any validation fails.
5272
*
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
/**
19+
* Marker interface for validator argument carriers used in the BDD test framework.
20+
*
21+
* <p>This interface serves as a deferred construction pattern for validators. Instead of
22+
* creating validators immediately when defining expectations in the "Then" stage, implementations
23+
* of this interface carry the necessary arguments until the {@link io.flamingock.support.stages.ThenStage#verify()} method
24+
* is called. This allows validators to be constructed with access to the {@link io.flamingock.support.context.TestContext},
25+
* particularly the {@link io.flamingock.internal.common.core.audit.AuditReader}.</p>
26+
*
27+
* <h2>Purpose</h2>
28+
* <p>The deferred construction is necessary because:</p>
29+
* <ul>
30+
* <li>The audit reader is only available after the test context is fully initialized</li>
31+
* <li>Validators need access to actual audit entries, which are only available after execution</li>
32+
* <li>The BDD API is lazy - expectations are defined but not executed until {@code verify()}</li>
33+
* </ul>
34+
*
35+
* <h2>Implementation</h2>
36+
* <p>Implementations are typically static inner classes within their corresponding validators:</p>
37+
* <pre>{@code
38+
* public class AuditSequenceStrictValidator implements SimpleValidator {
39+
* // ... validator implementation
40+
*
41+
* public static class Args implements ValidatorArgs {
42+
* private final List<AuditEntryDefinition> expectations;
43+
*
44+
* public Args(List<AuditEntryDefinition> expectations) {
45+
* this.expectations = expectations;
46+
* }
47+
*
48+
* public List<AuditEntryDefinition> getExpectations() {
49+
* return expectations;
50+
* }
51+
* }
52+
* }
53+
* }</pre>
54+
*
55+
* <h2>Usage in Framework</h2>
56+
* <p>The framework uses these argument carriers as follows:</p>
57+
* <ol>
58+
* <li>User calls expectation method (e.g., {@code thenExpectAuditSequenceStrict(...)})</li>
59+
* <li>Stage creates an {@code Args} instance and stores it in the validators list</li>
60+
* <li>When {@code verify()} is called, the {@link ValidationHandler} constructs actual
61+
* validators using {@link ValidatorFactory#getValidator(ValidatorArgs)}</li>
62+
* <li>Validators are executed with access to the test context and audit reader</li>
63+
* </ol>
64+
*
65+
* @see ValidatorFactory#getValidator(ValidatorArgs)
66+
* @see ValidationHandler
67+
* @see io.flamingock.support.stages.ThenStage#verify()
68+
*/
69+
public interface ValidatorArgs {
70+
}
71+
72+

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.flamingock.support.validation.impl.AuditSequenceStrictValidator;
2121
import io.flamingock.support.validation.impl.DefaultExceptionValidator;
2222

23+
import java.util.List;
2324
import java.util.function.Consumer;
2425

2526
public class ValidatorFactory {
@@ -30,11 +31,25 @@ public ValidatorFactory(AuditReader auditReader) {
3031
this.auditReader = auditReader;
3132
}
3233

33-
public Validator getAuditSeqStrictValidator(AuditEntryDefinition... definitions) {
34+
public Validator getAuditSeqStrictValidator(List<AuditEntryDefinition> definitions) {
3435
return new AuditSequenceStrictValidator(auditReader, definitions);
3536
}
3637

3738
public Validator getExceptionValidator(Class<? extends Throwable> exceptionClass, Consumer<Throwable> exceptionConsumer) {
3839
return new DefaultExceptionValidator(exceptionClass, exceptionConsumer);
3940
}
41+
42+
public Validator getValidator(ValidatorArgs args) {
43+
if (args instanceof AuditSequenceStrictValidator.Args) {
44+
AuditSequenceStrictValidator.Args a = (AuditSequenceStrictValidator.Args) args;
45+
return new AuditSequenceStrictValidator(auditReader, a.getExpectations());
46+
}
47+
48+
if (args instanceof DefaultExceptionValidator.Args) {
49+
DefaultExceptionValidator.Args a = (DefaultExceptionValidator.Args) args;
50+
return new DefaultExceptionValidator(a.getExceptionClass(), a.getExceptionConsumer());
51+
}
52+
53+
throw new IllegalArgumentException("Unknown ValidatorArgs type: " + (args != null ? args.getClass() : "null"));
54+
}
4055
}

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

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
import io.flamingock.internal.common.core.audit.AuditReader;
2020
import io.flamingock.support.domain.AuditEntryDefinition;
2121
import io.flamingock.support.validation.SimpleValidator;
22+
import io.flamingock.support.validation.ValidatorArgs;
2223
import io.flamingock.support.validation.error.*;
2324

2425
import java.util.ArrayList;
25-
import java.util.Arrays;
26+
import java.util.Collections;
2627
import java.util.List;
2728
import java.util.stream.Collectors;
2829

@@ -44,13 +45,22 @@ public class AuditSequenceStrictValidator implements SimpleValidator {
4445
private final AuditReader auditReader;
4546
private final List<AuditEntryExpectation> expectations;
4647
private final List<AuditEntry> actualEntries;
48+
private static final List<AuditEntry.Status> EXCLUDED_STATES = Collections.singletonList(
49+
AuditEntry.Status.STARTED
50+
);
4751

48-
public AuditSequenceStrictValidator(AuditReader auditReader, AuditEntryDefinition... definitions) {
52+
public AuditSequenceStrictValidator(AuditReader auditReader, List<AuditEntryDefinition> definitions) {
4953
this.auditReader = auditReader;
50-
this.expectations = Arrays.stream(definitions)
54+
this.expectations = definitions != null
55+
? definitions.stream()
5156
.map(AuditEntryExpectation::new)
57+
.collect(Collectors.toList())
58+
: new ArrayList<>();
59+
60+
this.actualEntries = auditReader.getAuditHistory().stream()
61+
.filter(entry -> !EXCLUDED_STATES.contains(entry.getState()))
62+
.sorted()
5263
.collect(Collectors.toList());
53-
this.actualEntries = auditReader.getAuditHistory();
5464
}
5565

5666
/**
@@ -77,8 +87,8 @@ public ValidationResult validate() {
7787
allErrors.addAll(getValidationErrors(expectations, actualEntries));
7888

7989
return allErrors.isEmpty()
80-
? ValidationResult.success(VALIDATOR_NAME)
81-
: ValidationResult.failure(VALIDATOR_NAME, allErrors.toArray(new ValidationError[0]));
90+
? ValidationResult.success(VALIDATOR_NAME)
91+
: ValidationResult.failure(VALIDATOR_NAME, allErrors.toArray(new ValidationError[0]));
8292
}
8393

8494
private static List<ValidationError> getValidationErrors(List<AuditEntryExpectation> expectedEntries, List<AuditEntry> actualEntries) {
@@ -93,15 +103,15 @@ private static List<ValidationError> getValidationErrors(List<AuditEntryExpectat
93103
AuditEntryExpectation expected = i < expectedEntries.size() ? expectedEntries.get(i) : null;
94104
AuditEntry actual = i < actualEntries.size() ? actualEntries.get(i) : null;
95105
if( expected != null && actual != null) {
96-
allErrors.addAll(expected.compareWith(actual));
106+
allErrors.addAll(expected.compareWith(actual));
97107
} else if( expected != null) {
98-
AuditEntryDefinition def = expected.getDefinition();
108+
AuditEntryDefinition def = expected.getDefinition();
99109
allErrors.add(new MissingEntryError(i, def.getChangeId(), def.getState()));
100110
} else {
101111
assert actual != null;
102112
allErrors.add(new UnexpectedEntryError(i, actual.getTaskId(), actual.getState()));
103113
}
104-
114+
105115
}
106116
return allErrors;
107117
}
@@ -117,4 +127,16 @@ private List<String> getActualChangeIds() {
117127
.map(AuditEntry::getTaskId)
118128
.collect(Collectors.toList());
119129
}
130+
131+
public static class Args implements ValidatorArgs {
132+
private final List<AuditEntryDefinition> expectations;
133+
134+
public Args(List<AuditEntryDefinition> expectations) {
135+
this.expectations = expectations;
136+
}
137+
138+
public List<AuditEntryDefinition> getExpectations() {
139+
return expectations;
140+
}
141+
}
120142
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,22 @@ public ValidationResult validate(Throwable actualException) {
6868
}
6969
return ValidationResult.success(VALIDATOR_NAME);
7070
}
71+
72+
public static class Args implements io.flamingock.support.validation.ValidatorArgs {
73+
private final Class<? extends Throwable> exceptionClass;
74+
private final Consumer<Throwable> exceptionConsumer;
75+
76+
public Args(Class<? extends Throwable> exceptionClass, Consumer<Throwable> exceptionConsumer) {
77+
this.exceptionClass = exceptionClass;
78+
this.exceptionConsumer = exceptionConsumer;
79+
}
80+
81+
public Class<? extends Throwable> getExceptionClass() {
82+
return exceptionClass;
83+
}
84+
85+
public Consumer<Throwable> getExceptionConsumer() {
86+
return exceptionConsumer;
87+
}
88+
}
7189
}

0 commit comments

Comments
 (0)