diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/MongoChangeTemplate.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/MongoChangeTemplate.java index 9890ec4ae..21dfd6fc0 100644 --- a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/MongoChangeTemplate.java +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/MongoChangeTemplate.java @@ -21,9 +21,10 @@ import io.flamingock.api.annotations.Nullable; import io.flamingock.api.annotations.Rollback; import io.flamingock.api.template.AbstractChangeTemplate; +import io.flamingock.template.mongodb.model.MongoApplyPayload; import io.flamingock.template.mongodb.model.MongoOperation; -public class MongoChangeTemplate extends AbstractChangeTemplate { +public class MongoChangeTemplate extends AbstractChangeTemplate { public MongoChangeTemplate() { super(MongoOperation.class); @@ -34,7 +35,7 @@ public void apply(MongoDatabase db, @Nullable ClientSession clientSession) { if (this.isTransactional && clientSession == null) { throw new IllegalArgumentException(String.format("Transactional change[%s] requires transactional ecosystem with ClientSession", changeId)); } - executeOp(db, applyPayload, clientSession); + executeOperations(db, applyPayload, clientSession); } @Rollback @@ -42,11 +43,16 @@ public void rollback(MongoDatabase db, @Nullable ClientSession clientSession) { if (this.isTransactional && clientSession == null) { throw new IllegalArgumentException(String.format("Transactional change[%s] requires transactional ecosystem with ClientSession", changeId)); } - executeOp(db, rollbackPayload, clientSession); + executeOperations(db, rollbackPayload, clientSession); } - private void executeOp(MongoDatabase db, MongoOperation op, ClientSession clientSession) { - op.getOperator(db).apply(clientSession); + private void executeOperations(MongoDatabase db, MongoApplyPayload payload, ClientSession clientSession) { + if (payload == null) { + return; + } + for (MongoOperation op : payload.getOperations()) { + op.getOperator(db).apply(clientSession); + } } } \ No newline at end of file diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/mapper/CreateViewOptionsMapper.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/mapper/CreateViewOptionsMapper.java new file mode 100644 index 000000000..a528bbe3b --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/mapper/CreateViewOptionsMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.mapper; + +import com.mongodb.client.model.CreateViewOptions; + +import java.util.Map; + +import static io.flamingock.template.mongodb.mapper.MapperUtil.getCollation; + +public final class CreateViewOptionsMapper { + + private CreateViewOptionsMapper() {} + + public static CreateViewOptions map(Map options) { + CreateViewOptions result = new CreateViewOptions(); + + if (options.containsKey("collation")) { + result.collation(getCollation(options, "collation")); + } + + return result; + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/mapper/RenameCollectionOptionsMapper.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/mapper/RenameCollectionOptionsMapper.java new file mode 100644 index 000000000..ade56d87b --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/mapper/RenameCollectionOptionsMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.mapper; + +import com.mongodb.client.model.RenameCollectionOptions; + +import java.util.Map; + +import static io.flamingock.template.mongodb.mapper.MapperUtil.getBoolean; + +public final class RenameCollectionOptionsMapper { + + private RenameCollectionOptionsMapper() {} + + public static RenameCollectionOptions map(Map options) { + RenameCollectionOptions result = new RenameCollectionOptions(); + + if (options.containsKey("dropTarget")) { + result.dropTarget(getBoolean(options, "dropTarget")); + } + + return result; + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoApplyPayload.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoApplyPayload.java new file mode 100644 index 000000000..2ce371274 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoApplyPayload.java @@ -0,0 +1,115 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Container class for MongoDB apply/rollback payload that supports both: + *
    + *
  • Single operation format (backward compatible): + *
    + *     apply:
    + *       type: createCollection
    + *       collection: users
    + *     
    + *
  • + *
  • Multiple operations format: + *
    + *     apply:
    + *       operations:
    + *         - type: createCollection
    + *           collection: users
    + *         - type: createIndex
    + *           collection: users
    + *           parameters:
    + *             keys: { name: 1 }
    + *     
    + *
  • + *
+ */ +public class MongoApplyPayload { + + private List operations; + + // For backward compatibility + private String type; + private String collection; + private Map parameters; + + /** + * Returns the list of operations to execute. + * Handles both formats: + *
    + *
  • Multiple: returns the operations list directly
  • + *
  • Single: wraps the single operation in a list
  • + *
+ * + * @return list of operations to execute, never null + */ + public List getOperations() { + if (operations != null && !operations.isEmpty()) { + return operations; + } + if (type != null) { + MongoOperation singleOp = new MongoOperation(); + singleOp.setType(type); + singleOp.setCollection(collection); + singleOp.setParameters(parameters != null ? parameters : new HashMap<>()); + return Collections.singletonList(singleOp); + } + return Collections.emptyList(); + } + + public void setOperations(List operations) { + this.operations = operations; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + @Override + public String toString() { + if (operations != null && !operations.isEmpty()) { + return "MongoApplyPayload{operations=" + operations + "}"; + } + return "MongoApplyPayload{type='" + type + "', collection='" + collection + "', parameters=" + parameters + "}"; + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperation.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperation.java index 4d931e157..115d09c55 100644 --- a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperation.java +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperation.java @@ -69,6 +69,40 @@ public Document getFilter() { return new Document((Map) parameters.get("filter")); } + public String getIndexName() { + Object value = parameters.get("indexName"); + return value != null ? (String) value : null; + } + + public String getTarget() { + return (String) parameters.get("target"); + } + + @SuppressWarnings("unchecked") + public Document getValidator() { + Object value = parameters.get("validator"); + return value != null ? new Document((Map) value) : null; + } + + public String getValidationLevel() { + return (String) parameters.get("validationLevel"); + } + + public String getValidationAction() { + return (String) parameters.get("validationAction"); + } + + public String getViewOn() { + return (String) parameters.get("viewOn"); + } + + @SuppressWarnings("unchecked") + public List getPipeline() { + List> rawPipeline = (List>) parameters.get("pipeline"); + return rawPipeline != null + ? rawPipeline.stream().map(Document::new).collect(Collectors.toList()) + : null; + } public MongoOperator getOperator(MongoDatabase db) { return MongoOperationType.getFromValue(getType()).getOperator(db, this); diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperationType.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperationType.java index f54c893e2..4cafa1b3c 100644 --- a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperationType.java +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/MongoOperationType.java @@ -18,8 +18,14 @@ import com.mongodb.client.MongoDatabase; import io.flamingock.template.mongodb.model.operator.CreateCollectionOperator; import io.flamingock.template.mongodb.model.operator.CreateIndexOperator; +import io.flamingock.template.mongodb.model.operator.CreateViewOperator; +import io.flamingock.template.mongodb.model.operator.DropCollectionOperator; +import io.flamingock.template.mongodb.model.operator.DropIndexOperator; +import io.flamingock.template.mongodb.model.operator.DropViewOperator; import io.flamingock.template.mongodb.model.operator.InsertOperator; +import io.flamingock.template.mongodb.model.operator.ModifyCollectionOperator; import io.flamingock.template.mongodb.model.operator.MongoOperator; +import io.flamingock.template.mongodb.model.operator.RenameCollectionOperator; import java.util.Arrays; import java.util.function.BiFunction; @@ -28,7 +34,13 @@ public enum MongoOperationType { CREATE_COLLECTION("createCollection", CreateCollectionOperator::new), CREATE_INDEX("createIndex", CreateIndexOperator::new), - INSERT("insert", InsertOperator::new); + INSERT("insert", InsertOperator::new), + DROP_COLLECTION("dropCollection", DropCollectionOperator::new), + DROP_INDEX("dropIndex", DropIndexOperator::new), + RENAME_COLLECTION("renameCollection", RenameCollectionOperator::new), + MODIFY_COLLECTION("modifyCollection", ModifyCollectionOperator::new), + CREATE_VIEW("createView", CreateViewOperator::new), + DROP_VIEW("dropView", DropViewOperator::new); private final String value; private final BiFunction createOperatorFunction; diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/CreateViewOperator.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/CreateViewOperator.java new file mode 100644 index 000000000..09633971d --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/CreateViewOperator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model.operator; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.CreateViewOptions; +import io.flamingock.template.mongodb.mapper.CreateViewOptionsMapper; +import io.flamingock.template.mongodb.model.MongoOperation; + +public class CreateViewOperator extends MongoOperator { + + public CreateViewOperator(MongoDatabase mongoDatabase, MongoOperation operation) { + super(mongoDatabase, operation, false); + } + + @Override + protected void applyInternal(ClientSession clientSession) { + CreateViewOptions options = CreateViewOptionsMapper.map(op.getOptions()); + mongoDatabase.createView(op.getCollection(), op.getViewOn(), op.getPipeline(), options); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropCollectionOperator.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropCollectionOperator.java new file mode 100644 index 000000000..e27f59363 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropCollectionOperator.java @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model.operator; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; + +public class DropCollectionOperator extends MongoOperator { + + public DropCollectionOperator(MongoDatabase mongoDatabase, MongoOperation operation) { + super(mongoDatabase, operation, false); + } + + @Override + protected void applyInternal(ClientSession clientSession) { + mongoDatabase.getCollection(op.getCollection()).drop(); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropIndexOperator.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropIndexOperator.java new file mode 100644 index 000000000..504de9889 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropIndexOperator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model.operator; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; + +public class DropIndexOperator extends MongoOperator { + + public DropIndexOperator(MongoDatabase mongoDatabase, MongoOperation operation) { + super(mongoDatabase, operation, false); + } + + @Override + protected void applyInternal(ClientSession clientSession) { + String indexName = op.getIndexName(); + if (indexName != null) { + mongoDatabase.getCollection(op.getCollection()).dropIndex(indexName); + } else { + mongoDatabase.getCollection(op.getCollection()).dropIndex(op.getKeys()); + } + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropViewOperator.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropViewOperator.java new file mode 100644 index 000000000..369bc9b2d --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/DropViewOperator.java @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model.operator; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; + +public class DropViewOperator extends MongoOperator { + + public DropViewOperator(MongoDatabase mongoDatabase, MongoOperation operation) { + super(mongoDatabase, operation, false); + } + + @Override + protected void applyInternal(ClientSession clientSession) { + mongoDatabase.getCollection(op.getCollection()).drop(); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/ModifyCollectionOperator.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/ModifyCollectionOperator.java new file mode 100644 index 000000000..fb0323ee2 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/ModifyCollectionOperator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model.operator; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import org.bson.Document; + +public class ModifyCollectionOperator extends MongoOperator { + + public ModifyCollectionOperator(MongoDatabase mongoDatabase, MongoOperation operation) { + super(mongoDatabase, operation, false); + } + + @Override + protected void applyInternal(ClientSession clientSession) { + Document command = new Document("collMod", op.getCollection()); + if (op.getValidator() != null) { + command.append("validator", op.getValidator()); + } + if (op.getValidationLevel() != null) { + command.append("validationLevel", op.getValidationLevel()); + } + if (op.getValidationAction() != null) { + command.append("validationAction", op.getValidationAction()); + } + mongoDatabase.runCommand(command); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/RenameCollectionOperator.java b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/RenameCollectionOperator.java new file mode 100644 index 000000000..af50238a6 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/main/java/io/flamingock/template/mongodb/model/operator/RenameCollectionOperator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.model.operator; + +import com.mongodb.MongoNamespace; +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.RenameCollectionOptions; +import io.flamingock.template.mongodb.mapper.RenameCollectionOptionsMapper; +import io.flamingock.template.mongodb.model.MongoOperation; + +public class RenameCollectionOperator extends MongoOperator { + + public RenameCollectionOperator(MongoDatabase mongoDatabase, MongoOperation operation) { + super(mongoDatabase, operation, false); + } + + @Override + protected void applyInternal(ClientSession clientSession) { + MongoNamespace target = new MongoNamespace(mongoDatabase.getName(), op.getTarget()); + RenameCollectionOptions options = RenameCollectionOptionsMapper.map(op.getOptions()); + mongoDatabase.getCollection(op.getCollection()).renameCollection(target, options); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/MongoChangeTemplateTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/MongoChangeTemplateTest.java index e46431f3c..62933785a 100644 --- a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/MongoChangeTemplateTest.java +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/MongoChangeTemplateTest.java @@ -40,6 +40,7 @@ import static io.flamingock.internal.util.constants.CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @EnableFlamingock(configFile = "flamingock/pipeline.yaml") @Testcontainers @@ -68,7 +69,8 @@ static void beforeAll() { @BeforeEach void setupEach() { mongoDatabase.getCollection(DEFAULT_AUDIT_STORE_NAME).drop(); - mongoDatabase.getCollection(DEFAULT_AUDIT_STORE_NAME).drop(); + mongoDatabase.getCollection("users").drop(); + mongoDatabase.getCollection("products").drop(); } @@ -88,7 +90,7 @@ void happyPath() { .find() .into(new ArrayList<>()); - assertEquals(4, auditLog.size()); + assertEquals(6, auditLog.size()); assertEquals("create-users-collection-with-index", auditLog.get(0).getString("changeId")); assertEquals(AuditEntry.Status.STARTED.name(), auditLog.get(0).getString("state")); @@ -100,6 +102,12 @@ void happyPath() { assertEquals("seed-users", auditLog.get(3).getString("changeId")); assertEquals(AuditEntry.Status.APPLIED.name(), auditLog.get(3).getString("state")); + assertEquals("multiple-operations-change", auditLog.get(4).getString("changeId")); + assertEquals(AuditEntry.Status.STARTED.name(), auditLog.get(4).getString("state")); + assertEquals("multiple-operations-change", auditLog.get(5).getString("changeId")); + assertEquals(AuditEntry.Status.APPLIED.name(), auditLog.get(5).getString("state")); + + // Verify for single operation List users = mongoDatabase.getCollection("users") .find() .into(new ArrayList<>()); @@ -112,6 +120,23 @@ void happyPath() { assertEquals("Backup", users.get(1).getString("name")); assertEquals("backup@company.com", users.get(1).getString("email")); assertEquals("readonly", users.get(1).getList("roles", String.class).get(0)); + + // Verify for multiple operation + List products = mongoDatabase.getCollection("products") + .find() + .into(new ArrayList<>()); + + assertEquals(3, products.size(), "Should have 3 products from multiple operations"); + assertEquals("Laptop", products.get(0).getString("name")); + assertEquals("Keyboard", products.get(1).getString("name")); + assertEquals("Mouse", products.get(2).getString("name")); + + List indexes = mongoDatabase.getCollection("products") + .listIndexes() + .into(new ArrayList<>()); + boolean categoryIndexExists = indexes.stream() + .anyMatch(idx -> "category_index".equals(idx.getString("name"))); + assertTrue(categoryIndexExists, "Category index should exist on products collection"); } diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/changes/_0003__multiple_operations.yaml b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/changes/_0003__multiple_operations.yaml new file mode 100644 index 000000000..e1627b83e --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/changes/_0003__multiple_operations.yaml @@ -0,0 +1,31 @@ +id: multiple-operations-change +transactional: false +template: MongoChangeTemplate +targetSystem: + id: "mongodb" +apply: + operations: + - type: createCollection + collection: products + + - type: insert + collection: products + parameters: + documents: + - name: "Laptop" + price: 999.99 + category: "Electronics" + - name: "Keyboard" + price: 79.99 + category: "Electronics" + - name: "Mouse" + price: 29.99 + category: "Electronics" + + - type: createIndex + collection: products + parameters: + keys: + category: 1 + options: + name: "category_index" diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateCollectionOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateCollectionOperatorTest.java new file mode 100644 index 000000000..4aa050b11 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateCollectionOperatorTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.CreateCollectionOperator; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class CreateCollectionOperatorTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "newTestCollection"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + } + + @Test + @DisplayName("WHEN createCollection operator is applied THEN collection is created") + void createCollectionTest() { + assertFalse(collectionExists(COLLECTION_NAME), "Collection should not exist before creation"); + + MongoOperation operation = new MongoOperation(); + operation.setType("createCollection"); + operation.setCollection(COLLECTION_NAME); + operation.setParameters(new HashMap<>()); + + CreateCollectionOperator operator = new CreateCollectionOperator(mongoDatabase, operation); + operator.apply(null); + + assertTrue(collectionExists(COLLECTION_NAME), "Collection should exist after creation"); + } + + private boolean collectionExists(String collectionName) { + return mongoDatabase.listCollectionNames() + .into(new ArrayList<>()) + .contains(collectionName); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateIndexOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateIndexOperatorTest.java new file mode 100644 index 000000000..a49ae801e --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateIndexOperatorTest.java @@ -0,0 +1,143 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.CreateIndexOperator; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class CreateIndexOperatorTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "indexTestCollection"; + private static final String INDEX_NAME = "email_unique_index"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + } + + @Test + @DisplayName("WHEN createIndex operator is applied THEN index is created") + void createIndexTest() { + mongoDatabase.createCollection(COLLECTION_NAME); + + MongoOperation operation = new MongoOperation(); + operation.setType("createIndex"); + operation.setCollection(COLLECTION_NAME); + + Map params = new HashMap<>(); + Map keys = new HashMap<>(); + keys.put("email", 1); + params.put("keys", keys); + operation.setParameters(params); + + CreateIndexOperator operator = new CreateIndexOperator(mongoDatabase, operation); + operator.apply(null); + + assertTrue(indexExistsByKeys("email"), "Index should exist after creation"); + } + + @Test + @DisplayName("WHEN createIndex operator is applied with options THEN index is created with options") + void createIndexWithOptionsTest() { + mongoDatabase.createCollection(COLLECTION_NAME); + + MongoOperation operation = new MongoOperation(); + operation.setType("createIndex"); + operation.setCollection(COLLECTION_NAME); + + Map params = new HashMap<>(); + Map keys = new HashMap<>(); + keys.put("email", 1); + params.put("keys", keys); + + Map options = new HashMap<>(); + options.put("unique", true); + options.put("name", INDEX_NAME); + params.put("options", options); + + operation.setParameters(params); + + CreateIndexOperator operator = new CreateIndexOperator(mongoDatabase, operation); + operator.apply(null); + + assertTrue(indexExists(INDEX_NAME), "Index should exist with specified name"); + assertTrue(isIndexUnique(INDEX_NAME), "Index should be unique"); + } + + private boolean indexExists(String indexName) { + List indexes = mongoDatabase.getCollection(COLLECTION_NAME) + .listIndexes() + .into(new ArrayList<>()); + return indexes.stream().anyMatch(idx -> indexName.equals(idx.getString("name"))); + } + + private boolean indexExistsByKeys(String keyField) { + List indexes = mongoDatabase.getCollection(COLLECTION_NAME) + .listIndexes() + .into(new ArrayList<>()); + return indexes.stream().anyMatch(idx -> { + Document key = idx.get("key", Document.class); + return key != null && key.containsKey(keyField); + }); + } + + private boolean isIndexUnique(String indexName) { + List indexes = mongoDatabase.getCollection(COLLECTION_NAME) + .listIndexes() + .into(new ArrayList<>()); + return indexes.stream() + .filter(idx -> indexName.equals(idx.getString("name"))) + .anyMatch(idx -> Boolean.TRUE.equals(idx.getBoolean("unique"))); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateViewOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateViewOperatorTest.java new file mode 100644 index 000000000..b8b3330c4 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/CreateViewOperatorTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.CreateViewOperator; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class CreateViewOperatorTest { + + private static final String DB_NAME = "test"; + private static final String SOURCE_COLLECTION = "viewSourceCollection"; + private static final String VIEW_NAME = "activeUsersView"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(SOURCE_COLLECTION).drop(); + mongoDatabase.getCollection(VIEW_NAME).drop(); + } + + @Test + @DisplayName("WHEN createView operator is applied THEN view is created and filters data") + void createViewTest() { + mongoDatabase.createCollection(SOURCE_COLLECTION); + mongoDatabase.getCollection(SOURCE_COLLECTION).insertMany(Arrays.asList( + new Document("name", "Active User").append("status", "active"), + new Document("name", "Inactive User").append("status", "inactive") + )); + + MongoOperation operation = new MongoOperation(); + operation.setType("createView"); + operation.setCollection(VIEW_NAME); + + Map params = new HashMap<>(); + params.put("viewOn", SOURCE_COLLECTION); + + List> pipeline = new ArrayList<>(); + Map matchStage = new HashMap<>(); + Map matchQuery = new HashMap<>(); + matchQuery.put("status", "active"); + matchStage.put("$match", matchQuery); + pipeline.add(matchStage); + params.put("pipeline", pipeline); + + operation.setParameters(params); + + CreateViewOperator operator = new CreateViewOperator(mongoDatabase, operation); + operator.apply(null); + + List collections = mongoDatabase.listCollectionNames().into(new ArrayList<>()); + assertTrue(collections.contains(VIEW_NAME), "View should exist"); + + List viewResults = mongoDatabase.getCollection(VIEW_NAME) + .find() + .into(new ArrayList<>()); + assertEquals(1, viewResults.size(), "View should return only active users"); + assertEquals("Active User", viewResults.get(0).getString("name")); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropCollectionOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropCollectionOperatorTest.java new file mode 100644 index 000000000..276f49e52 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropCollectionOperatorTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.DropCollectionOperator; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class DropCollectionOperatorTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "testCollection"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + } + + @Test + @DisplayName("WHEN dropCollection operator is applied THEN collection is dropped") + void dropCollectionTest() { + mongoDatabase.createCollection(COLLECTION_NAME); + assertTrue(collectionExists(COLLECTION_NAME), "Collection should exist before drop"); + + MongoOperation operation = new MongoOperation(); + operation.setType("dropCollection"); + operation.setCollection(COLLECTION_NAME); + operation.setParameters(new HashMap<>()); + + DropCollectionOperator operator = new DropCollectionOperator(mongoDatabase, operation); + operator.apply(null); + + assertFalse(collectionExists(COLLECTION_NAME), "Collection should have been dropped"); + } + + private boolean collectionExists(String collectionName) { + return mongoDatabase.listCollectionNames() + .into(new ArrayList<>()) + .contains(collectionName); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropIndexOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropIndexOperatorTest.java new file mode 100644 index 000000000..fbb4a3342 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropIndexOperatorTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.DropIndexOperator; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class DropIndexOperatorTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "indexTestCollection"; + private static final String INDEX_NAME = "email_index"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + } + + @Test + @DisplayName("WHEN dropIndex operator is applied with index name THEN index is dropped") + void dropIndexByNameTest() { + mongoDatabase.createCollection(COLLECTION_NAME); + mongoDatabase.getCollection(COLLECTION_NAME).createIndex(new Document("email", 1), + new com.mongodb.client.model.IndexOptions().name(INDEX_NAME)); + assertTrue(indexExists(INDEX_NAME), "Index should exist before drop"); + + MongoOperation operation = new MongoOperation(); + operation.setType("dropIndex"); + operation.setCollection(COLLECTION_NAME); + Map params = new HashMap<>(); + params.put("indexName", INDEX_NAME); + operation.setParameters(params); + + DropIndexOperator operator = new DropIndexOperator(mongoDatabase, operation); + operator.apply(null); + + assertFalse(indexExists(INDEX_NAME), "Index should have been dropped"); + } + + @Test + @DisplayName("WHEN dropIndex operator is applied with keys THEN index is dropped") + void dropIndexByKeysTest() { + mongoDatabase.createCollection(COLLECTION_NAME); + mongoDatabase.getCollection(COLLECTION_NAME).createIndex(new Document("name", 1)); + assertTrue(indexExistsByKeys("name"), "Index should exist before drop"); + + MongoOperation operation = new MongoOperation(); + operation.setType("dropIndex"); + operation.setCollection(COLLECTION_NAME); + Map params = new HashMap<>(); + Map keys = new HashMap<>(); + keys.put("name", 1); + params.put("keys", keys); + operation.setParameters(params); + + DropIndexOperator operator = new DropIndexOperator(mongoDatabase, operation); + operator.apply(null); + + assertFalse(indexExistsByKeys("name"), "Index should have been dropped"); + } + + private boolean indexExists(String indexName) { + List indexes = mongoDatabase.getCollection(COLLECTION_NAME) + .listIndexes() + .into(new ArrayList<>()); + return indexes.stream().anyMatch(idx -> indexName.equals(idx.getString("name"))); + } + + private boolean indexExistsByKeys(String keyField) { + List indexes = mongoDatabase.getCollection(COLLECTION_NAME) + .listIndexes() + .into(new ArrayList<>()); + return indexes.stream().anyMatch(idx -> { + Document key = idx.get("key", Document.class); + return key != null && key.containsKey(keyField); + }); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropViewOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropViewOperatorTest.java new file mode 100644 index 000000000..dfd2b1e4a --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/DropViewOperatorTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.DropViewOperator; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class DropViewOperatorTest { + + private static final String DB_NAME = "test"; + private static final String SOURCE_COLLECTION = "dropViewSourceCollection"; + private static final String VIEW_NAME = "viewToDrop"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(SOURCE_COLLECTION).drop(); + mongoDatabase.getCollection(VIEW_NAME).drop(); + } + + @Test + @DisplayName("WHEN dropView operator is applied THEN view is dropped") + void dropViewTest() { + mongoDatabase.createCollection(SOURCE_COLLECTION); + mongoDatabase.createView(VIEW_NAME, SOURCE_COLLECTION, Collections.emptyList()); + assertTrue(collectionExists(VIEW_NAME), "View should exist before drop"); + + MongoOperation operation = new MongoOperation(); + operation.setType("dropView"); + operation.setCollection(VIEW_NAME); + operation.setParameters(new HashMap<>()); + + DropViewOperator operator = new DropViewOperator(mongoDatabase, operation); + operator.apply(null); + + assertFalse(collectionExists(VIEW_NAME), "View should have been dropped"); + } + + private boolean collectionExists(String collectionName) { + List collections = mongoDatabase.listCollectionNames().into(new ArrayList<>()); + return collections.contains(collectionName); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/InsertOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/InsertOperatorTest.java new file mode 100644 index 000000000..87334c1a6 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/InsertOperatorTest.java @@ -0,0 +1,174 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.InsertOperator; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Testcontainers +class InsertOperatorTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "insertTestCollection"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + mongoDatabase.createCollection(COLLECTION_NAME); + } + + @Test + @DisplayName("WHEN insert operator is applied with one document THEN document is inserted") + void insertOneDocumentTest() { + // Verify collection is empty before + assertEquals(0, getDocumentCount(), "Collection should be empty before insert"); + + // Create the operation + MongoOperation operation = new MongoOperation(); + operation.setType("insert"); + operation.setCollection(COLLECTION_NAME); + + Map params = new HashMap<>(); + List> documents = new ArrayList<>(); + Map doc = new HashMap<>(); + doc.put("name", "John Doe"); + doc.put("email", "john@example.com"); + documents.add(doc); + params.put("documents", documents); + operation.setParameters(params); + + // Execute the operator + InsertOperator operator = new InsertOperator(mongoDatabase, operation); + operator.apply(null); + + // Verify + assertEquals(1, getDocumentCount(), "Collection should have one document"); + Document inserted = mongoDatabase.getCollection(COLLECTION_NAME).find().first(); + assertEquals("John Doe", inserted.getString("name")); + assertEquals("john@example.com", inserted.getString("email")); + } + + @Test + @DisplayName("WHEN insert operator is applied with multiple documents THEN documents are inserted") + void insertManyDocumentsTest() { + assertEquals(0, getDocumentCount(), "Collection should be empty before insert"); + + MongoOperation operation = new MongoOperation(); + operation.setType("insert"); + operation.setCollection(COLLECTION_NAME); + + Map params = new HashMap<>(); + List> documents = new ArrayList<>(); + + Map doc1 = new HashMap<>(); + doc1.put("name", "Alice"); + doc1.put("email", "alice@example.com"); + documents.add(doc1); + + Map doc2 = new HashMap<>(); + doc2.put("name", "Bob"); + doc2.put("email", "bob@example.com"); + documents.add(doc2); + + Map doc3 = new HashMap<>(); + doc3.put("name", "Charlie"); + doc3.put("email", "charlie@example.com"); + documents.add(doc3); + + params.put("documents", documents); + operation.setParameters(params); + + InsertOperator operator = new InsertOperator(mongoDatabase, operation); + operator.apply(null); + + assertEquals(3, getDocumentCount(), "Collection should have three documents"); + + List insertedDocs = mongoDatabase.getCollection(COLLECTION_NAME) + .find() + .into(new ArrayList<>()); + + List names = Arrays.asList("Alice", "Bob", "Charlie"); + for (Document doc : insertedDocs) { + assertTrue(names.contains(doc.getString("name")), "Document name should be in expected list"); + } + } + + @Test + @DisplayName("WHEN insert operator is applied with empty documents THEN nothing is inserted") + void insertEmptyDocumentsTest() { + assertEquals(0, getDocumentCount(), "Collection should be empty before insert"); + + MongoOperation operation = new MongoOperation(); + operation.setType("insert"); + operation.setCollection(COLLECTION_NAME); + + Map params = new HashMap<>(); + params.put("documents", new ArrayList<>()); + operation.setParameters(params); + + InsertOperator operator = new InsertOperator(mongoDatabase, operation); + operator.apply(null); + + assertEquals(0, getDocumentCount(), "Collection should still be empty"); + } + + private long getDocumentCount() { + return mongoDatabase.getCollection(COLLECTION_NAME).countDocuments(); + } + + private static void assertTrue(boolean condition, String message) { + if (!condition) { + throw new AssertionError(message); + } + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/ModifyCollectionOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/ModifyCollectionOperatorTest.java new file mode 100644 index 000000000..0a4536941 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/ModifyCollectionOperatorTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.ModifyCollectionOperator; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Testcontainers +class ModifyCollectionOperatorTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "modifyTestCollection"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + } + + @Test + @DisplayName("WHEN modifyCollection operator is applied THEN collection validation is set") + void modifyCollectionTest() { + mongoDatabase.createCollection(COLLECTION_NAME); + + MongoOperation operation = new MongoOperation(); + operation.setType("modifyCollection"); + operation.setCollection(COLLECTION_NAME); + + Map params = new HashMap<>(); + Map validator = new HashMap<>(); + Map jsonSchema = new HashMap<>(); + jsonSchema.put("bsonType", "object"); + validator.put("$jsonSchema", jsonSchema); + params.put("validator", validator); + params.put("validationLevel", "moderate"); + params.put("validationAction", "warn"); + operation.setParameters(params); + + ModifyCollectionOperator operator = new ModifyCollectionOperator(mongoDatabase, operation); + operator.apply(null); + + Document collectionInfo = mongoDatabase.listCollections() + .filter(new Document("name", COLLECTION_NAME)) + .first(); + assertNotNull(collectionInfo, "Collection should exist"); + Document options = collectionInfo.get("options", Document.class); + assertNotNull(options, "Collection should have options"); + assertNotNull(options.get("validator"), "Collection should have validator"); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/MultipleOperationsTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/MultipleOperationsTest.java new file mode 100644 index 000000000..b37a733a2 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/MultipleOperationsTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoApplyPayload; +import io.flamingock.template.mongodb.model.MongoOperation; +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class MultipleOperationsTest { + + private static final String DB_NAME = "test"; + private static final String COLLECTION_NAME = "multiOpsCollection"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(COLLECTION_NAME).drop(); + } + + @Test + @DisplayName("WHEN multiple operations are executed THEN all operations complete successfully") + void multipleOperationsTest() { + MongoApplyPayload payload = new MongoApplyPayload(); + List ops = new ArrayList<>(); + + // Op 1: Create collection + MongoOperation op1 = new MongoOperation(); + op1.setType("createCollection"); + op1.setCollection(COLLECTION_NAME); + op1.setParameters(new HashMap<>()); + ops.add(op1); + + // Op 2: Insert documents + MongoOperation op2 = new MongoOperation(); + op2.setType("insert"); + op2.setCollection(COLLECTION_NAME); + Map insertParams = new HashMap<>(); + List> documents = new ArrayList<>(); + Map doc1 = new HashMap<>(); + doc1.put("name", "User1"); + doc1.put("email", "user1@example.com"); + documents.add(doc1); + Map doc2 = new HashMap<>(); + doc2.put("name", "User2"); + doc2.put("email", "user2@example.com"); + documents.add(doc2); + insertParams.put("documents", documents); + op2.setParameters(insertParams); + ops.add(op2); + + // Op 3: Create index + MongoOperation op3 = new MongoOperation(); + op3.setType("createIndex"); + op3.setCollection(COLLECTION_NAME); + Map indexParams = new HashMap<>(); + Map keys = new HashMap<>(); + keys.put("email", 1); + indexParams.put("keys", keys); + Map options = new HashMap<>(); + options.put("unique", true); + indexParams.put("options", options); + op3.setParameters(indexParams); + ops.add(op3); + + payload.setOperations(ops); + + for (MongoOperation op : payload.getOperations()) { + op.getOperator(mongoDatabase).apply(null); + } + + assertTrue(collectionExists(COLLECTION_NAME), "Collection should exist"); + assertEquals(2, getDocumentCount(), "Should have 2 documents"); + assertTrue(indexExists("email"), "Index on email should exist"); + } + + @Test + @DisplayName("WHEN single operation format is used THEN backward compatibility works") + void backwardCompatibilitySingleOperationTest() { + MongoApplyPayload payload = new MongoApplyPayload(); + payload.setType("createCollection"); + payload.setCollection(COLLECTION_NAME); + payload.setParameters(new HashMap<>()); + + List ops = payload.getOperations(); + assertEquals(1, ops.size(), "Should have exactly one operation"); + assertEquals("createCollection", ops.get(0).getType()); + assertEquals(COLLECTION_NAME, ops.get(0).getCollection()); + + for (MongoOperation op : ops) { + op.getOperator(mongoDatabase).apply(null); + } + + assertTrue(collectionExists(COLLECTION_NAME), "Collection should exist"); + } + + @Test + @DisplayName("WHEN payload has no operations THEN empty list is returned") + void emptyPayloadTest() { + MongoApplyPayload payload = new MongoApplyPayload(); + + List ops = payload.getOperations(); + assertTrue(ops.isEmpty(), "Should return empty list"); + } + + @Test + @DisplayName("WHEN operations list is set but type is also set THEN operations list takes precedence") + void operationsListTakesPrecedenceTest() { + MongoApplyPayload payload = new MongoApplyPayload(); + + // Backward compatibility + payload.setType("dropCollection"); + payload.setCollection("someOtherCollection"); + + List ops = new ArrayList<>(); + MongoOperation op = new MongoOperation(); + op.setType("createCollection"); + op.setCollection(COLLECTION_NAME); + op.setParameters(new HashMap<>()); + ops.add(op); + payload.setOperations(ops); + + List result = payload.getOperations(); + assertEquals(1, result.size()); + assertEquals("createCollection", result.get(0).getType()); + assertEquals(COLLECTION_NAME, result.get(0).getCollection()); + } + + private boolean collectionExists(String collectionName) { + return mongoDatabase.listCollectionNames() + .into(new ArrayList<>()) + .contains(collectionName); + } + + private long getDocumentCount() { + return mongoDatabase.getCollection(COLLECTION_NAME).countDocuments(); + } + + private boolean indexExists(String keyField) { + List indexes = mongoDatabase.getCollection(COLLECTION_NAME) + .listIndexes() + .into(new ArrayList<>()); + return indexes.stream().anyMatch(idx -> { + Document key = idx.get("key", Document.class); + return key != null && key.containsKey(keyField); + }); + } +} diff --git a/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/RenameCollectionOperatorTest.java b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/RenameCollectionOperatorTest.java new file mode 100644 index 000000000..e9c576b54 --- /dev/null +++ b/templates/flamingock-mongodb-sync-template/src/test/java/io/flamingock/template/mongodb/operations/RenameCollectionOperatorTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.template.mongodb.operations; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import io.flamingock.template.mongodb.model.MongoOperation; +import io.flamingock.template.mongodb.model.operator.RenameCollectionOperator; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +class RenameCollectionOperatorTest { + + private static final String DB_NAME = "test"; + private static final String ORIGINAL_NAME = "originalCollection"; + private static final String RENAMED_NAME = "renamedCollection"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + @Container + public static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6")); + + @BeforeAll + static void beforeAll() { + mongoClient = MongoClients.create(MongoClientSettings + .builder() + .applyConnectionString(new ConnectionString(mongoDBContainer.getConnectionString())) + .build()); + mongoDatabase = mongoClient.getDatabase(DB_NAME); + } + + @BeforeEach + void setupEach() { + mongoDatabase.getCollection(ORIGINAL_NAME).drop(); + mongoDatabase.getCollection(RENAMED_NAME).drop(); + } + + @Test + @DisplayName("WHEN renameCollection operator is applied THEN collection is renamed") + void renameCollectionTest() { + mongoDatabase.createCollection(ORIGINAL_NAME); + assertTrue(collectionExists(ORIGINAL_NAME), "Original collection should exist before rename"); + + MongoOperation operation = new MongoOperation(); + operation.setType("renameCollection"); + operation.setCollection(ORIGINAL_NAME); + Map params = new HashMap<>(); + params.put("target", RENAMED_NAME); + operation.setParameters(params); + + RenameCollectionOperator operator = new RenameCollectionOperator(mongoDatabase, operation); + operator.apply(null); + + assertFalse(collectionExists(ORIGINAL_NAME), "Original collection should not exist after rename"); + assertTrue(collectionExists(RENAMED_NAME), "Renamed collection should exist"); + } + + private boolean collectionExists(String collectionName) { + List collections = mongoDatabase.listCollectionNames().into(new ArrayList<>()); + return collections.contains(collectionName); + } +}