This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kafka-connector.git
The following commit(s) were added to refs/heads/main by this push:
new 253029081f Fix #743: add integration tests for the FTP, FTPS and SFTP
connectors (#1868)
253029081f is described below
commit 253029081f0be6a8bdb225f02563722b565ef3a4
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Nov 5 13:09:04 2026 +0100
Fix #743: add integration tests for the FTP, FTPS and SFTP connectors
(#1868)
Add the tests/itests-ftp module with a source and a sink test for each of
the ftp, ftps and sftp connectors (#743, #744, #745), using the embedded
servers of camel-test-infra-ftp.
- Source tests drop a file into the polled directory and check the record
value and its CamelHeader.file header.
- Sink tests send records with a CamelHeader.file header and check the
files on the server.
- FTPS runs in explicit mode, the only one the ftps Kamelets support, and
validates the server certificate through camel.ssl.* with
camel.component.ftps.useGlobalSslContextParameters=true.
- SFTP checks the server host key, with strictHostKeyChecking=yes and
camel.component.sftp.knownHostsFile.
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
tests/itests-ftp/pom.xml | 60 ++++++++++
.../kafkaconnector/ftp/common/FtpTestServers.java | 127 +++++++++++++++++++++
.../ftp/sink/CamelFtpPropertyFactory.java | 84 ++++++++++++++
.../ftp/sink/CamelSinkFtpITCase.java | 50 ++++++++
.../ftp/sink/CamelSinkFtpTestSupport.java | 118 +++++++++++++++++++
.../ftp/sink/CamelSinkFtpsITCase.java | 51 +++++++++
.../ftp/sink/CamelSinkSftpITCase.java | 53 +++++++++
.../ftp/source/CamelFtpPropertyFactory.java | 89 +++++++++++++++
.../ftp/source/CamelSourceFtpITCase.java | 50 ++++++++
.../ftp/source/CamelSourceFtpTestSupport.java | 101 ++++++++++++++++
.../ftp/source/CamelSourceFtpsITCase.java | 51 +++++++++
.../ftp/source/CamelSourceSftpITCase.java | 53 +++++++++
tests/pom.xml | 1 +
13 files changed, 888 insertions(+)
diff --git a/tests/itests-ftp/pom.xml b/tests/itests-ftp/pom.xml
new file mode 100644
index 0000000000..faa5a49f14
--- /dev/null
+++ b/tests/itests-ftp/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.camel.kafkaconnector</groupId>
+ <artifactId>itests-parent</artifactId>
+ <version>4.22.1-SNAPSHOT</version>
+ <relativePath>../itests-parent/pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>itests-ftp</artifactId>
+ <name>Camel-Kafka-Connector :: Tests :: FTP</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.kafkaconnector</groupId>
+ <artifactId>itests-common</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-ftp</artifactId>
+ </dependency>
+
+ <!-- Embedded FTP, FTPS and SFTP servers -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-ftp</artifactId>
+ <version>${camel.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <version>${awaitility-version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/common/FtpTestServers.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/common/FtpTestServers.java
new file mode 100644
index 0000000000..18d3b58e83
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/common/FtpTestServers.java
@@ -0,0 +1,127 @@
+/*
+ * 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.camel.kafkaconnector.ftp.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Properties;
+
+import org.apache.camel.test.infra.ftp.services.FtpInfraService;
+import org.apache.camel.test.infra.ftp.services.embedded.EmbeddedConfiguration;
+import org.apache.camel.test.infra.ftp.services.embedded.FtpsEmbeddedService;
+
+/**
+ * Creates the embedded FTPS server used by the tests and provides the
connector configuration that verifies the
+ * identity of the FTPS and SFTP servers.
+ */
+public final class FtpTestServers {
+ // The key store bundled with camel-test-infra-ftp, holding the
self-signed certificate of the FTPS server. It is
+ // used from there rather than copied into this module, so that it follows
the Camel upgrades.
+ private static final String BUNDLED_KEY_STORE = "/server.jks";
+ private static final String KEY_STORE_PASSWORD = "password";
+ private static final Path KEY_STORE = Paths.get("target", "ftps",
"server.jks").toAbsolutePath();
+ private static final Path KNOWN_HOSTS = Paths.get("target", "sftp",
"known_hosts").toAbsolutePath();
+
+ private FtpTestServers() {
+
+ }
+
+ /**
+ * Creates an FTPS server running in explicit mode: the ftps Kamelets have
no option to use the implicit one.
+ *
+ * @return the FTPS service, to be registered as a JUnit extension
+ */
+ public static FtpsEmbeddedService createFtpsService() {
+ copyBundledKeyStore();
+
+ return new BundledKeyStoreFtpsService();
+ }
+
+ /**
+ * The connector configuration that makes the ftps component trust the
certificate of the FTPS server, through the
+ * global SSL configuration of the Camel context.
+ *
+ * @return the connector properties
+ */
+ public static Properties ftpsTrustProperties() {
+ final String keyStore = "file:" + KEY_STORE;
+
+ Properties properties = new Properties();
+ properties.put("camel.ssl.enabled", "true");
+ // Camel main only creates the global SSL context parameters when a
key store is configured
+ properties.put("camel.ssl.keyStore", keyStore);
+ properties.put("camel.ssl.keystorePassword", KEY_STORE_PASSWORD);
+ properties.put("camel.ssl.trustStore", keyStore);
+ properties.put("camel.ssl.trustStorePassword", KEY_STORE_PASSWORD);
+ properties.put("camel.component.ftps.useGlobalSslContextParameters",
"true");
+
+ return properties;
+ }
+
+ /**
+ * The connector configuration that makes the sftp component verify the
host key of the SFTP server against a
+ * known_hosts file. The sftp Kamelets have no option for it, so it is set
on the component.
+ *
+ * @param service the running SFTP service
+ * @return the connector properties
+ */
+ public static Properties sftpKnownHostsProperties(FtpInfraService service)
{
+ try {
+ Files.createDirectories(KNOWN_HOSTS.getParent());
+ Files.writeString(KNOWN_HOSTS, service.knownHostsEntry() +
System.lineSeparator());
+ } catch (IOException e) {
+ throw new UncheckedIOException("Unable to write the known hosts
file " + KNOWN_HOSTS, e);
+ }
+
+ Properties properties = new Properties();
+ properties.put("camel.component.sftp.knownHostsFile",
KNOWN_HOSTS.toString());
+
+ return properties;
+ }
+
+ private static void copyBundledKeyStore() {
+ try (InputStream keyStore =
FtpsEmbeddedService.class.getResourceAsStream(BUNDLED_KEY_STORE)) {
+ if (keyStore == null) {
+ throw new IllegalStateException("The key store bundled with
camel-test-infra-ftp was not found");
+ }
+
+ Files.createDirectories(KEY_STORE.getParent());
+ Files.copy(keyStore, KEY_STORE,
StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Unable to copy the FTPS key store
to " + KEY_STORE, e);
+ }
+ }
+
+ /**
+ * FtpsEmbeddedService only looks for its key store at
./src/test/resources/server.jks, so it is pointed to the copy
+ * of the bundled one instead.
+ */
+ private static final class BundledKeyStoreFtpsService extends
FtpsEmbeddedService {
+ BundledKeyStoreFtpsService() {
+ // explicit mode, TLSv1.2 (as in the Camel FTPS tests using this
server) and no client authentication
+ super(new EmbeddedConfiguration.SecurityConfiguration(false,
"TLSv1.2", false));
+
+ embeddedConfigurationTemplate.withKeyStore(KEY_STORE.toString());
+ }
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelFtpPropertyFactory.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelFtpPropertyFactory.java
new file mode 100644
index 0000000000..514ce583e1
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelFtpPropertyFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.camel.kafkaconnector.ftp.sink;
+
+import org.apache.camel.kafkaconnector.common.SinkConnectorPropertyFactory;
+
+/**
+ * Property factory for the ftp-sink, ftps-sink and sftp-sink connectors,
whose Kamelets share the same options.
+ */
+final class CamelFtpPropertyFactory extends
SinkConnectorPropertyFactory<CamelFtpPropertyFactory> {
+ private final String kamelet;
+
+ private CamelFtpPropertyFactory(String kamelet) {
+ this.kamelet = kamelet;
+ }
+
+ public CamelFtpPropertyFactory withConnectionHost(String connectionHost) {
+ return withKameletProperty("connectionHost", connectionHost);
+ }
+
+ public CamelFtpPropertyFactory withConnectionPort(int connectionPort) {
+ return withKameletProperty("connectionPort",
String.valueOf(connectionPort));
+ }
+
+ public CamelFtpPropertyFactory withUsername(String username) {
+ return withKameletProperty("username", username);
+ }
+
+ public CamelFtpPropertyFactory withPassword(String password) {
+ return withKameletProperty("password", password);
+ }
+
+ public CamelFtpPropertyFactory withDirectoryName(String directoryName) {
+ return withKameletProperty("directoryName", directoryName);
+ }
+
+ public CamelFtpPropertyFactory withStrictHostKeyChecking(String
strictHostKeyChecking) {
+ return withKameletProperty("strictHostKeyChecking",
strictHostKeyChecking);
+ }
+
+ private CamelFtpPropertyFactory withKameletProperty(String name, String
value) {
+ return setProperty("camel.kamelet." + kamelet + "." + name, value);
+ }
+
+ public static CamelFtpPropertyFactory ftp() {
+ return basic("ftp-sink", "CamelFtpSinkConnector",
+
"org.apache.camel.kafkaconnector.ftpsink.CamelFtpsinkSinkConnector");
+ }
+
+ public static CamelFtpPropertyFactory ftps() {
+ return basic("ftps-sink", "CamelFtpsSinkConnector",
+
"org.apache.camel.kafkaconnector.ftpssink.CamelFtpssinkSinkConnector");
+ }
+
+ public static CamelFtpPropertyFactory sftp() {
+ return basic("sftp-sink", "CamelSftpSinkConnector",
+
"org.apache.camel.kafkaconnector.sftpsink.CamelSftpsinkSinkConnector");
+ }
+
+ private static CamelFtpPropertyFactory basic(String kamelet, String name,
String connectorClass) {
+ return new CamelFtpPropertyFactory(kamelet)
+ .withName(name)
+ .withTasksMax(1)
+ .withConnectorClass(connectorClass)
+
.withKeyConverterClass("org.apache.kafka.connect.storage.StringConverter")
+
.withValueConverterClass("org.apache.kafka.connect.storage.StringConverter")
+ .setProperty("camel.component.kamelet.location", "kamelets");
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpITCase.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpITCase.java
new file mode 100644
index 0000000000..f4849922df
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpITCase.java
@@ -0,0 +1,50 @@
+/*
+ * 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.camel.kafkaconnector.ftp.sink;
+
+import java.nio.file.Path;
+
+import org.apache.camel.test.infra.ftp.services.FtpService;
+import org.apache.camel.test.infra.ftp.services.FtpServiceFactory;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CamelSinkFtpITCase extends CamelSinkFtpTestSupport {
+ @RegisterExtension
+ public static FtpService service =
FtpServiceFactory.createEmbeddedService();
+
+ @Override
+ protected String[] getConnectorsInTest() {
+ return new String[] {"camel-ftp-sink-kafka-connector"};
+ }
+
+ @Override
+ protected Path localDirectory() {
+ // the FTP user home is the root directory of the server
+ return service.getFtpRootDir().resolve(DIRECTORY);
+ }
+
+ @Override
+ protected CamelFtpPropertyFactory connectorPropertyFactory() {
+ return CamelFtpPropertyFactory.ftp()
+ .withConnectionHost(service.host())
+ .withConnectionPort(service.port())
+ .withUsername(service.username())
+ .withPassword(service.password())
+ .withDirectoryName(DIRECTORY);
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpTestSupport.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpTestSupport.java
new file mode 100644
index 0000000000..7296fc38d1
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpTestSupport.java
@@ -0,0 +1,118 @@
+/*
+ * 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.camel.kafkaconnector.ftp.sink;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.camel.kafkaconnector.CamelSinkTask;
+import org.apache.camel.kafkaconnector.common.test.CamelSinkTestSupport;
+import org.apache.camel.kafkaconnector.common.test.StringMessageProducer;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.Timeout;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Sends records to the sink connector and checks the files written on the
server. The name of each file comes from
+ * the CamelHeader.file Kafka header of the record, which the connector maps
to the file header of the Kamelet.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public abstract class CamelSinkFtpTestSupport extends CamelSinkTestSupport {
+ protected static final String DIRECTORY = "sink";
+
+ private static final int EXPECT = 3;
+
+ private String topic;
+
+ private static class FileNameHeaderProducer extends StringMessageProducer {
+ FileNameHeaderProducer(String bootstrapServer, String topicName, int
count) {
+ super(bootstrapServer, topicName, count);
+ }
+
+ @Override
+ public Map<String, String> messageHeaders(String text, int current) {
+ return Collections.singletonMap(CamelSinkTask.HEADER_CAMEL_PREFIX
+ "file", fileName(current));
+ }
+
+ @Override
+ public String testMessageContent(int current) {
+ return fileContent(current);
+ }
+ }
+
+ /**
+ * @return the local path of the directory the connector writes the files
to
+ */
+ protected abstract Path localDirectory();
+
+ /**
+ * @return the connector configuration for the server under test, without
the Kafka topic
+ */
+ protected abstract CamelFtpPropertyFactory connectorPropertyFactory();
+
+ @BeforeEach
+ public void setUp() {
+ topic = getTopicForTest(this);
+ }
+
+ @Override
+ protected void consumeMessages(CountDownLatch latch) {
+ // nothing to consume from Kafka: verifyMessages checks the files
written on the server
+ latch.countDown();
+ }
+
+ @Override
+ protected void verifyMessages(CountDownLatch latch) {
+ final Path directory = localDirectory();
+
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(60))
+ .untilAsserted(() -> {
+ for (int i = 0; i < EXPECT; i++) {
+ Path file = directory.resolve(fileName(i));
+
+ assertTrue(Files.exists(file), "The file " + file + "
was not written");
+ assertEquals(fileContent(i), Files.readString(file),
"Unexpected content in " + file);
+ }
+ });
+ }
+
+ @Test
+ @Timeout(180)
+ public void testStoreFiles() throws Exception {
+ runTest(connectorPropertyFactory().withTopics(topic),
+ new
FileNameHeaderProducer(getKafkaService().getBootstrapServers(), topic, EXPECT));
+ }
+
+ private static String fileName(int current) {
+ return "sink-test-" + current + ".txt";
+ }
+
+ private static String fileContent(int current) {
+ return "Camel Kafka Connector sink test " + current;
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpsITCase.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpsITCase.java
new file mode 100644
index 0000000000..437a9eac5c
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkFtpsITCase.java
@@ -0,0 +1,51 @@
+/*
+ * 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.camel.kafkaconnector.ftp.sink;
+
+import java.nio.file.Path;
+
+import org.apache.camel.kafkaconnector.ftp.common.FtpTestServers;
+import org.apache.camel.test.infra.ftp.services.FtpService;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CamelSinkFtpsITCase extends CamelSinkFtpTestSupport {
+ @RegisterExtension
+ public static FtpService service = FtpTestServers.createFtpsService();
+
+ @Override
+ protected String[] getConnectorsInTest() {
+ return new String[] {"camel-ftps-sink-kafka-connector"};
+ }
+
+ @Override
+ protected Path localDirectory() {
+ // the FTPS user home is the root directory of the server
+ return service.getFtpRootDir().resolve(DIRECTORY);
+ }
+
+ @Override
+ protected CamelFtpPropertyFactory connectorPropertyFactory() {
+ return CamelFtpPropertyFactory.ftps()
+ .withConnectionHost(service.host())
+ .withConnectionPort(service.port())
+ .withUsername(service.username())
+ .withPassword(service.password())
+ .withDirectoryName(DIRECTORY)
+ .merge(FtpTestServers.ftpsTrustProperties());
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkSftpITCase.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkSftpITCase.java
new file mode 100644
index 0000000000..3590ebe6e3
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/sink/CamelSinkSftpITCase.java
@@ -0,0 +1,53 @@
+/*
+ * 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.camel.kafkaconnector.ftp.sink;
+
+import java.nio.file.Path;
+
+import org.apache.camel.kafkaconnector.ftp.common.FtpTestServers;
+import org.apache.camel.test.infra.ftp.services.FtpService;
+import org.apache.camel.test.infra.ftp.services.embedded.SftpEmbeddedService;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CamelSinkSftpITCase extends CamelSinkFtpTestSupport {
+ @RegisterExtension
+ public static FtpService service = new SftpEmbeddedService();
+
+ @Override
+ protected String[] getConnectorsInTest() {
+ return new String[] {"camel-sftp-sink-kafka-connector"};
+ }
+
+ @Override
+ protected Path localDirectory() {
+ // the SFTP server exposes the local file system: its root directory
is relative to the working directory
+ return service.getFtpRootDir().resolve(DIRECTORY);
+ }
+
+ @Override
+ protected CamelFtpPropertyFactory connectorPropertyFactory() {
+ return CamelFtpPropertyFactory.sftp()
+ .withConnectionHost(service.host())
+ .withConnectionPort(service.port())
+ .withUsername(service.username())
+ .withPassword(service.password())
+ .withDirectoryName(localDirectory().toString())
+ .withStrictHostKeyChecking("yes")
+ .merge(FtpTestServers.sftpKnownHostsProperties(service));
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelFtpPropertyFactory.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelFtpPropertyFactory.java
new file mode 100644
index 0000000000..d911cf6826
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelFtpPropertyFactory.java
@@ -0,0 +1,89 @@
+/*
+ * 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.camel.kafkaconnector.ftp.source;
+
+import org.apache.camel.kafkaconnector.common.SourceConnectorPropertyFactory;
+
+/**
+ * Property factory for the ftp-source, ftps-source and sftp-source
connectors, whose Kamelets share the same options.
+ */
+final class CamelFtpPropertyFactory extends
SourceConnectorPropertyFactory<CamelFtpPropertyFactory> {
+ private final String kamelet;
+
+ private CamelFtpPropertyFactory(String kamelet) {
+ this.kamelet = kamelet;
+ }
+
+ public CamelFtpPropertyFactory withConnectionHost(String connectionHost) {
+ return withKameletProperty("connectionHost", connectionHost);
+ }
+
+ public CamelFtpPropertyFactory withConnectionPort(int connectionPort) {
+ return withKameletProperty("connectionPort",
String.valueOf(connectionPort));
+ }
+
+ public CamelFtpPropertyFactory withUsername(String username) {
+ return withKameletProperty("username", username);
+ }
+
+ public CamelFtpPropertyFactory withPassword(String password) {
+ return withKameletProperty("password", password);
+ }
+
+ public CamelFtpPropertyFactory withDirectoryName(String directoryName) {
+ return withKameletProperty("directoryName", directoryName);
+ }
+
+ public CamelFtpPropertyFactory withStrictHostKeyChecking(String
strictHostKeyChecking) {
+ return withKameletProperty("strictHostKeyChecking",
strictHostKeyChecking);
+ }
+
+ private CamelFtpPropertyFactory withKameletProperty(String name, String
value) {
+ return setProperty("camel.kamelet." + kamelet + "." + name, value);
+ }
+
+ public static CamelFtpPropertyFactory ftp() {
+ return basic("ftp-source", "CamelFtpSourceConnector",
+
"org.apache.camel.kafkaconnector.ftpsource.CamelFtpsourceSourceConnector");
+ }
+
+ public static CamelFtpPropertyFactory ftps() {
+ return basic("ftps-source", "CamelFtpsSourceConnector",
+
"org.apache.camel.kafkaconnector.ftpssource.CamelFtpssourceSourceConnector");
+ }
+
+ public static CamelFtpPropertyFactory sftp() {
+ return basic("sftp-source", "CamelSftpSourceConnector",
+
"org.apache.camel.kafkaconnector.sftpsource.CamelSftpsourceSourceConnector");
+ }
+
+ private static CamelFtpPropertyFactory basic(String kamelet, String name,
String connectorClass) {
+ return new CamelFtpPropertyFactory(kamelet)
+ .withName(name)
+ .withTasksMax(1)
+ .withConnectorClass(connectorClass)
+
.withKeyConverterClass("org.apache.kafka.connect.storage.StringConverter")
+
.withValueConverterClass("org.apache.kafka.connect.storage.StringConverter")
+ .setProperty("camel.component.kamelet.location", "kamelets")
+ // the Kamelets hand over the file content as a stream:
convert it with the Camel type converters
+ .withTransformsConfig("FileContentToString")
+ .withEntry("type",
"org.apache.camel.kafkaconnector.transforms.CamelTypeConverterTransform$Value")
+ .withEntry("target.type", "java.lang.String")
+ .end();
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpITCase.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpITCase.java
new file mode 100644
index 0000000000..1657ae45f0
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpITCase.java
@@ -0,0 +1,50 @@
+/*
+ * 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.camel.kafkaconnector.ftp.source;
+
+import java.nio.file.Path;
+
+import org.apache.camel.test.infra.ftp.services.FtpService;
+import org.apache.camel.test.infra.ftp.services.FtpServiceFactory;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CamelSourceFtpITCase extends CamelSourceFtpTestSupport {
+ @RegisterExtension
+ public static FtpService service =
FtpServiceFactory.createEmbeddedService();
+
+ @Override
+ protected String[] getConnectorsInTest() {
+ return new String[] {"camel-ftp-source-kafka-connector"};
+ }
+
+ @Override
+ protected Path localDirectory() {
+ // the FTP user home is the root directory of the server
+ return service.getFtpRootDir().resolve(DIRECTORY);
+ }
+
+ @Override
+ protected CamelFtpPropertyFactory connectorPropertyFactory() {
+ return CamelFtpPropertyFactory.ftp()
+ .withConnectionHost(service.host())
+ .withConnectionPort(service.port())
+ .withUsername(service.username())
+ .withPassword(service.password())
+ .withDirectoryName(DIRECTORY);
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpTestSupport.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpTestSupport.java
new file mode 100644
index 0000000000..4bef7b7e6e
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpTestSupport.java
@@ -0,0 +1,101 @@
+/*
+ * 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.camel.kafkaconnector.ftp.source;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.camel.kafkaconnector.CamelSourceTask;
+import org.apache.camel.kafkaconnector.common.test.CamelSourceTestSupport;
+import org.apache.camel.kafkaconnector.common.test.TestMessageConsumer;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.common.header.Header;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.Timeout;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Drops a file in the directory polled by the source connector and checks the
record sent to Kafka.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public abstract class CamelSourceFtpTestSupport extends CamelSourceTestSupport
{
+ protected static final String DIRECTORY = "source";
+
+ private static final String FILE_NAME = "source-test.txt";
+ private static final String FILE_CONTENT = "Camel Kafka Connector source
test";
+
+ private String topic;
+
+ /**
+ * @return the local path of the directory polled by the connector
+ */
+ protected abstract Path localDirectory();
+
+ /**
+ * @return the connector configuration for the server under test, without
the Kafka topic
+ */
+ protected abstract CamelFtpPropertyFactory connectorPropertyFactory();
+
+ @BeforeEach
+ public void setUp() {
+ topic = getTopicForTest(this);
+ }
+
+ @Override
+ protected void produceTestData() {
+ try {
+ Path directory = localDirectory();
+ Files.createDirectories(directory);
+
+ // write the file aside and move it in, so that the connector
cannot pick it up partially written
+ Path file = Files.writeString(directory.resolveSibling(FILE_NAME),
FILE_CONTENT);
+ Files.move(file, directory.resolve(FILE_NAME),
StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ fail("Unable to create the test file: " + e.getMessage());
+ }
+ }
+
+ @Override
+ protected void verifyMessages(TestMessageConsumer<?> consumer) {
+ List<? extends ConsumerRecord<String, ?>> records =
consumer.consumedMessages();
+ assertEquals(1, records.size(), "Did not receive the expected number
of records");
+
+ ConsumerRecord<String, ?> record = records.get(0);
+ assertEquals(FILE_CONTENT, record.value(), "The record value is not
the content of the file");
+
+ Header fileName =
record.headers().lastHeader(CamelSourceTask.HEADER_CAMEL_PREFIX + "file");
+ assertNotNull(fileName, "The record does not carry the file name
header");
+ assertEquals(FILE_NAME, new String(fileName.value(),
StandardCharsets.UTF_8), "Unexpected file name header");
+ }
+
+ @Test
+ @Timeout(180)
+ public void testRetrieveFile() throws ExecutionException,
InterruptedException {
+ runTest(connectorPropertyFactory().withKafkaTopic(topic), topic, 1);
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpsITCase.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpsITCase.java
new file mode 100644
index 0000000000..59035641ab
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceFtpsITCase.java
@@ -0,0 +1,51 @@
+/*
+ * 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.camel.kafkaconnector.ftp.source;
+
+import java.nio.file.Path;
+
+import org.apache.camel.kafkaconnector.ftp.common.FtpTestServers;
+import org.apache.camel.test.infra.ftp.services.FtpService;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CamelSourceFtpsITCase extends CamelSourceFtpTestSupport {
+ @RegisterExtension
+ public static FtpService service = FtpTestServers.createFtpsService();
+
+ @Override
+ protected String[] getConnectorsInTest() {
+ return new String[] {"camel-ftps-source-kafka-connector"};
+ }
+
+ @Override
+ protected Path localDirectory() {
+ // the FTPS user home is the root directory of the server
+ return service.getFtpRootDir().resolve(DIRECTORY);
+ }
+
+ @Override
+ protected CamelFtpPropertyFactory connectorPropertyFactory() {
+ return CamelFtpPropertyFactory.ftps()
+ .withConnectionHost(service.host())
+ .withConnectionPort(service.port())
+ .withUsername(service.username())
+ .withPassword(service.password())
+ .withDirectoryName(DIRECTORY)
+ .merge(FtpTestServers.ftpsTrustProperties());
+ }
+}
diff --git
a/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceSftpITCase.java
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceSftpITCase.java
new file mode 100644
index 0000000000..2e81c7466e
--- /dev/null
+++
b/tests/itests-ftp/src/test/java/org/apache/camel/kafkaconnector/ftp/source/CamelSourceSftpITCase.java
@@ -0,0 +1,53 @@
+/*
+ * 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.camel.kafkaconnector.ftp.source;
+
+import java.nio.file.Path;
+
+import org.apache.camel.kafkaconnector.ftp.common.FtpTestServers;
+import org.apache.camel.test.infra.ftp.services.FtpService;
+import org.apache.camel.test.infra.ftp.services.embedded.SftpEmbeddedService;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CamelSourceSftpITCase extends CamelSourceFtpTestSupport {
+ @RegisterExtension
+ public static FtpService service = new SftpEmbeddedService();
+
+ @Override
+ protected String[] getConnectorsInTest() {
+ return new String[] {"camel-sftp-source-kafka-connector"};
+ }
+
+ @Override
+ protected Path localDirectory() {
+ // the SFTP server exposes the local file system: its root directory
is relative to the working directory
+ return service.getFtpRootDir().resolve(DIRECTORY);
+ }
+
+ @Override
+ protected CamelFtpPropertyFactory connectorPropertyFactory() {
+ return CamelFtpPropertyFactory.sftp()
+ .withConnectionHost(service.host())
+ .withConnectionPort(service.port())
+ .withUsername(service.username())
+ .withPassword(service.password())
+ .withDirectoryName(localDirectory().toString())
+ .withStrictHostKeyChecking("yes")
+ .merge(FtpTestServers.sftpKnownHostsProperties(service));
+ }
+}
diff --git a/tests/pom.xml b/tests/pom.xml
index 14f6e5b4ae..616b8cc942 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -48,6 +48,7 @@
<module>itests-elasticsearch</module>
<module>itests-syslog</module>
<module>itests-file</module>
+ <module>itests-ftp</module>
<module>itests-http</module>
<module>itests-https</module>
<module>itests-timer</module>