1818import io .flamingock .internal .common .core .audit .AuditEntry ;
1919import io .flamingock .internal .common .core .audit .AuditReader ;
2020import io .flamingock .support .domain .AuditEntryDefinition ;
21+ import io .flamingock .support .stages .ThenStage ;
2122import io .flamingock .support .validation .SimpleValidator ;
2223import io .flamingock .support .validation .ValidatorArgs ;
2324import io .flamingock .support .validation .error .*;
2829import java .util .stream .Collectors ;
2930
3031/**
31- * Validator that performs strict sequence validation of audit entries.
32+ * Validates that the final state sequence of audit entries matches the expected definitions exactly .
3233 *
33- * <p>This validator verifies that the actual audit entries match the expected
34- * sequence exactly, both in count and in field values. Checking:</p>
34+ * <p>This validator focuses on the <strong>final state</strong> of each change, filtering out
35+ * intermediate states like {@code STARTED}. It validates only the states that represent
36+ * actual outcomes: {@code APPLIED}, {@code FAILED}, {@code ROLLED_BACK}, {@code ROLLBACK_FAILED}.</p>
37+ *
38+ * <p><strong>Exact sequence validation:</strong></p>
3539 * <ul>
36- * <li>Exact count match between expected and actual entries</li>
37- * <li>Strict field-by-field validation for each entry at each index</li>
38- * <li>Order preservation (expected[0] must match actual[0], etc.)</li>
40+ * <li>The number of actual entries must exactly match the number of expected definitions</li>
41+ * <li>Order is preserved: expected[0] must match actual[0], expected[1] must match actual[1], etc.</li>
3942 * </ul>
43+ *
44+ * <p>This is not a "contains" validator - if the audit log has 3 changes, you must
45+ * provide exactly 3 expected definitions.</p>
46+ *
47+ * <p><strong>Field validation:</strong></p>
48+ * <p>Only fields that are set in the {@link AuditEntryDefinition} are validated. The {@code changeId}
49+ * and {@code state} are always compared (they are required). Additional fields depend on how the
50+ * definition is constructed:</p>
51+ * <ul>
52+ * <li>Class-based factory methods (e.g., {@code APPLIED(MyChange.class)}) auto-extract fields
53+ * from annotations: author, className, methodName, targetSystemId, recoveryStrategy, order, transactional</li>
54+ * <li>String-based factory methods (e.g., {@code APPLIED("change-id")}) only set changeId and state</li>
55+ * <li>Use {@code withXxx()} methods to add or override specific fields to validate</li>
56+ * <li>Fields that are null in the definition are not compared</li>
57+ * </ul>
58+ *
59+ * <p><strong>Example:</strong></p>
60+ * <pre>{@code
61+ * testSupport.givenBuilderFromContext()
62+ * .whenRun()
63+ * .thenExpectAuditFinalStateSequence(
64+ * APPLIED(CreateUsersChange.class), // validates fields from annotations
65+ * APPLIED("template-change-id"), // validates only changeId and state
66+ * FAILED(BrokenChange.class).withErrorTrace("Expected error") // adds error trace validation
67+ * )
68+ * .verify();
69+ * }</pre>
70+ *
71+ * @see AuditEntryDefinition
72+ * @see ThenStage#andExpectAuditFinalStateSequence(AuditEntryDefinition...)
4073 */
41- public class AuditSequenceStrictValidator implements SimpleValidator {
74+ public class AuditFinalStateSequenceValidator implements SimpleValidator {
4275
43- private static final String VALIDATOR_NAME = "Audit Sequence (Strict) " ;
76+ private static final String VALIDATOR_NAME = "Audit Final State Sequence " ;
4477
45- private final AuditReader auditReader ;
4678 private final List <AuditEntryExpectation > expectations ;
4779 private final List <AuditEntry > actualEntries ;
4880 private static final List <AuditEntry .Status > EXCLUDED_STATES = Collections .singletonList (
4981 AuditEntry .Status .STARTED
5082 );
5183
52- public AuditSequenceStrictValidator (AuditReader auditReader , List <AuditEntryDefinition > definitions ) {
53- this .auditReader = auditReader ;
84+ public AuditFinalStateSequenceValidator (AuditReader auditReader , List <AuditEntryDefinition > definitions ) {
5485 this .expectations = definitions != null
5586 ? definitions .stream ()
5687 .map (AuditEntryExpectation ::new )
5788 .collect (Collectors .toList ())
5889 : new ArrayList <>();
5990
60- this .actualEntries = auditReader .getAuditHistory ().stream ()
91+ this .actualEntries = auditReader .getAuditHistory ().stream ()
6192 .filter (entry -> !EXCLUDED_STATES .contains (entry .getState ()))
6293 .sorted ()
6394 .collect (Collectors .toList ());
@@ -66,13 +97,12 @@ public AuditSequenceStrictValidator(AuditReader auditReader, List<AuditEntryDefi
6697 /**
6798 * Internal constructor for direct list initialization (used by tests).
6899 */
69- AuditSequenceStrictValidator (List <AuditEntryDefinition > expectedDefinitions , List <AuditEntry > actualEntries , AuditReader auditReader ) {
100+ AuditFinalStateSequenceValidator (List <AuditEntryDefinition > expectedDefinitions , List <AuditEntry > actualEntries ) {
70101 this .expectations = expectedDefinitions != null
71102 ? expectedDefinitions .stream ()
72103 .map (AuditEntryExpectation ::new )
73104 .collect (Collectors .toList ())
74105 : new ArrayList <>();
75- this .auditReader = auditReader ;
76106 this .actualEntries = actualEntries != null ? actualEntries : new ArrayList <>();
77107 }
78108
0 commit comments