You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2022/02/04 23:43:50 UTC

[nifi] branch main updated: NIFI-6699 Corrected SFTP symbolic link handling (#5744)

This is an automated email from the ASF dual-hosted git repository.

markap14 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new ce66cf4  NIFI-6699 Corrected SFTP symbolic link handling (#5744)
ce66cf4 is described below

commit ce66cf41e2e16a0aa4098d8ae5a69cd73851986d
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Fri Feb 4 17:43:42 2022 -0600

    NIFI-6699 Corrected SFTP symbolic link handling (#5744)
    
    - Added SFTP stat() request on symbolic links to check directory status
    - Refactored and renamed TestServerSFTPTransfer class
---
 .../processors/standard/util/SFTPTransfer.java     |  78 +++--
 ...TestServer.java => TestServerSFTPTransfer.java} | 384 +++++++++------------
 2 files changed, 227 insertions(+), 235 deletions(-)

diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/SFTPTransfer.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/SFTPTransfer.java
index 13b8eec..628b982 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/SFTPTransfer.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/SFTPTransfer.java
@@ -41,7 +41,6 @@ import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.logging.ComponentLog;
 import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.exception.ProcessException;
-import org.apache.nifi.processor.io.OutputStreamCallback;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.processors.standard.ssh.PatchedSFTPEngine;
 import org.apache.nifi.processors.standard.ssh.SSHClientProvider;
@@ -54,7 +53,6 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.text.DateFormat;
@@ -74,6 +72,10 @@ import java.util.stream.Collectors;
 public class SFTPTransfer implements FileTransfer {
     private static final SSHClientProvider SSH_CLIENT_PROVIDER = new StandardSSHClientProvider();
 
+    private static final String DOT_PREFIX = ".";
+    private static final String RELATIVE_CURRENT_DIRECTORY = DOT_PREFIX;
+    private static final String RELATIVE_PARENT_DIRECTORY = "..";
+
     private static final Set<String> DEFAULT_KEY_ALGORITHM_NAMES;
     private static final Set<String> DEFAULT_CIPHER_NAMES;
     private static final Set<String> DEFAULT_MESSAGE_AUTHENTICATION_CODE_NAMES;
@@ -278,7 +280,7 @@ public class SFTPTransfer implements FileTransfer {
         final boolean recurse = ctx.getProperty(FileTransfer.RECURSIVE_SEARCH).asBoolean();
         final boolean symlink  = ctx.getProperty(FileTransfer.FOLLOW_SYMLINK).asBoolean();
         final String fileFilterRegex = ctx.getProperty(FileTransfer.FILE_FILTER_REGEX).getValue();
-        final Pattern pattern = (fileFilterRegex == null) ? null : Pattern.compile(fileFilterRegex);
+        final Pattern fileFilterPattern = (fileFilterRegex == null) ? null : Pattern.compile(fileFilterRegex);
         final String pathFilterRegex = ctx.getProperty(FileTransfer.PATH_FILTER_REGEX).getValue();
         final Pattern pathPattern = (!recurse || pathFilterRegex == null) ? null : Pattern.compile(pathFilterRegex);
         final String remotePath = ctx.getProperty(FileTransfer.REMOTE_PATH).evaluateAttributeExpressions().getValue();
@@ -286,7 +288,7 @@ public class SFTPTransfer implements FileTransfer {
         // check if this directory path matches the PATH_FILTER_REGEX
         boolean pathFilterMatches = true;
         if (pathPattern != null) {
-            Path reldir = path == null ? Paths.get(".") : Paths.get(path);
+            Path reldir = path == null ? Paths.get(RELATIVE_CURRENT_DIRECTORY) : Paths.get(path);
             if (remotePath != null) {
                 reldir = Paths.get(remotePath).relativize(reldir);
             }
@@ -298,26 +300,25 @@ public class SFTPTransfer implements FileTransfer {
         }
 
         final SFTPClient sftpClient = getSFTPClient(null);
-        final boolean isPathMatch = pathFilterMatches;
+        final boolean pathMatched = pathFilterMatches;
+        final boolean filteringDisabled = !applyFilters;
 
-        //subDirs list is used for both 'sub directories' and 'symlinks'
         final List<RemoteResourceInfo> subDirs = new ArrayList<>();
         try {
             final RemoteResourceFilter filter = (entry) -> {
                 final String entryFilename = entry.getName();
 
                 // skip over 'this directory' and 'parent directory' special files regardless of ignoring dot files
-                if (entryFilename.equals(".") || entryFilename.equals("..")) {
+                if (RELATIVE_CURRENT_DIRECTORY.equals(entryFilename) || RELATIVE_PARENT_DIRECTORY.equals(entryFilename)) {
                     return false;
                 }
 
                 // skip files and directories that begin with a dot if we're ignoring them
-                if (ignoreDottedFiles && entryFilename.startsWith(".")) {
+                if (ignoreDottedFiles && entryFilename.startsWith(DOT_PREFIX)) {
                     return false;
                 }
 
-                // if is a directory and we're supposed to recurse OR if is a link and we're supposed to follow symlink
-                if ((recurse && entry.isDirectory()) || (symlink && (entry.getAttributes().getType() == FileMode.Type.SYMLINK))){
+                if (isIncludedDirectory(entry, recurse, symlink)) {
                     subDirs.add(entry);
                     return false;
                 }
@@ -328,9 +329,8 @@ public class SFTPTransfer implements FileTransfer {
                     return false;
                 }
 
-                // if is not a directory and is not a link and it matches FILE_FILTER_REGEX - then let's add it
-                if (!entry.isDirectory() && !(entry.getAttributes().getType() == FileMode.Type.SYMLINK) && (!applyFilters || isPathMatch)) {
-                    if (pattern == null || !applyFilters || pattern.matcher(entryFilename).matches()) {
+                if (isIncludedFile(entry, symlink) && (filteringDisabled || pathMatched)) {
+                    if (filteringDisabled || fileFilterPattern == null || fileFilterPattern.matcher(entryFilename).matches()) {
                         listing.add(newFileInfo(entry, path));
                     }
                 }
@@ -339,7 +339,7 @@ public class SFTPTransfer implements FileTransfer {
             };
 
             if (path == null || path.trim().isEmpty()) {
-                sftpClient.ls(".", filter);
+                sftpClient.ls(RELATIVE_CURRENT_DIRECTORY, filter);
             } else {
                 sftpClient.ls(path, filter);
             }
@@ -370,6 +370,47 @@ public class SFTPTransfer implements FileTransfer {
 
     }
 
+    /**
+     * Include remote resources when regular file found or when symbolic links are enabled and the resource is a link
+     *
+     * @param remoteResourceInfo Remote Resource Information
+     * @param symlinksEnabled Follow symbolic links enabled
+     * @return Included file status
+     */
+    private boolean isIncludedFile(final RemoteResourceInfo remoteResourceInfo, final boolean symlinksEnabled) {
+        return remoteResourceInfo.isRegularFile() || (symlinksEnabled && isSymlink(remoteResourceInfo));
+    }
+
+    /**
+     * Include remote resources when recursion is enabled or when symbolic links are enabled and the resource is a directory link
+     *
+     * @param remoteResourceInfo Remote Resource Information
+     * @param recursionEnabled Recursion enabled status
+     * @param symlinksEnabled Follow symbolic links enabled
+     * @return Included directory status
+     */
+    private boolean isIncludedDirectory(final RemoteResourceInfo remoteResourceInfo, final boolean recursionEnabled, final boolean symlinksEnabled) {
+        boolean includedDirectory = false;
+
+        if (remoteResourceInfo.isDirectory()) {
+            includedDirectory = recursionEnabled;
+        } else if (symlinksEnabled && isSymlink(remoteResourceInfo)) {
+            final String path = remoteResourceInfo.getPath();
+            try {
+                final FileAttributes pathAttributes = sftpClient.stat(path);
+                includedDirectory = FileMode.Type.DIRECTORY == pathAttributes.getMode().getType();
+            } catch (final IOException e) {
+                logger.warn("Read symbolic link attributes failed [{}]", path, e);
+            }
+        }
+
+        return includedDirectory;
+    }
+
+    private boolean isSymlink(final RemoteResourceInfo remoteResourceInfo) {
+        return FileMode.Type.SYMLINK == remoteResourceInfo.getAttributes().getType();
+    }
+
     private FileInfo newFileInfo(final RemoteResourceInfo entry, String path) {
         if (entry == null) {
             return null;
@@ -422,12 +463,7 @@ public class SFTPTransfer implements FileTransfer {
             rf = sftpClient.open(remoteFileName);
             rfis = rf.new ReadAheadRemoteFileInputStream(16);
             final InputStream in = rfis;
-            resultFlowFile = session.write(origFlowFile, new OutputStreamCallback() {
-                @Override
-                public void process(final OutputStream out) throws IOException {
-                    StreamUtils.copy(in, out);
-                }
-            });
+            resultFlowFile = session.write(origFlowFile, out -> StreamUtils.copy(in, out));
             return resultFlowFile;
         } catch (final SFTPException e) {
             switch (e.getStatusCode()) {
@@ -660,7 +696,7 @@ public class SFTPTransfer implements FileTransfer {
         String tempFilename = ctx.getProperty(TEMP_FILENAME).evaluateAttributeExpressions(flowFile).getValue();
         if (tempFilename == null) {
             final boolean dotRename = ctx.getProperty(DOT_RENAME).asBoolean();
-            tempFilename = dotRename ? "." + filename : filename;
+            tempFilename = dotRename ? DOT_PREFIX + filename : filename;
         }
         final String tempPath = (path == null) ? tempFilename : (path.endsWith("/")) ? path + tempFilename : path + "/" + tempFilename;
 
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/ITestSFTPTransferWithSSHTestServer.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestServerSFTPTransfer.java
similarity index 62%
rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/ITestSFTPTransferWithSSHTestServer.java
rename to nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestServerSFTPTransfer.java
index 50813c9..762b72c 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/ITestSFTPTransferWithSSHTestServer.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestServerSFTPTransfer.java
@@ -16,30 +16,27 @@
  */
 package org.apache.nifi.processors.standard.util;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.context.PropertyContext;
 import org.apache.nifi.logging.ComponentLog;
 import org.apache.nifi.util.MockPropertyContext;
-import org.apache.nifi.util.file.FileUtils;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -47,27 +44,33 @@ import java.nio.file.Paths;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.UUID;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class ITestSFTPTransferWithSSHTestServer {
+public class TestServerSFTPTransfer {
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(ITestSFTPTransferWithSSHTestServer.class);
+    private static final String LOCALHOST = "127.0.0.1";
 
-    private static final String SFTP_ROOT_DIR = "target/test-sftp-transfer-vfs";
+    private static final String USERNAME = "user";
+
+    private static final String PASSWORD = UUID.randomUUID().toString();
 
     private static final String DIR_1 = "dir1";
     private static final String DIR_2 = "dir2";
-    private static final String DIR_3 = "dir3";
-    private static final String DIR_4 = "dir4";
+    private static final String LINKED_DIRECTORY = "linked-directory";
+    private static final String LINKED_FILE = "linked-file";
+    private static final String EMPTY_DIRECTORY = "dir4";
 
     private static final String DIR_1_CHILD_1 = "child1";
     private static final String DIR_1_CHILD_2 = "child2";
@@ -76,66 +79,45 @@ public class ITestSFTPTransferWithSSHTestServer {
     private static final String FILE_2 = "file2.txt";
     private static final String DOT_FILE = ".foo.txt";
 
-    private static SSHTestServer sshTestServer;
+    private static final boolean FILTERING_ENABLED = true;
 
-    @BeforeClass
-    public static void setupClass() throws IOException {
-        sshTestServer = new SSHTestServer();
-        sshTestServer.setVirtualFileSystemPath(SFTP_ROOT_DIR);
-        sshTestServer.startServer();
-    }
+    @TempDir
+    File serverDirectory;
 
-    @AfterClass
-    public static void cleanupClass() throws IOException {
-        sshTestServer.stopServer();
-    }
+    private SshServer sshServer;
 
-    @Before
+    @BeforeEach
     public void setupFiles() throws IOException {
-        final File sftpRootDir = new File(SFTP_ROOT_DIR);
-        FileUtils.deleteFilesInDir(sftpRootDir, null, LOGGER, true, true);
+        writeFile(DIR_1, DIR_1_CHILD_1, FILE_1);
+        writeFile(DIR_1, DIR_1_CHILD_1, FILE_2);
+        writeFile(DIR_1, DIR_1_CHILD_1, DOT_FILE);
 
-        // create and initialize dir1/child1
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_1 + "/" + DIR_1_CHILD_1, FILE_1, "dir1 child1 file1");
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_1 + "/" + DIR_1_CHILD_1, FILE_2, "dir1 child1 file2");
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_1 + "/" + DIR_1_CHILD_1, DOT_FILE, "dir1 child1 foo");
+        writeFile(DIR_1, DIR_1_CHILD_2, FILE_1);
+        writeFile(DIR_1, DIR_1_CHILD_2, FILE_2);
+        writeFile(DIR_1, DIR_1_CHILD_2, DOT_FILE);
 
-        // create and initialize dir1/child2
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_1 + "/" + DIR_1_CHILD_2, FILE_1, "dir1 child2 file1");
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_1 + "/" + DIR_1_CHILD_2, FILE_2, "dir1 child2 file2");
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_1 + "/" + DIR_1_CHILD_2, DOT_FILE, "dir1 child2 foo");
+        writeFile(DIR_2, FILE_1);
+        writeFile(DIR_2, FILE_2);
+        writeFile(DIR_2, DOT_FILE);
 
-        // create and initialize dir2
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_2, FILE_1, "dir2 file1");
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_2, FILE_2, "dir2 file2");
-        initializeFile(SFTP_ROOT_DIR + "/" + DIR_2, DOT_FILE, "dir2 foo");
+        final File linkedDirectory = new File(serverDirectory, LINKED_DIRECTORY);
+        final File linkedDirectoryTarget = new File(serverDirectory.getAbsolutePath(), DIR_1);
+        Files.createSymbolicLink(linkedDirectory.toPath(), linkedDirectoryTarget.toPath());
 
-        // Create a symbolic link so that dir3/dir1 links to dir1 so we can test following links
-        final Path targetPath = Paths.get("../" + DIR_1);
+        final File secondDirectory = new File(serverDirectory, DIR_2);
+        final File linkedFile = new File(serverDirectory, LINKED_FILE);
+        final File linkedFileTarget = new File(secondDirectory, FILE_1);
+        Files.createSymbolicLink(linkedFile.toPath(), linkedFileTarget.toPath());
 
-        final String dir3Path = SFTP_ROOT_DIR + "/" + DIR_3;
-        FileUtils.ensureDirectoryExistAndCanAccess(new File(dir3Path));
-        final Path linkPath = Paths.get(dir3Path + "/" + DIR_1);
+        final File emptyDirectory = new File(serverDirectory, EMPTY_DIRECTORY);
+        assertTrue(emptyDirectory.mkdirs());
 
-        Files.createSymbolicLink(linkPath, targetPath);
-
-        // create dir4 for writing files
-        final File dir4File = new File(SFTP_ROOT_DIR + "/" + DIR_4);
-        FileUtils.ensureDirectoryExistAndCanAccess(dir4File);
+        startServer();
     }
 
-    private void initializeFile(final String path, final String filename, final String content) throws IOException {
-        final File parent = new File(path);
-        if (!parent.exists()) {
-            assertTrue("Failed to create parent directory: " + path, parent.mkdirs());
-        }
-
-        final File file = new File(parent, filename);
-        try (final OutputStream out = new FileOutputStream(file);
-             final Writer writer = new OutputStreamWriter(out)) {
-            writer.write(content);
-            writer.flush();
-        }
+    @AfterEach
+    public void stopServer() throws IOException {
+        sshServer.stop(true);
     }
 
     @Test
@@ -143,20 +125,18 @@ public class ITestSFTPTransferWithSSHTestServer {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_2);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
 
             final FileInfo file1Info = listing.stream().filter(f -> f.getFileName().equals(FILE_1)).findFirst().orElse(null);
             assertNotNull(file1Info);
             assertFalse(file1Info.isDirectory());
-            assertEquals("rw-r--r--", file1Info.getPermissions());
 
             final FileInfo file2Info = listing.stream().filter(f -> f.getFileName().equals(FILE_2)).findFirst().orElse(null);
             assertNotNull(file2Info);
             assertFalse(file2Info.isDirectory());
-            assertEquals("rw-r--r--", file2Info.getPermissions());
         }
     }
 
@@ -166,8 +146,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_2);
         properties.put(SFTPTransfer.IGNORE_DOTTED_FILES, "false");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(3, listing.size());
 
@@ -182,8 +162,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_1);
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "false");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(0, listing.size());
         }
@@ -195,8 +175,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_1);
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(4, listing.size());
         }
@@ -205,28 +185,26 @@ public class ITestSFTPTransferWithSSHTestServer {
     @Test
     public void testGetListingWithoutSymlinks() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
-        properties.put(SFTPTransfer.REMOTE_PATH, DIR_3);
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
         properties.put(SFTPTransfer.FOLLOW_SYMLINK, "false");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
-            assertEquals(0, listing.size());
+            assertEquals(6, listing.size());
         }
     }
 
     @Test
     public void testGetListingWithSymlinks() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
-        properties.put(SFTPTransfer.REMOTE_PATH, DIR_3);
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
         properties.put(SFTPTransfer.FOLLOW_SYMLINK, "true");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
-            assertEquals(4, listing.size());
+            assertEquals(11, listing.size());
         }
     }
 
@@ -237,8 +215,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
 
         // first listing is without batch size and shows 4 results
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(4, listing.size());
         }
@@ -246,8 +224,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         // set a batch size of 2 and ensure we get 2 results
         properties.put(SFTPTransfer.REMOTE_POLL_BATCH_SIZE, "2");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
         }
@@ -262,8 +240,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
         properties.put(SFTPTransfer.FILE_FILTER_REGEX, fileFilterRegex);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
 
@@ -281,32 +259,21 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
         properties.put(SFTPTransfer.PATH_FILTER_REGEX, pathFilterRegex);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            final List<FileInfo> listing = transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
-
-            // a listing will have fullPathFileName like "./dir1/child1/file1.txt" so to verify the path pattern
-            // we need to remove the file part and relativize based on the remote path to get "dir1/child1"
-            listing.forEach(f -> {
-                final String filename = f.getFileName();
-                final String path = f.getFullPathFileName().replace(filename, "");
-
-                final Path fullPath = Paths.get(path);
-                final Path relPath = Paths.get(remotePath).relativize(fullPath);
-                assertTrue(relPath.toString().matches(pathFilterRegex));
-            });
         }
     }
 
-    @Test(expected = FileNotFoundException.class)
+    @Test
     public void testGetListingWhenRemotePathDoesNotExist() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, "DOES-NOT-EXIST");
         properties.put(SFTPTransfer.RECURSIVE_SEARCH, "true");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            transfer.getListing(true);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            assertThrows(FileNotFoundException.class, () -> transfer.getListing(FILTERING_ENABLED));
         }
     }
 
@@ -315,9 +282,9 @@ public class ITestSFTPTransferWithSSHTestServer {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_2);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             // verify the directory has two files
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
 
@@ -327,7 +294,7 @@ public class ITestSFTPTransferWithSSHTestServer {
             }
 
             // verify there are now zero files
-            final List<FileInfo> listingAfterDelete = transfer.getListing(true);
+            final List<FileInfo> listingAfterDelete = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listingAfterDelete);
             assertEquals(0, listingAfterDelete.size());
         }
@@ -338,9 +305,9 @@ public class ITestSFTPTransferWithSSHTestServer {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_2);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             // verify the directory has two files
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
 
@@ -352,50 +319,44 @@ public class ITestSFTPTransferWithSSHTestServer {
             }
 
             // verify there are now zero files
-            final List<FileInfo> listingAfterDelete = transfer.getListing(true);
+            final List<FileInfo> listingAfterDelete = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listingAfterDelete);
             assertEquals(0, listingAfterDelete.size());
         }
     }
 
-    @Test(expected = FileNotFoundException.class)
+    @Test
     public void testDeleteFileWhenDoesNotExist() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            transfer.deleteFile(null, null, "foo/bar/does-not-exist.txt");
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            assertThrows(FileNotFoundException.class, () -> transfer.deleteFile(null, null, "foo/bar/does-not-exist.txt"));
         }
     }
 
     @Test
     public void testDeleteDirectory() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
-        properties.put(SFTPTransfer.REMOTE_PATH, DIR_4);
+        properties.put(SFTPTransfer.REMOTE_PATH, EMPTY_DIRECTORY);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             // verify the directory exists
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(0, listing.size());
 
-            transfer.deleteDirectory(null, DIR_4);
+            transfer.deleteDirectory(null, EMPTY_DIRECTORY);
 
-            // verify the directory no longer exists
-            try {
-                transfer.getListing(true);
-                Assert.fail("Should have thrown exception");
-            } catch (FileNotFoundException e) {
-                // nothing to do, expected
-            }
+            assertThrows(FileNotFoundException.class, () -> transfer.getListing(FILTERING_ENABLED));
         }
     }
 
-    @Test(expected = IOException.class)
+    @Test
     public void testDeleteDirectoryWhenDoesNotExist() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            transfer.deleteDirectory(null, "DOES-NOT-EXIST");
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            assertThrows(IOException.class, () -> transfer.deleteDirectory(null, "DOES-NOT-EXIST"));
         }
     }
 
@@ -405,20 +366,15 @@ public class ITestSFTPTransferWithSSHTestServer {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, remotePath);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             // verify the directory does not exist
-            try {
-                transfer.getListing(true);
-                Assert.fail("Should have failed");
-            } catch (FileNotFoundException e) {
-                // Nothing to do, expected
-            }
+            assertThrows(FileNotFoundException.class, () -> transfer.getListing(FILTERING_ENABLED));
 
             final String absolutePath = transfer.getAbsolutePath(null, remotePath);
             transfer.ensureDirectoryExists(null, new File(absolutePath));
 
             // verify the directory now exists
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(0, listing.size());
         }
@@ -430,20 +386,14 @@ public class ITestSFTPTransferWithSSHTestServer {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, remotePath);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            // verify the directory does not exist
-            try {
-                transfer.getListing(true);
-                Assert.fail("Should have failed");
-            } catch (FileNotFoundException e) {
-                // Nothing to do, expected
-            }
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            assertThrows(FileNotFoundException.class, () -> transfer.getListing(FILTERING_ENABLED));
 
             final String absolutePath = transfer.getAbsolutePath(null, remotePath);
             transfer.ensureDirectoryExists(null, new File(absolutePath));
 
             // verify the directory now exists
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(0, listing.size());
         }
@@ -454,9 +404,9 @@ public class ITestSFTPTransferWithSSHTestServer {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_2);
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             // verify the directory already exists
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
 
@@ -472,39 +422,33 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.REMOTE_PATH, remotePath);
         properties.put(SFTPTransfer.DISABLE_DIRECTORY_LISTING, "true");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            // verify the directory does not exist
-            try {
-                transfer.getListing(true);
-                Assert.fail("Should have failed");
-            } catch (FileNotFoundException e) {
-                // Nothing to do, expected
-            }
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            assertThrows(FileNotFoundException.class, () -> transfer.getListing(FILTERING_ENABLED));
 
             final String absolutePath = transfer.getAbsolutePath(null, remotePath);
             transfer.ensureDirectoryExists(null, new File(absolutePath));
 
             // verify the directory now exists
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(0, listing.size());
         }
     }
 
-    @Test(expected = IOException.class)
+    @Test
     public void testEnsureDirectoryExistsWithDirectoryListingDisabledAndAlreadyExists() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
         properties.put(SFTPTransfer.REMOTE_PATH, DIR_2);
         properties.put(SFTPTransfer.DISABLE_DIRECTORY_LISTING, "true");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             // verify the directory already exists
-            final List<FileInfo> listing = transfer.getListing(true);
+            final List<FileInfo> listing = transfer.getListing(FILTERING_ENABLED);
             assertNotNull(listing);
             assertEquals(2, listing.size());
 
             final String absolutePath = transfer.getAbsolutePath(null, DIR_2);
-            transfer.ensureDirectoryExists(null, new File(absolutePath));
+            assertThrows(IOException.class, () -> transfer.ensureDirectoryExists(null, new File(absolutePath)));
         }
     }
 
@@ -516,14 +460,8 @@ public class ITestSFTPTransferWithSSHTestServer {
         properties.put(SFTPTransfer.REMOTE_PATH, remotePath);
         properties.put(SFTPTransfer.DISABLE_DIRECTORY_LISTING, "true");
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
-            // verify the directory does not exist
-            try {
-                transfer.getListing(true);
-                Assert.fail("Should have failed");
-            } catch (FileNotFoundException e) {
-                // Nothing to do, expected
-            }
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+            assertThrows(FileNotFoundException.class, () -> transfer.getListing(FILTERING_ENABLED));
 
             // Should swallow exception here
             final String absolutePath = transfer.getAbsolutePath(null, remotePath);
@@ -535,7 +473,7 @@ public class ITestSFTPTransferWithSSHTestServer {
     public void testGetRemoteFileInfo() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final FileInfo fileInfo = transfer.getRemoteFileInfo(null, DIR_2, FILE_1);
             assertNotNull(fileInfo);
             assertEquals(FILE_1, fileInfo.getFileName());
@@ -546,7 +484,7 @@ public class ITestSFTPTransferWithSSHTestServer {
     public void testGetRemoteFileInfoWhenPathDoesNotExist() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final FileInfo fileInfo = transfer.getRemoteFileInfo(null, "DOES-NOT-EXIST", FILE_1);
             assertNull(fileInfo);
         }
@@ -556,7 +494,7 @@ public class ITestSFTPTransferWithSSHTestServer {
     public void testGetRemoteFileInfoWhenFileDoesNotExist() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final FileInfo fileInfo = transfer.getRemoteFileInfo(null, DIR_2, "DOES-NOT-EXIST");
             assertNull(fileInfo);
         }
@@ -566,7 +504,7 @@ public class ITestSFTPTransferWithSSHTestServer {
     public void testGetRemoteFileInfoWhenFileIsADirectory() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final FileInfo fileInfo = transfer.getRemoteFileInfo(null, DIR_1, DIR_1_CHILD_1);
             assertNull(fileInfo);
         }
@@ -576,7 +514,7 @@ public class ITestSFTPTransferWithSSHTestServer {
     public void testRename() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final String source = DIR_2 + "/" + FILE_1;
             final String target = DIR_2 + "/" + FILE_1 + "-RENAMED";
 
@@ -590,29 +528,29 @@ public class ITestSFTPTransferWithSSHTestServer {
         }
     }
 
-    @Test(expected = FileNotFoundException.class)
+    @Test
     public void testRenameWhenSourceDoesNotExist() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final String source = DIR_2 + "/DOES-NOT-EXIST";
             final String target = DIR_2 + "/" + FILE_1 + "-RENAMED";
-            transfer.rename(null, source, target);
+            assertThrows(FileNotFoundException.class, () -> transfer.rename(null, source, target));
         }
     }
 
-    @Test(expected = IOException.class)
+    @Test
     public void testRenameWhenTargetAlreadyExists() throws IOException {
         final Map<PropertyDescriptor, String> properties = createBaseProperties();
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties)) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties)) {
             final String source = DIR_2 + "/" + FILE_1;
             final String target = DIR_2 + "/" + FILE_2;
 
             final FileInfo targetInfoBefore = transfer.getRemoteFileInfo(null, DIR_2, FILE_2);
             assertNotNull(targetInfoBefore);
 
-            transfer.rename(null, source, target);
+            assertThrows(IOException.class, () -> transfer.rename(null, source, target));
         }
     }
 
@@ -626,23 +564,23 @@ public class ITestSFTPTransferWithSSHTestServer {
         final String filename = "test-put-simple.txt";
         final String fileContent = "this is a test";
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties);
-            final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties);
+             final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
 
             // Verify file does not already exist
-            final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, DIR_4, filename);
+            final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, EMPTY_DIRECTORY, filename);
             assertNull(fileInfoBefore);
 
-            final String fullPath = transfer.put(null, DIR_4, filename, in);
+            final String fullPath = transfer.put(null, EMPTY_DIRECTORY, filename, in);
             assertNotNull(fullPath);
 
             // Verify file now exists
-            final FileInfo fileInfoAfter = transfer.getRemoteFileInfo(null, DIR_4, filename);
+            final FileInfo fileInfoAfter = transfer.getRemoteFileInfo(null, EMPTY_DIRECTORY, filename);
             assertNotNull(fileInfoAfter);
             assertEquals(permissions, fileInfoAfter.getPermissions());
 
             // Verify correct content was written
-            final File writtenFile = new File(SFTP_ROOT_DIR + "/" + DIR_4 + "/" + filename);
+            final File writtenFile = new File(serverDirectory, EMPTY_DIRECTORY + "/" + filename);
             final String retrievedContent = IOUtils.toString(writtenFile.toURI(), StandardCharsets.UTF_8);
             assertEquals(fileContent, retrievedContent);
         }
@@ -659,18 +597,18 @@ public class ITestSFTPTransferWithSSHTestServer {
         final String filename = "test-put-simple.txt";
         final String fileContent = "this is a test";
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties);
-            final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties);
+             final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
 
             // Verify file does not already exist
-            final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, DIR_4, filename);
+            final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, EMPTY_DIRECTORY, filename);
             assertNull(fileInfoBefore);
 
-            final String fullPath = transfer.put(null, DIR_4, filename, in);
+            final String fullPath = transfer.put(null, EMPTY_DIRECTORY, filename, in);
             assertNotNull(fullPath);
 
             // Verify file now exists
-            final FileInfo fileInfoAfter = transfer.getRemoteFileInfo(null, DIR_4, filename);
+            final FileInfo fileInfoAfter = transfer.getRemoteFileInfo(null, EMPTY_DIRECTORY, filename);
             assertNotNull(fileInfoAfter);
             assertEquals(permissions, fileInfoAfter.getPermissions());
         }
@@ -691,25 +629,25 @@ public class ITestSFTPTransferWithSSHTestServer {
         final String filename = "test-put-simple.txt";
         final String fileContent = "this is a test";
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties);
-            final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties);
+             final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
 
             // Verify file does not already exist
-            final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, DIR_4, filename);
+            final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, EMPTY_DIRECTORY, filename);
             assertNull(fileInfoBefore);
 
-            final String fullPath = transfer.put(null, DIR_4, filename, in);
+            final String fullPath = transfer.put(null, EMPTY_DIRECTORY, filename, in);
             assertNotNull(fullPath);
 
             // Verify file now exists
-            final FileInfo fileInfoAfter = transfer.getRemoteFileInfo(null, DIR_4, filename);
+            final FileInfo fileInfoAfter = transfer.getRemoteFileInfo(null, EMPTY_DIRECTORY, filename);
             assertNotNull(fileInfoAfter);
             assertEquals(permissions, fileInfoAfter.getPermissions());
             assertEquals(expectedLastModifiedTime, fileInfoAfter.getLastModifiedTime());
         }
     }
 
-    @Test(expected = IOException.class)
+    @Test
     public void testPutWhenFileAlreadyExists() throws IOException {
         final String permissions = "rw-rw-rw-";
 
@@ -718,19 +656,19 @@ public class ITestSFTPTransferWithSSHTestServer {
 
         final String fileContent = "this is a test";
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties);
-            final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties);
+             final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
 
             // Verify file already exists
             final FileInfo fileInfoBefore = transfer.getRemoteFileInfo(null, DIR_2, FILE_1);
             assertNotNull(fileInfoBefore);
 
             // Should fail because file already exists
-            transfer.put(null, DIR_2, FILE_1, in);
+            assertThrows(IOException.class, () -> transfer.put(null, DIR_2, FILE_1, in));
         }
     }
 
-    @Test(expected = IOException.class)
+    @Test
     public void testPutWhenDirectoryDoesNotExist() throws IOException {
         final String permissions = "rw-rw-rw-";
 
@@ -739,19 +677,19 @@ public class ITestSFTPTransferWithSSHTestServer {
 
         final String fileContent = "this is a test";
 
-        try(final SFTPTransfer transfer = createSFTPTransfer(properties);
-            final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
-            transfer.put(null, "DOES-NOT-EXIST", FILE_1, in);
+        try (final SFTPTransfer transfer = createSFTPTransfer(properties);
+             final InputStream in = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))) {
+            assertThrows(IOException.class, () -> transfer.put(null, "DOES-NOT-EXIST", FILE_1, in));
         }
     }
 
     private Map<PropertyDescriptor, String> createBaseProperties() {
-        final Map<PropertyDescriptor,String> properties = new HashMap<>();
-        properties.put(SFTPTransfer.HOSTNAME, "localhost");
-        properties.put(SFTPTransfer.PORT, Integer.toString(sshTestServer.getSSHPort()));
-        properties.put(SFTPTransfer.USERNAME, sshTestServer.getUsername());
-        properties.put(SFTPTransfer.PASSWORD, sshTestServer.getPassword());
-        properties.put(SFTPTransfer.STRICT_HOST_KEY_CHECKING, "false");
+        final Map<PropertyDescriptor, String> properties = new HashMap<>();
+        properties.put(SFTPTransfer.HOSTNAME, LOCALHOST);
+        properties.put(SFTPTransfer.PORT, Integer.toString(sshServer.getPort()));
+        properties.put(SFTPTransfer.USERNAME, USERNAME);
+        properties.put(SFTPTransfer.PASSWORD, PASSWORD);
+        properties.put(SFTPTransfer.STRICT_HOST_KEY_CHECKING, Boolean.FALSE.toString());
         return properties;
     }
 
@@ -760,4 +698,22 @@ public class ITestSFTPTransferWithSSHTestServer {
         final ComponentLog logger = Mockito.mock(ComponentLog.class);
         return new SFTPTransfer(propertyContext, logger);
     }
+
+    private void startServer() throws IOException {
+        sshServer = SshServer.setUpDefaultServer();
+        sshServer.setHost(LOCALHOST);
+        sshServer.setPasswordAuthenticator((username, password, serverSession) -> USERNAME.equals(username) && PASSWORD.equals(password));
+        sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
+        sshServer.setFileSystemFactory(new VirtualFileSystemFactory(serverDirectory.toPath()));
+        sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+        sshServer.start();
+    }
+
+    private void writeFile(final String... pathElements) throws IOException {
+        final Path path = Paths.get(serverDirectory.getAbsolutePath(), pathElements);
+        final File parentFile = path.toFile().getParentFile();
+        FileUtils.forceMkdir(parentFile);
+        final byte[] contents = path.toFile().getAbsolutePath().getBytes(StandardCharsets.UTF_8);
+        Files.write(path, contents);
+    }
 }