|
22 | 22 | import io.flamingock.internal.common.core.audit.AuditEntry; |
23 | 23 | import io.flamingock.support.stages.ThenStage; |
24 | 24 | import io.flamingock.support.stages.WhenStage; |
| 25 | +import io.flamingock.support.validation.error.FieldMismatchError; |
25 | 26 |
|
26 | 27 | import java.lang.annotation.Annotation; |
27 | 28 | import java.lang.reflect.Method; |
28 | 29 | import java.time.LocalDateTime; |
| 30 | +import java.util.ArrayList; |
| 31 | +import java.util.List; |
| 32 | +import java.util.Objects; |
29 | 33 |
|
30 | 34 | import static io.flamingock.internal.common.core.audit.AuditEntry.Status.APPLIED; |
31 | 35 | import static io.flamingock.internal.common.core.audit.AuditEntry.Status.FAILED; |
@@ -466,6 +470,131 @@ public AuditEntryExpectation withTargetSystemId(String targetSystemId) { |
466 | 470 | return this; |
467 | 471 | } |
468 | 472 |
|
| 473 | + // ==================== Comparison Logic ==================== |
| 474 | + |
| 475 | + /** |
| 476 | + * Compares this expectation against an actual audit entry. |
| 477 | + * |
| 478 | + * <p>Returns a list of field mismatches (empty if all expected fields match). |
| 479 | + * Only fields with non-null expected values are verified, except for |
| 480 | + * {@code changeId} and {@code status} which are always verified.</p> |
| 481 | + * |
| 482 | + * <p>Timestamp verification supports two modes:</p> |
| 483 | + * <ul> |
| 484 | + * <li>Exact match: when {@code expectedCreatedAt} is set</li> |
| 485 | + * <li>Range match: when {@code timestampAfter} and/or {@code timestampBefore} are set</li> |
| 486 | + * </ul> |
| 487 | + * |
| 488 | + * @param actual the actual audit entry to compare against |
| 489 | + * @return list of field mismatch errors (empty if all match) |
| 490 | + */ |
| 491 | + public List<FieldMismatchError> compareWith(AuditEntry actual) { |
| 492 | + List<FieldMismatchError> errors = new ArrayList<>(); |
| 493 | + |
| 494 | + // Required fields - always verified |
| 495 | + if (!expectedChangeId.equals(actual.getTaskId())) { |
| 496 | + errors.add(new FieldMismatchError("changeId", expectedChangeId, actual.getTaskId())); |
| 497 | + } |
| 498 | + |
| 499 | + if (expectedState != actual.getState()) { |
| 500 | + errors.add(new FieldMismatchError("status", |
| 501 | + expectedState.name(), |
| 502 | + actual.getState() != null ? actual.getState().name() : null)); |
| 503 | + } |
| 504 | + |
| 505 | + // Optional fields - verified when non-null |
| 506 | + if (expectedExecutionId != null && !expectedExecutionId.equals(actual.getExecutionId())) { |
| 507 | + errors.add(new FieldMismatchError("executionId", expectedExecutionId, actual.getExecutionId())); |
| 508 | + } |
| 509 | + |
| 510 | + if (expectedStageId != null && !expectedStageId.equals(actual.getStageId())) { |
| 511 | + errors.add(new FieldMismatchError("stageId", expectedStageId, actual.getStageId())); |
| 512 | + } |
| 513 | + |
| 514 | + if (expectedAuthor != null && !expectedAuthor.equals(actual.getAuthor())) { |
| 515 | + errors.add(new FieldMismatchError("author", expectedAuthor, actual.getAuthor())); |
| 516 | + } |
| 517 | + |
| 518 | + if (expectedClassName != null && !expectedClassName.equals(actual.getClassName())) { |
| 519 | + errors.add(new FieldMismatchError("className", expectedClassName, actual.getClassName())); |
| 520 | + } |
| 521 | + |
| 522 | + if (expectedMethodName != null && !expectedMethodName.equals(actual.getMethodName())) { |
| 523 | + errors.add(new FieldMismatchError("methodName", expectedMethodName, actual.getMethodName())); |
| 524 | + } |
| 525 | + |
| 526 | + if (expectedMetadata != null && !Objects.equals(expectedMetadata, actual.getMetadata())) { |
| 527 | + errors.add(new FieldMismatchError("metadata", |
| 528 | + String.valueOf(expectedMetadata), |
| 529 | + String.valueOf(actual.getMetadata()))); |
| 530 | + } |
| 531 | + |
| 532 | + if (expectedExecutionMillis != null && expectedExecutionMillis != actual.getExecutionMillis()) { |
| 533 | + errors.add(new FieldMismatchError("executionMillis", |
| 534 | + String.valueOf(expectedExecutionMillis), |
| 535 | + String.valueOf(actual.getExecutionMillis()))); |
| 536 | + } |
| 537 | + |
| 538 | + if (expectedExecutionHostname != null && !expectedExecutionHostname.equals(actual.getExecutionHostname())) { |
| 539 | + errors.add(new FieldMismatchError("executionHostname", expectedExecutionHostname, actual.getExecutionHostname())); |
| 540 | + } |
| 541 | + |
| 542 | + if (expectedErrorTrace != null && !expectedErrorTrace.equals(actual.getErrorTrace())) { |
| 543 | + errors.add(new FieldMismatchError("errorTrace", expectedErrorTrace, actual.getErrorTrace())); |
| 544 | + } |
| 545 | + |
| 546 | + if (expectedTargetSystemId != null && !expectedTargetSystemId.equals(actual.getTargetSystemId())) { |
| 547 | + errors.add(new FieldMismatchError("targetSystemId", expectedTargetSystemId, actual.getTargetSystemId())); |
| 548 | + } |
| 549 | + |
| 550 | + // Timestamp verification |
| 551 | + compareTimestamp(actual, errors); |
| 552 | + |
| 553 | + return errors; |
| 554 | + } |
| 555 | + |
| 556 | + private void compareTimestamp(AuditEntry actual, List<FieldMismatchError> errors) { |
| 557 | + if (expectedCreatedAt != null) { |
| 558 | + // Exact match mode |
| 559 | + if (!expectedCreatedAt.equals(actual.getCreatedAt())) { |
| 560 | + errors.add(new FieldMismatchError("createdAt", |
| 561 | + expectedCreatedAt.toString(), |
| 562 | + actual.getCreatedAt() != null ? actual.getCreatedAt().toString() : null)); |
| 563 | + } |
| 564 | + } else if (timestampAfter != null || timestampBefore != null) { |
| 565 | + // Range match mode |
| 566 | + LocalDateTime actualTimestamp = actual.getCreatedAt(); |
| 567 | + if (actualTimestamp == null) { |
| 568 | + errors.add(new FieldMismatchError("createdAt", |
| 569 | + formatTimestampRange(), |
| 570 | + null)); |
| 571 | + } else { |
| 572 | + boolean afterOk = timestampAfter == null || |
| 573 | + actualTimestamp.isAfter(timestampAfter) || |
| 574 | + actualTimestamp.isEqual(timestampAfter); |
| 575 | + boolean beforeOk = timestampBefore == null || |
| 576 | + actualTimestamp.isBefore(timestampBefore) || |
| 577 | + actualTimestamp.isEqual(timestampBefore); |
| 578 | + |
| 579 | + if (!afterOk || !beforeOk) { |
| 580 | + errors.add(new FieldMismatchError("createdAt", |
| 581 | + formatTimestampRange(), |
| 582 | + actualTimestamp.toString())); |
| 583 | + } |
| 584 | + } |
| 585 | + } |
| 586 | + } |
| 587 | + |
| 588 | + private String formatTimestampRange() { |
| 589 | + if (timestampAfter != null && timestampBefore != null) { |
| 590 | + return String.format("between %s and %s", timestampAfter, timestampBefore); |
| 591 | + } else if (timestampAfter != null) { |
| 592 | + return String.format("after %s", timestampAfter); |
| 593 | + } else { |
| 594 | + return String.format("before %s", timestampBefore); |
| 595 | + } |
| 596 | + } |
| 597 | + |
469 | 598 | // ==================== Getters (for verification logic) ==================== |
470 | 599 |
|
471 | 600 | /** Returns the expected execution ID. */ |
|
0 commit comments