2222import io .flamingock .internal .common .core .audit .AuditEntry ;
2323import io .flamingock .support .stages .ThenStage ;
2424import io .flamingock .support .stages .WhenStage ;
25+ import io .flamingock .support .validation .error .FieldMismatchError ;
2526
2627import java .lang .annotation .Annotation ;
2728import java .lang .reflect .Method ;
2829import java .time .LocalDateTime ;
30+ import java .util .ArrayList ;
31+ import java .util .List ;
32+ import java .util .Objects ;
2933
3034import static io .flamingock .internal .common .core .audit .AuditEntry .Status .APPLIED ;
3135import static io .flamingock .internal .common .core .audit .AuditEntry .Status .FAILED ;
@@ -162,8 +166,6 @@ public static AuditEntryExpectation ROLLBACK_FAILED(String expectedChangeId) {
162166 return new AuditEntryExpectation (expectedChangeId , ROLLBACK_FAILED );
163167 }
164168
165- // ==================== Class-Based Factory Methods ====================
166-
167169 /**
168170 * Creates an expectation for a successfully applied change by extracting
169171 * metadata from the change class annotations.
@@ -296,8 +298,6 @@ private static String findMethodName(Class<?> changeClass, AuditEntry.Status sta
296298 annotationClass .getSimpleName ()));
297299 }
298300
299- // ==================== Identity Fields ====================
300-
301301 /**
302302 * Sets the expected execution ID for verification.
303303 *
@@ -324,8 +324,6 @@ public AuditEntryExpectation withStageId(String stageId) {
324324 return this ;
325325 }
326326
327- // ==================== Metadata Fields ====================
328-
329327 /**
330328 * Sets the expected author of the change.
331329 *
@@ -368,8 +366,6 @@ public AuditEntryExpectation withTimestampBetween(LocalDateTime after, LocalDate
368366 return this ;
369367 }
370368
371- // ==================== Execution Fields ====================
372-
373369 /**
374370 * Sets the expected fully-qualified class name.
375371 *
@@ -409,8 +405,6 @@ public AuditEntryExpectation withMetadata(Object metadata) {
409405 return this ;
410406 }
411407
412- // ==================== Performance Fields ====================
413-
414408 /**
415409 * Sets the expected execution duration in milliseconds.
416410 *
@@ -437,8 +431,6 @@ public AuditEntryExpectation withExecutionHostname(String hostname) {
437431 return this ;
438432 }
439433
440- // ==================== Error Fields ====================
441-
442434 /**
443435 * Sets the expected error trace for failed changes.
444436 *
@@ -453,8 +445,6 @@ public AuditEntryExpectation withErrorTrace(String errorTrace) {
453445 return this ;
454446 }
455447
456- // ==================== Target System Fields ====================
457-
458448 /**
459449 * Sets the expected target system identifier.
460450 *
@@ -466,50 +456,128 @@ public AuditEntryExpectation withTargetSystemId(String targetSystemId) {
466456 return this ;
467457 }
468458
469- // ==================== Getters (for verification logic) ====================
459+ /**
460+ * Compares this expectation against an actual audit entry.
461+ *
462+ * <p>Returns a list of field mismatches (empty if all expected fields match).
463+ * Only fields with non-null expected values are verified, except for
464+ * {@code changeId} and {@code status} which are always verified.</p>
465+ *
466+ * <p>Timestamp verification supports two modes:</p>
467+ * <ul>
468+ * <li>Exact match: when {@code expectedCreatedAt} is set</li>
469+ * <li>Range match: when {@code timestampAfter} and/or {@code timestampBefore} are set</li>
470+ * </ul>
471+ *
472+ * @param actual the actual audit entry to compare against
473+ * @return list of field mismatch errors (empty if all match)
474+ */
475+ public List <FieldMismatchError > compareWith (AuditEntry actual ) {
476+ List <FieldMismatchError > errors = new ArrayList <>();
470477
471- /** Returns the expected execution ID. */
472- public String getExpectedExecutionId () { return expectedExecutionId ; }
478+ // Required fields - always verified
479+ if (!expectedChangeId .equals (actual .getTaskId ())) {
480+ errors .add (new FieldMismatchError ("changeId" , expectedChangeId , actual .getTaskId ()));
481+ }
473482
474- /** Returns the expected stage ID. */
475- public String getExpectedStageId () { return expectedStageId ; }
483+ if (expectedState != actual .getState ()) {
484+ errors .add (new FieldMismatchError ("status" ,
485+ expectedState .name (),
486+ actual .getState () != null ? actual .getState ().name () : null ));
487+ }
476488
477- /** Returns the expected change ID. */
478- public String getExpectedChangeId () { return expectedChangeId ; }
489+ // Optional fields - verified when non-null
490+ if (expectedExecutionId != null && !expectedExecutionId .equals (actual .getExecutionId ())) {
491+ errors .add (new FieldMismatchError ("executionId" , expectedExecutionId , actual .getExecutionId ()));
492+ }
479493
480- /** Returns the expected author. */
481- public String getExpectedAuthor () { return expectedAuthor ; }
494+ if (expectedStageId != null && !expectedStageId .equals (actual .getStageId ())) {
495+ errors .add (new FieldMismatchError ("stageId" , expectedStageId , actual .getStageId ()));
496+ }
482497
483- /** Returns the expected creation timestamp. */
484- public LocalDateTime getExpectedCreatedAt () { return expectedCreatedAt ; }
498+ if (expectedAuthor != null && !expectedAuthor .equals (actual .getAuthor ())) {
499+ errors .add (new FieldMismatchError ("author" , expectedAuthor , actual .getAuthor ()));
500+ }
485501
486- /** Returns the expected audit entry status. */
487- public AuditEntry .Status getExpectedState () { return expectedState ; }
502+ if (expectedClassName != null && !expectedClassName .equals (actual .getClassName ())) {
503+ errors .add (new FieldMismatchError ("className" , expectedClassName , actual .getClassName ()));
504+ }
488505
489- /** Returns the expected class name. */
490- public String getExpectedClassName () { return expectedClassName ; }
506+ if (expectedMethodName != null && !expectedMethodName .equals (actual .getMethodName ())) {
507+ errors .add (new FieldMismatchError ("methodName" , expectedMethodName , actual .getMethodName ()));
508+ }
491509
492- /** Returns the expected method name. */
493- public String getExpectedMethodName () { return expectedMethodName ; }
510+ if (expectedMetadata != null && !Objects .equals (expectedMetadata , actual .getMetadata ())) {
511+ errors .add (new FieldMismatchError ("metadata" ,
512+ String .valueOf (expectedMetadata ),
513+ String .valueOf (actual .getMetadata ())));
514+ }
494515
495- /** Returns the expected metadata. */
496- public Object getExpectedMetadata () { return expectedMetadata ; }
516+ if (expectedExecutionMillis != null && expectedExecutionMillis != actual .getExecutionMillis ()) {
517+ errors .add (new FieldMismatchError ("executionMillis" ,
518+ String .valueOf (expectedExecutionMillis ),
519+ String .valueOf (actual .getExecutionMillis ())));
520+ }
497521
498- /** Returns the expected execution duration in milliseconds. */
499- public Long getExpectedExecutionMillis () { return expectedExecutionMillis ; }
522+ if (expectedExecutionHostname != null && !expectedExecutionHostname .equals (actual .getExecutionHostname ())) {
523+ errors .add (new FieldMismatchError ("executionHostname" , expectedExecutionHostname , actual .getExecutionHostname ()));
524+ }
500525
501- /** Returns the expected execution hostname. */
502- public String getExpectedExecutionHostname () { return expectedExecutionHostname ; }
526+ if (expectedErrorTrace != null && !expectedErrorTrace .equals (actual .getErrorTrace ())) {
527+ errors .add (new FieldMismatchError ("errorTrace" , expectedErrorTrace , actual .getErrorTrace ()));
528+ }
503529
504- /** Returns the expected error trace. */
505- public String getExpectedErrorTrace () { return expectedErrorTrace ; }
530+ if (expectedTargetSystemId != null && !expectedTargetSystemId .equals (actual .getTargetSystemId ())) {
531+ errors .add (new FieldMismatchError ("targetSystemId" , expectedTargetSystemId , actual .getTargetSystemId ()));
532+ }
506533
507- /** Returns the expected target system ID. */
508- public String getExpectedTargetSystemId () { return expectedTargetSystemId ; }
534+ errors .addAll (compareTimestamp (actual ));
509535
510- /** Returns the lower bound for timestamp range verification. */
511- public LocalDateTime getTimestampAfter () { return timestampAfter ; }
536+ return errors ;
537+ }
538+
539+ private List <FieldMismatchError > compareTimestamp (AuditEntry actual ) {
540+ List <FieldMismatchError > errors = new ArrayList <>();
541+ if (expectedCreatedAt != null ) {
542+ // Exact match mode
543+ if (!expectedCreatedAt .equals (actual .getCreatedAt ())) {
544+ errors .add (new FieldMismatchError ("createdAt" ,
545+ expectedCreatedAt .toString (),
546+ actual .getCreatedAt () != null ? actual .getCreatedAt ().toString () : null ));
547+ }
548+ } else if (timestampAfter != null || timestampBefore != null ) {
549+ // Range match mode
550+ LocalDateTime actualTimestamp = actual .getCreatedAt ();
551+ if (actualTimestamp == null ) {
552+ errors .add (new FieldMismatchError ("createdAt" ,
553+ formatTimestampRange (),
554+ null ));
555+ } else {
556+ boolean afterOk = timestampAfter == null ||
557+ actualTimestamp .isAfter (timestampAfter ) ||
558+ actualTimestamp .isEqual (timestampAfter );
559+ boolean beforeOk = timestampBefore == null ||
560+ actualTimestamp .isBefore (timestampBefore ) ||
561+ actualTimestamp .isEqual (timestampBefore );
562+
563+ if (!afterOk || !beforeOk ) {
564+ errors .add (new FieldMismatchError ("createdAt" ,
565+ formatTimestampRange (),
566+ actualTimestamp .toString ()));
567+ }
568+ }
569+ }
570+ return errors ;
571+ }
572+
573+ private String formatTimestampRange () {
574+ if (timestampAfter != null && timestampBefore != null ) {
575+ return String .format ("between %s and %s" , timestampAfter , timestampBefore );
576+ } else if (timestampAfter != null ) {
577+ return String .format ("after %s" , timestampAfter );
578+ } else {
579+ return String .format ("before %s" , timestampBefore );
580+ }
581+ }
512582
513- /** Returns the upper bound for timestamp range verification. */
514- public LocalDateTime getTimestampBefore () { return timestampBefore ; }
515583}
0 commit comments