Skip to content

Commit 2a49087

Browse files
committed
feat: flamingock test support: AuditSequenceStrictValidator (WIP)
1 parent 85cb196 commit 2a49087

File tree

4 files changed

+434
-2
lines changed

4 files changed

+434
-2
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ dependencies {
44
testImplementation(platform("org.junit:junit-bom:5.10.0"))
55
testImplementation("org.junit.jupiter:junit-jupiter")
66
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
7+
// JUnit for assertion utilities
8+
api("org.junit.jupiter:junit-jupiter-api:5.9.2")
79
}
810

911
description = "Test support module for Flamingock framework"
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
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.domain;
17+
18+
import io.flamingock.internal.common.core.audit.AuditEntry;
19+
import io.flamingock.internal.common.core.audit.AuditTxType;
20+
21+
import java.time.LocalDateTime;
22+
import java.util.List;
23+
24+
import static org.junit.jupiter.api.Assertions.*;
25+
26+
/**
27+
* Comprehensive assertion utilities for AuditEntry testing.
28+
* Provides systematic verification of all audit entry fields to ensure
29+
* complete correctness of audit persistence across different storage implementations.
30+
*
31+
* <p><strong>Usage Examples:</strong></p>
32+
* <pre>{@code
33+
* // Complete audit entry verification
34+
* AuditEntryExpectation expected = auditEntry()
35+
* .withTaskId("test-change")
36+
* .withState(EXECUTED)
37+
* .withTxType(TX_SHARED);
38+
*
39+
* AuditEntryAssertions.assertAuditEntry(actualEntry, expected);
40+
*
41+
* // Quick individual field verification
42+
* AuditEntryAssertions.assertTransactionFields(entry, TX_SHARED, "custom-target-system");
43+
*
44+
* // Multiple entries verification
45+
* AuditEntryAssertions.assertAuditSequence(entries,
46+
* auditEntry().withState(STARTED),
47+
* auditEntry().withState(APPLIED));
48+
* }</pre>
49+
*/
50+
public class AuditEntryAssertions {
51+
52+
/**
53+
* Comprehensive verification of all audit entry fields against expectations.
54+
* This is the primary method for complete audit entry validation.
55+
*
56+
* @param actual the actual audit entry to verify
57+
* @param expected the expected values
58+
*/
59+
public static void assertAuditEntry(AuditEntry actual, AuditEntryExpectation expected) {
60+
assertNotNull(actual, "Audit entry should not be null");
61+
62+
// Identity fields
63+
assertEquals(expected.getExpectedExecutionId(), actual.getExecutionId(),
64+
"ExecutionId mismatch");
65+
66+
67+
assertEquals(expected.getExpectedStageId(), actual.getStageId(),
68+
"StageId mismatch");
69+
70+
71+
assertEquals(expected.getExpectedTaskId(), actual.getTaskId(),
72+
"TaskId mismatch");
73+
74+
75+
// Metadata fields
76+
assertEquals(expected.getExpectedAuthor(), actual.getAuthor(),
77+
"Author mismatch");
78+
79+
80+
// Timestamp verification (flexible)
81+
assertNotNull(actual.getCreatedAt(), "CreatedAt should not be null");
82+
83+
if (expected.getExpectedCreatedAt() != null) {
84+
assertEquals(expected.getExpectedCreatedAt(), actual.getCreatedAt(),
85+
"CreatedAt exact match failed");
86+
} else if (expected.getTimestampAfter() != null && expected.getTimestampBefore() != null) {
87+
assertTrue(actual.getCreatedAt().isAfter(expected.getTimestampAfter()) ||
88+
actual.getCreatedAt().isEqual(expected.getTimestampAfter()),
89+
"CreatedAt should be after " + expected.getTimestampAfter());
90+
assertTrue(actual.getCreatedAt().isBefore(expected.getTimestampBefore()) ||
91+
actual.getCreatedAt().isEqual(expected.getTimestampBefore()),
92+
"CreatedAt should be before " + expected.getTimestampBefore());
93+
}
94+
95+
96+
// State fields
97+
if (expected.getExpectedState() != null) {
98+
assertEquals(expected.getExpectedState(), actual.getState(),
99+
"State mismatch");
100+
}
101+
102+
if (expected.getExpectedType() != null) {
103+
assertEquals(expected.getExpectedType(), actual.getType(),
104+
"ExecutionType mismatch");
105+
}
106+
107+
// Execution fields
108+
if (expected.getExpectedClassName() != null) {
109+
assertEquals(expected.getExpectedClassName(), actual.getClassName(),
110+
"ClassName mismatch");
111+
}
112+
113+
if (expected.getExpectedMethodName() != null) {
114+
assertEquals(expected.getExpectedMethodName(), actual.getMethodName(),
115+
"MethodName mismatch");
116+
}
117+
118+
if (expected.getExpectedMetadata() != null) {
119+
assertEquals(expected.getExpectedMetadata(), actual.getMetadata(),
120+
"Metadata mismatch");
121+
}
122+
123+
// Performance fields
124+
assertEquals(expected.getExpectedExecutionMillis().longValue(), actual.getExecutionMillis(),
125+
"ExecutionMillis mismatch");
126+
127+
128+
assertEquals(expected.getExpectedExecutionHostname(), actual.getExecutionHostname(),
129+
"ExecutionHostname mismatch");
130+
131+
132+
// Error fields
133+
if (expected.getExpectedErrorTrace() != null) {
134+
assertEquals(expected.getExpectedErrorTrace(), actual.getErrorTrace(),
135+
"ErrorTrace mismatch");
136+
}
137+
138+
// System fields
139+
if (expected.getExpectedSystemChange() != null) {
140+
assertEquals(expected.getExpectedSystemChange(), actual.getSystemChange(),
141+
"SystemChange mismatch");
142+
}
143+
144+
// Transaction fields
145+
if (expected.getExpectedTxType() != null) {
146+
assertEquals(expected.getExpectedTxType(), actual.getTxType(),
147+
"TxType mismatch");
148+
}
149+
150+
if (expected.getExpectedTargetSystemId() != null) {
151+
assertEquals(expected.getExpectedTargetSystemId(), actual.getTargetSystemId(),
152+
"TargetSystemId mismatch");
153+
}
154+
}
155+
156+
/**
157+
* Quick verification of basic audit entry identity fields.
158+
*
159+
* @param entry the audit entry to verify
160+
* @param expectedTaskId expected task ID
161+
* @param expectedAuthor expected author
162+
* @param expectedState expected audit entry state
163+
*/
164+
//TODO add author check, when CodeChangeTestDefinition adds it
165+
public static void assertBasicFields(AuditEntry entry, String expectedTaskId, String expectedAuthor,
166+
AuditEntry.Status expectedState) {
167+
assertNotNull(entry, "Audit entry should not be null");
168+
assertEquals(expectedTaskId, entry.getTaskId(), "TaskId mismatch");
169+
// assertEquals(expectedAuthor, entry.getAuthor(), "Author mismatch");
170+
assertEquals(expectedState, entry.getState(), "State mismatch");
171+
}
172+
173+
/**
174+
* Quick verification of execution-related fields.
175+
*
176+
* @param entry the audit entry to verify
177+
* @param expectedClassName expected class name
178+
* @param expectedMethodName expected method name
179+
* @param expectedType expected execution type
180+
*/
181+
public static void assertExecutionFields(AuditEntry entry, String expectedClassName,
182+
String expectedMethodName, AuditEntry.ExecutionType expectedType) {
183+
assertNotNull(entry, "Audit entry should not be null");
184+
assertEquals(expectedClassName, entry.getClassName(), "ClassName mismatch");
185+
assertEquals(expectedMethodName, entry.getMethodName(), "MethodName mismatch");
186+
assertEquals(expectedType, entry.getType(), "ExecutionType mismatch");
187+
}
188+
189+
/**
190+
* Quick verification of transaction-related fields.
191+
*
192+
* @param entry the audit entry to verify
193+
* @param expectedTxType expected transaction type
194+
* @param expectedTargetSystemId expected target system ID
195+
*/
196+
public static void assertTransactionFields(AuditEntry entry, AuditTxType expectedTxType,
197+
String expectedTargetSystemId) {
198+
assertNotNull(entry, "Audit entry should not be null");
199+
assertEquals(expectedTxType, entry.getTxType(), "TxType mismatch");
200+
assertEquals(expectedTargetSystemId, entry.getTargetSystemId(), "TargetSystemId mismatch");
201+
}
202+
203+
/**
204+
* Quick verification of timing and performance fields.
205+
*
206+
* @param entry the audit entry to verify
207+
* @param timestampAfter expected timestamp should be after this time (can be null)
208+
* @param timestampBefore expected timestamp should be before this time (can be null)
209+
*/
210+
public static void assertTimingFields(AuditEntry entry, LocalDateTime timestampAfter,
211+
LocalDateTime timestampBefore) {
212+
assertNotNull(entry, "Audit entry should not be null");
213+
assertNotNull(entry.getCreatedAt(), "CreatedAt should not be null");
214+
215+
if (timestampAfter != null) {
216+
assertTrue(entry.getCreatedAt().isAfter(timestampAfter) ||
217+
entry.getCreatedAt().isEqual(timestampAfter),
218+
"CreatedAt should be after " + timestampAfter);
219+
}
220+
221+
if (timestampBefore != null) {
222+
assertTrue(entry.getCreatedAt().isBefore(timestampBefore) ||
223+
entry.getCreatedAt().isEqual(timestampBefore),
224+
"CreatedAt should be before " + timestampBefore);
225+
}
226+
227+
assertTrue(entry.getExecutionMillis() >= 0, "ExecutionMillis should be non-negative");
228+
}
229+
230+
/**
231+
* Quick verification of system-related fields.
232+
*
233+
* @param entry the audit entry to verify
234+
* @param expectedSystemChange expected system change flag
235+
* @param expectedHostname expected execution hostname (can be null)
236+
*/
237+
public static void assertSystemFields(AuditEntry entry, boolean expectedSystemChange,
238+
String expectedHostname) {
239+
assertNotNull(entry, "Audit entry should not be null");
240+
assertEquals(expectedSystemChange, entry.getSystemChange(), "SystemChange mismatch");
241+
242+
if (expectedHostname != null) {
243+
assertEquals(expectedHostname, entry.getExecutionHostname(), "ExecutionHostname mismatch");
244+
}
245+
}
246+
247+
/**
248+
* Verification of multiple audit entries in sequence.
249+
* Useful for testing complete execution flows (STARTED to APPLIED, etc.)
250+
*
251+
* @param actualEntries list of actual audit entries from the system
252+
* @param expectedEntries expected audit entry expectations to verify against
253+
*/
254+
public static void assertAuditSequence(List<AuditEntry> actualEntries,
255+
AuditEntryExpectation... expectedEntries) {
256+
assertNotNull(actualEntries, "Audit entries list should not be null");
257+
assertEquals(expectedEntries.length, actualEntries.size(),
258+
"Expected " + expectedEntries.length + " audit entries, but got " + actualEntries.size());
259+
260+
for (int i = 0; i < expectedEntries.length; i++) {
261+
assertAuditEntry(actualEntries.get(i), expectedEntries[i]);
262+
}
263+
}
264+
265+
/**
266+
* Verification that all entries in a sequence have the same core identity fields.
267+
* Useful for verifying that STARTED and APPLIED entries belong to the same execution.
268+
*
269+
* @param entries list of audit entries to verify belong to same execution
270+
*/
271+
public static void assertSameExecution(List<AuditEntry> entries) {
272+
assertNotNull(entries, "Audit entries list should not be null");
273+
assertTrue(entries.size() >= 2, "Need at least 2 entries to verify same execution");
274+
275+
String expectedExecutionId = entries.get(0).getExecutionId();
276+
String expectedStageId = entries.get(0).getStageId();
277+
String expectedTaskId = entries.get(0).getTaskId();
278+
279+
for (AuditEntry entry : entries) {
280+
assertEquals(expectedExecutionId, entry.getExecutionId(),
281+
"All entries should have same executionId");
282+
assertEquals(expectedStageId, entry.getStageId(),
283+
"All entries should have same stageId");
284+
assertEquals(expectedTaskId, entry.getTaskId(),
285+
"All entries should have same taskId");
286+
}
287+
}
288+
289+
/**
290+
* Quick verification that critical audit fields are not null/empty.
291+
* Useful for basic audit entry completeness checks.
292+
*
293+
* @param entry the audit entry to verify for completeness
294+
*/
295+
public static void assertAuditEntryCompleteness(AuditEntry entry) {
296+
assertNotNull(entry, "Audit entry should not be null");
297+
298+
// Critical fields that should never be null/empty
299+
assertNotNull(entry.getExecutionId(), "ExecutionId should not be null");
300+
assertFalse(entry.getExecutionId().trim().isEmpty(), "ExecutionId should not be empty");
301+
302+
assertNotNull(entry.getStageId(), "StageId should not be null");
303+
assertFalse(entry.getStageId().trim().isEmpty(), "StageId should not be empty");
304+
305+
assertNotNull(entry.getTaskId(), "TaskId should not be null");
306+
assertFalse(entry.getTaskId().trim().isEmpty(), "TaskId should not be empty");
307+
308+
assertNotNull(entry.getAuthor(), "Author should not be null");
309+
assertFalse(entry.getAuthor().trim().isEmpty(), "Author should not be empty");
310+
311+
assertNotNull(entry.getCreatedAt(), "CreatedAt should not be null");
312+
assertNotNull(entry.getState(), "State should not be null");
313+
assertNotNull(entry.getType(), "ExecutionType should not be null");
314+
assertNotNull(entry.getTxType(), "TxType should not be null");
315+
316+
// Fields that can be null but if present should not be empty strings
317+
if (entry.getClassName() != null) {
318+
assertFalse(entry.getClassName().trim().isEmpty(), "ClassName should not be empty if present");
319+
}
320+
321+
if (entry.getMethodName() != null) {
322+
assertFalse(entry.getMethodName().trim().isEmpty(), "MethodName should not be empty if present");
323+
}
324+
325+
if (entry.getTargetSystemId() != null) {
326+
assertFalse(entry.getTargetSystemId().trim().isEmpty(), "TargetSystemId should not be empty if present");
327+
}
328+
}
329+
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.flamingock.api.annotations.Rollback;
2121
import io.flamingock.api.annotations.TargetSystem;
2222
import io.flamingock.internal.common.core.audit.AuditEntry;
23+
import io.flamingock.internal.common.core.audit.AuditTxType;
2324
import io.flamingock.support.stages.ThenStage;
2425
import io.flamingock.support.stages.WhenStage;
2526

@@ -100,6 +101,12 @@ public class AuditEntryExpectation {
100101
private String expectedExecutionHostname;
101102
private String expectedErrorTrace;
102103
private String expectedTargetSystemId;
104+
private Boolean expectedSystemChange;
105+
private AuditTxType expectedTxType;
106+
private String expectedTaskId;
107+
private AuditEntry.ExecutionType expectedType;
108+
109+
103110

104111
// Time range for flexible timestamp verification
105112
private LocalDateTime timestampAfter;
@@ -477,6 +484,9 @@ public AuditEntryExpectation withTargetSystemId(String targetSystemId) {
477484
/** Returns the expected change ID. */
478485
public String getExpectedChangeId() { return expectedChangeId; }
479486

487+
/** Returns the expected task ID. */
488+
public String getExpectedTaskId() { return expectedTaskId; }
489+
480490
/** Returns the expected author. */
481491
public String getExpectedAuthor() { return expectedAuthor; }
482492

@@ -512,4 +522,26 @@ public AuditEntryExpectation withTargetSystemId(String targetSystemId) {
512522

513523
/** Returns the upper bound for timestamp range verification. */
514524
public LocalDateTime getTimestampBefore() { return timestampBefore; }
525+
526+
public AuditEntry.ExecutionType getExpectedType() { return expectedType; }
527+
528+
public Boolean getExpectedSystemChange() { return expectedSystemChange; }
529+
530+
public AuditTxType getExpectedTxType() { return expectedTxType; }
531+
532+
public void setExpectedSystemChange(Boolean expectedSystemChange) {
533+
this.expectedSystemChange = expectedSystemChange;
534+
}
535+
536+
public void setExpectedTxType(AuditTxType expectedTxType) {
537+
this.expectedTxType = expectedTxType;
538+
}
539+
540+
public void setExpectedTaskId(String expectedTaskId) {
541+
this.expectedTaskId = expectedTaskId;
542+
}
543+
544+
public void setExpectedType(AuditEntry.ExecutionType expectedType) {
545+
this.expectedType = expectedType;
546+
}
515547
}

0 commit comments

Comments
 (0)