diff --git a/mongo/src/test/java/org/apache/pulsar/io/mongodb/MongoSourceContainerTest.java b/mongo/src/test/java/org/apache/pulsar/io/mongodb/MongoSourceContainerTest.java new file mode 100644 index 0000000000..7da9965bc8 --- /dev/null +++ b/mongo/src/test/java/org/apache/pulsar/io/mongodb/MongoSourceContainerTest.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.pulsar.io.mongodb; + +import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import lombok.extern.slf4j.Slf4j; +import org.apache.pulsar.functions.api.Record; +import org.apache.pulsar.io.core.SourceContext; +import org.bson.Document; +import org.testcontainers.containers.MongoDBContainer; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Integration test for {@link MongoSource} that exercises the full path from a real MongoDB change + * stream into the source's record queue. A {@link MongoDBContainer} provides a single-node replica + * set (which change streams require) and the source is driven end-to-end against it. + * + *
The test seeds documents before opening the source and configures {@code
+ * syncType=FULL_SYNC}, then asserts that exactly those seeded documents are replayed through the
+ * change stream — so a pass proves the FULL_SYNC replay path, not merely that some record arrived.
+ */
+@Slf4j
+public class MongoSourceContainerTest {
+
+ private static final String DB = "testdb";
+ private static final String COLLECTION = "messages";
+
+ // Change streams surface within seconds, but give each read a generous deadline so a
+ // non-delivering source fails promptly rather than hanging the suite.
+ private static final int READ_TIMEOUT_SECONDS = 60;
+ private static final int EXPECTED_RECORDS = 3;
+
+ private MongoDBContainer mongoContainer;
+ private com.mongodb.client.MongoClient verifyClient;
+ private MongoSource source;
+ private ExecutorService readerExecutor;
+
+ @BeforeMethod
+ public void setUp() {
+ mongoContainer = new MongoDBContainer("mongo:6.0")
+ .withStartupTimeout(Duration.ofMinutes(3));
+ mongoContainer.start();
+
+ // Sync driver used only to write the test documents.
+ verifyClient = com.mongodb.client.MongoClients.create(mongoContainer.getConnectionString());
+
+ readerExecutor = Executors.newSingleThreadExecutor(r -> {
+ Thread t = new Thread(r, "mongo-it-reader");
+ t.setDaemon(true);
+ return t;
+ });
+
+ source = new MongoSource();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws Exception {
+ if (readerExecutor != null) {
+ // A reader may still be blocked in read(), which never returns null.
+ readerExecutor.shutdownNow();
+ }
+ if (source != null) {
+ try {
+ source.close();
+ } catch (Exception e) {
+ log.warn("Failed to close source", e);
+ }
+ }
+ if (verifyClient != null) {
+ verifyClient.close();
+ }
+ if (mongoContainer != null) {
+ mongoContainer.stop();
+ }
+ }
+
+ @Test(timeOut = 300_000)
+ public void testReadFromChangeStream() throws Exception {
+ // Seed the documents before the source starts. FULL_SYNC (startAtOperationTime=0) replays
+ // them from the start of the stream, so these exact documents must be delivered.
+ Set