Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public void setExecutionId(String executionId) {
this.executionId = executionId;
}

public String getChangeId() {
return changeId;
}

public static CouchbaseChangeEntry fromJson(JsonObject doc) {
CouchbaseChangeEntry entry = new CouchbaseChangeEntry();
entry.executionId = doc.getString("executionId");
Expand All @@ -71,7 +75,15 @@ private static Long parseLong(Object value) {
throw new IllegalArgumentException("Cannot convert value to Long: " + value);
}

public boolean shouldBeIgnored() {
return MongockChangeState.valueOf(state) == MongockChangeState.IGNORED;
}

public AuditEntry toAuditEntry() {
if (shouldBeIgnored()) {
return null;
}

LocalDateTime ts = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());

MongockChangeState stateEnum = MongockChangeState.valueOf(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.flamingock.internal.common.core.audit.AuditHistoryReader;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class MongockImporterCouchbase implements AuditHistoryReader {
Expand Down Expand Up @@ -56,7 +57,15 @@ public List<AuditEntry> getAuditHistory() {

return result.rowsAsObject().stream()
.map(CouchbaseChangeEntry::fromJson)
.map(CouchbaseChangeEntry::toAuditEntry)
.map(MongockImporterCouchbase::toAuditEntry)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private static AuditEntry toAuditEntry(CouchbaseChangeEntry entry) {
if (entry.shouldBeIgnored()) {
return null;
}
return entry.toAuditEntry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,40 @@ void GIVEN_someChangeUnitsAlreadyExecuted_WHEN_migratingToFlamingockCommunity_TH

}

@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() {
Collection originCollection = cluster.bucket(MONGOCK_BUCKET_NAME).scope(MONGOCK_SCOPE_NAME).collection(MONGOCK_COLLECTION_NAME);

originCollection.upsert("mongock-change-1", createAuditObject("mongock-change-1"));
originCollection.upsert("mongock-change-2", createAuditObject("mongock-change-2"));
originCollection.upsert("ignored-change", createAuditObject("ignored-change", true, "io.example.IgnoredChangeUnit", "apply", "IGNORED"));

Runner flamingock = testKit.createBuilder()
.setAuditStore(auditStore)
.addTargetSystem(targetSystem)
.build();

flamingock.run();

auditHelper.verifyAuditSequenceStrict(
// Legacy imports from Mongock (APPLIED only - no STARTED for imported changes)
APPLIED("mongock-change-1"),
APPLIED("mongock-change-2"),

// System stage - actual system importer change
STARTED("migration-mongock-to-flamingock-community"),
APPLIED("migration-mongock-to-flamingock-community"),

// Application stage - new changes
STARTED("flamingock-change"),
APPLIED("flamingock-change")
);
}

@Test
@DisplayName("GIVEN mongock audit history empty " +
"AND no empty origen allowed value provided " +
Expand Down Expand Up @@ -587,12 +621,16 @@ private static JsonObject createAuditObject(String value) {
}

private static JsonObject createAuditObject(String value, boolean systemChange, String changeLogClass, String changeSetMethod) {
return createAuditObject(value, systemChange, changeLogClass, changeSetMethod, "EXECUTED");
}

private static JsonObject createAuditObject(String value, boolean systemChange, String changeLogClass, String changeSetMethod, String state) {
JsonObject doc = JsonObject.create()
.put("executionId", "exec-1")
.put("changeId", value)
.put("author", "author1")
.put("timestamp", Instant.now().toEpochMilli())
.put("state", "EXECUTED")
.put("state", state)
.put("type", "EXECUTION")
.put("changeLogClass", changeLogClass)
.put("changeSetMethod", changeSetMethod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,15 @@ public void setSystemChange(Boolean systemChange) {
this.systemChange = systemChange;
}

public boolean shouldBeIgnored() {
return MongockChangeState.valueOf(state) == MongockChangeState.IGNORED;
}

public AuditEntry toAuditEntry() {
if (shouldBeIgnored()) {
return null;
}

long epochMillis;
try {
epochMillis = Long.parseLong(timestamp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

Expand All @@ -44,7 +45,15 @@ public List<AuditEntry> getAuditHistory() {
.collect(Collectors.toList());

return entries.stream()
.map(MongockAuditEntry::toAuditEntry)
.map(MongockImporterDynamoDB::toAuditEntry)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private static AuditEntry toAuditEntry(MongockAuditEntry entry) {
if (entry.shouldBeIgnored()) {
return null;
}
return entry.toAuditEntry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import io.flamingock.api.annotations.EnableFlamingock;
import io.flamingock.api.annotations.Stage;
import io.flamingock.common.test.mongock.MongockChangeEntry;
import io.flamingock.common.test.mongock.MongockChangeState;
import io.flamingock.store.dynamodb.DynamoDBAuditStore;
import io.flamingock.core.kit.TestKit;
import io.flamingock.core.kit.audit.AuditTestHelper;
Expand Down Expand Up @@ -420,6 +422,51 @@ 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 {
mongockTestHelper.setupBasicScenario();
mongockTestHelper.write(new MongockChangeEntry(
"ignored-execution-1",
"ignored-change",
"mongock",
io.flamingock.common.test.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,
io.flamingock.common.test.mongock.MongockTestHelper.DEFAULT_HOSTNAME,
null,
false,
null
));

DynamoDBTargetSystem dynamodbTargetSystem = new DynamoDBTargetSystem("dynamodb-target-system", client);

Runner flamingock = testKit.createBuilder()
.addTargetSystem(dynamodbTargetSystem)
.build();

flamingock.run();

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-table"),
APPLIED("create-users-table")
);
}

@Test
@DisplayName("GIVEN relaxed import flag with invalid value " +
"WHEN migrating to Flamingock Community " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class MongockImporterMongoDBReactive implements AuditHistoryReader {
Expand All @@ -43,19 +44,21 @@ public List<AuditEntry> getAuditHistory() {
return PublisherSync.collect(sourceCollection.find())
.stream()
.map(MongockImporterMongoDBReactive::toAuditEntry)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}


private static AuditEntry toAuditEntry(Document document) {
MongockAuditEntry changeEntry = toChangeEntry(document);
LocalDateTime timestamp = Instant.ofEpochMilli(changeEntry.getTimestamp().getTime())
.atZone(ZoneId.systemDefault())
.toLocalDateTime();

if (changeEntry.shouldBeIgnored()) {
return null;
}

LocalDateTime timestamp = Instant.ofEpochMilli(changeEntry.getTimestamp().getTime())
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
return new AuditEntry(
changeEntry.getExecutionId(),
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,16 @@ void shouldMapExecutedEntry() {
}

@Test
@DisplayName("Should map an IGNORED legacy entry to null")
void shouldMapIgnoredEntryToNull() {
@DisplayName("Should skip an IGNORED legacy entry")
void shouldSkipIgnoredEntry() {
seed(document("users-initialization", "EXECUTED", "EXECUTION", "pretend-mongock-run"));
seed(document("ghost-extra", "IGNORED", "EXECUTION", null));

MongockImporterMongoDBReactive importer = new MongockImporterMongoDBReactive(database, LEGACY_COLLECTION);
List<AuditEntry> history = importer.getAuditHistory();

Assertions.assertEquals(2, history.size());
Assertions.assertEquals(1, history.size());
Assertions.assertEquals("users-initialization", history.get(0).getChangeId());
Assertions.assertNull(history.get(1));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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 {
Expand All @@ -44,6 +45,7 @@ public List<AuditEntry> getAuditHistory() {
.into(new ArrayList<>())
.stream()
.map(MongockImporterMongoDB::toAuditEntry)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 " +
Expand Down
Loading