All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.stefanbirkner.fakesftpserver.rule.FakeSftpServerRule Maven / Gradle / Ivy

package com.github.stefanbirkner.fakesftpserver.rule;

import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.password.StaticPasswordAuthenticator;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
import java.util.Set;

import static com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder.newLinux;
import static java.nio.file.Files.*;
import static java.util.Collections.singletonList;

/**
 * Fake SFTP Server Rule is a JUnit rule that runs an in-memory SFTP server
 * while your tests are running.
 * 

The Fake SFTP Server Rule is used by adding it to your test class. *

 * public class TestClass {
 *   @Rule
 *   public final FakeSftpServerRule sftpServer = new FakeSftpServerRule();
 *
 *   ...
 * }
 * 
*

This rule starts a server before your test and stops it afterwards. *

You can interact with the SFTP server by using the SFTP protocol with an * arbitrary username and password. (The server accepts every combination of * username and password.) *

The port of the server is obtained by * {@link #getPort() sftpServer.getPort()}. You can change it by calling * {@link #setPort(int)}. If you do this from within a test then the server gets * restarted. Therefore it is recommended to set the port immediately after * creating the rule in order to avoid the time consuming restart. *

 * public class TestClass {
 *   @Rule
 *   public final FakeSftpServerRule sftpServer = new FakeSftpServerRule()
 *       .setPort(1234);
 *
 *   ...
 * }
 * 
* *

Testing code that reads files

*

If you test code that reads files from an SFTP server then you need the * server to provide these files. Fake SFTP Server Rule has a shortcut for * uploading files to the server. *

 * @Test
 * public void testTextFile() {
 *   {@link #putFile(String, String, Charset) sftpServer.putFile}("/directory/file.txt", "content of file", UTF_8);
 *   //code that downloads the file
 * }
 *
 * @Test
 * public void testBinaryFile() {
 *   byte[] content = createContent();
 *   {@link #putFile(String, byte[]) sftpServer.putFile}("/directory/file.bin", content);
 *   //code that downloads the file
 * }
 * 
*

Test data that is provided as an input stream can be uploaded directly * from that input stream. This is very handy if your test data is available as * a resource. *

 * @Test
 * public void testFileFromInputStream() {
 *   InputStream is = getClass().getResourceAsStream("data.bin");
 *   {@link #putFile(String, InputStream) sftpServer.putFile}("/directory/file.bin", is);
 *   //code that downloads the file
 * }
 * 
*

If you need an empty directory then you can use the method * {@link #createDirectory(String)}. *

 * @Test
 * public void testTextFile() {
 *   sftpServer.{@link #createDirectory(String) createDirectory}("/a/directory");
 *   //code that reads from or writes to that directory
 * }
 * 
* *

Testing code that writes files

*

If you test code that writes files to an SFTP server then you need to * verify the upload. Fake SFTP Server Rule provides a shortcut for getting the * file's content from the server. *

 * @Test
 * public void testTextFile() {
 *   //code that uploads the file
 *   String fileContent = {@link #getFileContent(String, Charset) sftpServer.getFileContent}("/directory/file.txt", UTF_8);
 *   ...
 * }
 *
 * @Test
 * public void testBinaryFile() {
 *   //code that uploads the file
 *   byte[] fileContent = {@link #getFileContent(String) sftpServer.getFileContent}("/directory/file.bin");
 *   ...
 * }
 * 
* *

Testing existence of files

*

If you want to check whether a file hast been created or deleted then you * can verify that it exists or not. *

 * @Test
 * public void testFile() {
 *   //code that uploads or deletes the file
 *   boolean exists = {@link #existsFile(String) sftpServer.existsFile}("/directory/file.txt");
 *   ...
 * }
 * 
*

The method returns {@code true} iff the file exists and it is not a directory. */ public class FakeSftpServerRule implements TestRule { private int port = 23454; private FileSystem fileSystem; private SshServer server; /** * Returns the port of the SFTP server. * * @return the port of the SFTP server. */ public int getPort() { return port; } /** * Set the port of the SFTP server. The SFTP server gets restarted if you * call {@code setPort} from within a test. It is recommended to set the * port immediately after creating the rule in order to avoid the time * consuming restart. * @param port the port. Must be between 1 and 65535. * @return the rule itself. * @throws IllegalArgumentException if the port is not between 1 and 65535. * @throws IllegalStateException if the server cannot be restarted. */ public FakeSftpServerRule setPort( int port ) { if (port < 1 || port > 65535) throw new IllegalArgumentException( "Port cannot be set to " + port + " because only ports between 1 and 65535 are valid." ); this.port = port; if (server != null) restartServer(); return this; } private void restartServer() { try { server.stop(); startServer(fileSystem); } catch (IOException e) { throw new IllegalStateException( "The SFTP server cannot be restarted.", e ); } } /** * Put a text file on the SFTP folder. The file is available by the * specified path. * @param path the path to the file. * @param content the files content. * @param encoding the encoding of the file. * @throws IOException if the file cannot be written. */ public void putFile( String path, String content, Charset encoding ) throws IOException { byte[] contentAsBytes = content.getBytes(encoding); putFile(path, contentAsBytes); } /** * Put a file on the SFTP folder. The file is available by the specified * path. * @param path the path to the file. * @param content the files content. * @throws IOException if the file cannot be written. */ public void putFile( String path, byte[] content ) throws IOException { verifyThatTestIsRunning("upload file"); Path pathAsObject = fileSystem.getPath(path); ensureDirectoryOfPathExists(pathAsObject); write(pathAsObject, content); } /** * Put a file on the SFTP folder. The file is available by the specified * path. The file content is read from an {@code InputStream}. * @param path the path to the file. * @param is an {@code InputStream} that provides the file's content. * @throws IOException if the file cannot be written or the input stream * cannot be read. */ public void putFile( String path, InputStream is ) throws IOException { verifyThatTestIsRunning("upload file"); Path pathAsObject = fileSystem.getPath(path); ensureDirectoryOfPathExists(pathAsObject); copy(is, pathAsObject); } /** * Create a directory on the SFTP server. * @param path the directory's path. * @throws IOException if the directory cannot be created. */ public void createDirectory( String path ) throws IOException { verifyThatTestIsRunning("create directory"); Path pathAsObject = fileSystem.getPath(path); createDirectories(pathAsObject); } /** * Get a text file from the SFTP server. The file is decoded using the * specified encoding. * @param path the path to the file. * @param encoding the file's encoding. * @return the content of the text file. * @throws IOException if the file cannot be read. * @throws IllegalStateException if not called from within a test. */ public String getFileContent( String path, Charset encoding ) throws IOException { byte[] content = getFileContent(path); return new String(content, encoding); } /** * Get a file from the SFTP server. * @param path the path to the file. * @return the content of the file. * @throws IOException if the file cannot be read. * @throws IllegalStateException if not called from within a test. */ public byte[] getFileContent( String path ) throws IOException { verifyThatTestIsRunning("download file"); Path pathAsObject = fileSystem.getPath(path); return readAllBytes(pathAsObject); } /** * Checks the existence of a file. returns {@code true} iff the file exists * and it is not a directory. * @param path the path to the file. * @return {@code true} iff the file exists and it is not a directory. * @throws IllegalStateException if not called from within a test. */ public boolean existsFile( String path ) { verifyThatTestIsRunning("check existence of file"); Path pathAsObject = fileSystem.getPath(path); return exists(pathAsObject) && !isDirectory(pathAsObject); } @Override public Statement apply( Statement base, Description description ) { return new Statement() { @Override public void evaluate() throws Throwable { try ( FileSystem fileSystem = createFileSystem() ) { startServer(fileSystem); try { base.evaluate(); } finally { server.stop(); server = null; } } finally { fileSystem = null; } } }; } private FileSystem createFileSystem( ) throws IOException { fileSystem = newLinux().build("FakeSftpServerRule@" + hashCode()); return fileSystem; } private SshServer startServer( FileSystem fileSystem ) throws IOException { SshServer server = SshServer.setUpDefaultServer(); server.setPort(port); server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider()); server.setPasswordAuthenticator(new StaticPasswordAuthenticator(true)); server.setSubsystemFactories(singletonList(new SftpSubsystemFactory())); /* When a channel is closed SshServer calls close() on the file system. * In order to use the file system for multiple channels/sessions we * have to use a file system wrapper whose close() does nothing. */ server.setFileSystemFactory(session -> new DoNotClose(fileSystem)); server.start(); this.server = server; return server; } private void ensureDirectoryOfPathExists( Path path ) throws IOException { Path directory = path.getParent(); if (directory != null && !directory.equals(path.getRoot())) createDirectories(directory); } private void verifyThatTestIsRunning( String mode ) { if (fileSystem == null) throw new IllegalStateException( "Failed to " + mode + " because test has not been started or" + " is already finished." ); } private static class DoNotClose extends FileSystem { final FileSystem fileSystem; DoNotClose( FileSystem fileSystem ) { this.fileSystem = fileSystem; } @Override public FileSystemProvider provider() { return fileSystem.provider(); } @Override public void close( ) throws IOException { //will not be closed } @Override public boolean isOpen() { return fileSystem.isOpen(); } @Override public boolean isReadOnly() { return fileSystem.isReadOnly(); } @Override public String getSeparator() { return fileSystem.getSeparator(); } @Override public Iterable getRootDirectories() { return fileSystem.getRootDirectories(); } @Override public Iterable getFileStores() { return fileSystem.getFileStores(); } @Override public Set supportedFileAttributeViews() { return fileSystem.supportedFileAttributeViews(); } @Override public Path getPath( String first, String... more ) { return fileSystem.getPath(first, more); } @Override public PathMatcher getPathMatcher( String syntaxAndPattern ) { return fileSystem.getPathMatcher(syntaxAndPattern); } @Override public UserPrincipalLookupService getUserPrincipalLookupService() { return fileSystem.getUserPrincipalLookupService(); } @Override public WatchService newWatchService() throws IOException { return fileSystem.newWatchService(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy