diff --git a/legacy/mongock-importer-mongodb/src/main/java/io/flamingock/importer/mongock/mongodb/MongockImporterMongoDB.java b/legacy/mongock-importer-mongodb/src/main/java/io/flamingock/importer/mongock/mongodb/MongockImporterMongoDB.java index 0ba220ebe..7d4e830f9 100644 --- a/legacy/mongock-importer-mongodb/src/main/java/io/flamingock/importer/mongock/mongodb/MongockImporterMongoDB.java +++ b/legacy/mongock-importer-mongodb/src/main/java/io/flamingock/importer/mongock/mongodb/MongockImporterMongoDB.java @@ -21,6 +21,8 @@ import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.core.audit.AuditHistoryReader; import org.bson.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.time.Instant; import java.time.LocalDateTime; @@ -28,10 +30,13 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class MongockImporterMongoDB implements AuditHistoryReader { + private static final Logger logger = LoggerFactory.getLogger("MongockImporter"); + private final MongoCollection sourceCollection; public MongockImporterMongoDB(MongoDatabase mongoDatabase, String collectionName) { @@ -44,6 +49,7 @@ public List getAuditHistory() { .into(new ArrayList<>()) .stream() .map(MongockImporterMongoDB::toAuditEntry) + .filter(Objects::nonNull) .collect(Collectors.toList()); } @@ -55,6 +61,8 @@ private static AuditEntry toAuditEntry(Document document) { .toLocalDateTime(); if (changeEntry.shouldBeIgnored()) { + logger.info("Skipping Mongock audit entry with changeId[{}]: state=IGNORED (Mongock never executed this change; nothing to import).", + changeEntry.getChangeId()); return null; } return new AuditEntry( diff --git a/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java b/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java index fa4fad124..8ce564540 100644 --- a/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java +++ b/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java @@ -498,6 +498,65 @@ void GIVEN_unknownAuditEntriesAndRelaxedMode_WHEN_migratingToFlamingockCommunity ); } + @Test + @DisplayName("GIVEN Mongock audit history contains an IGNORED entry " + + "WHEN migrating to Flamingock Community " + + "THEN should skip the IGNORED entry without crashing " + + "AND import the rest of the history normally") + void GIVEN_ignoredAuditEntry_WHEN_migratingToFlamingockCommunity_THEN_shouldSkipIgnoredAndImportRest() throws java.text.ParseException { + // Regression test for IGNORED-state null leak in MongockImporterMongoDB.toAuditEntry(). + // Before the fix, the importer returned null for IGNORED entries and the caller + // (MongockImportChange.importHistory) crashed with: + // NullPointerException: Cannot invoke "AuditEntry.getSystemChange()" + // because "auditEntryFromOrigin" is null + // The fix filters nulls at both layers and logs the skipped entry. + + mongockTestHelper.setupBasicScenario(); + mongockTestHelper.write(new MongockChangeEntry( + "ignored-execution-1", + "ignored-change", + "mongock", + MongockTestHelper.DEFAULT_DATE_FORMAT.parse("2025-06-19T05:43:57.200Z"), + MongockChangeState.IGNORED, + io.flamingock.common.test.mongock.MongockChangeType.EXECUTION, + "io.example.IgnoredChangeUnit", + "apply", + null, + 0L, + MongockTestHelper.DEFAULT_HOSTNAME, + null, + false, + null + )); + + MongoDBSyncTargetSystem mongodbTargetSystem = new MongoDBSyncTargetSystem("mongodb-target-system", mongoClient, DATABASE_NAME); + + Runner flamingock = testKit.createBuilder() + .addTargetSystem(mongodbTargetSystem) + .build(); + + flamingock.run(); + + // IGNORED entry must NOT appear in the Flamingock audit store. + assertNull(getAuditEntryByChangeId("ignored-change"), + "IGNORED Mongock entry must not be imported into the Flamingock audit store"); + + // Remaining basic-scenario entries imported as normal, plus native changes executed. + auditHelper.verifyAuditSequenceStrict( + APPLIED("system-change-00001_before"), + APPLIED("system-change-00001"), + APPLIED("mongock-change-1_before"), + APPLIED("mongock-change-1"), + APPLIED("mongock-change-2"), + STARTED("migration-mongock-to-flamingock-community"), + APPLIED("migration-mongock-to-flamingock-community"), + STARTED("create-users-collection-with-index"), + APPLIED("create-users-collection-with-index"), + STARTED("seed-users"), + APPLIED("seed-users") + ); + } + @Test @DisplayName("GIVEN relaxed import flag with invalid value " + "WHEN migrating to Flamingock Community " + diff --git a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java index 37a9700ff..563aac132 100644 --- a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java +++ b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java @@ -31,7 +31,9 @@ import javax.inject.Named; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import static io.flamingock.internal.common.core.audit.AuditReaderType.MONGOCK; import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY; @@ -61,7 +63,10 @@ public void importHistory(@Named("change.targetSystem.id") String targetSystemId logger.info("Starting audit log migration from Mongock to Flamingock community audit store"); AuditHistoryReader legacyHistoryReader = getAuditHistoryReader(targetSystemId, targetSystemManager); PipelineHelper pipelineHelper = new PipelineHelper(pipelineDescriptor); - List legacyHistory = legacyHistoryReader.getAuditHistory(); + List legacyHistory = legacyHistoryReader.getAuditHistory() + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); boolean ignoreUnknownEntries = resolveIgnoreUnknownEntries(ignoreUnknownEntriesPropertyValue); validate(legacyHistory, targetSystemId, emptyOriginAllowedPropertyValue); legacyHistory.forEach(auditEntryFromOrigin -> {