Kaynağa Gözat

make some better use of mocks during unit tests

Sebastian Stenzel 9 yıl önce
ebeveyn
işleme
97a72ecbf7

+ 0 - 5
main/core/pom.xml

@@ -27,11 +27,6 @@
 			<groupId>org.cryptomator</groupId>
 			<artifactId>crypto-api</artifactId>
 		</dependency>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>crypto-aes</artifactId>
-			<scope>test</scope>
-		</dependency>
 
 		<!-- Jetty (Servlet Container) -->
 		<dependency>

+ 103 - 0
main/core/src/test/java/org/cryptomator/webdav/jackrabbit/CryptorMock.java

@@ -0,0 +1,103 @@
+package org.cryptomator.webdav.jackrabbit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+
+import org.cryptomator.crypto.Cryptor;
+import org.cryptomator.crypto.exceptions.DecryptFailedException;
+import org.cryptomator.crypto.exceptions.EncryptFailedException;
+import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
+import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
+import org.cryptomator.crypto.exceptions.WrongPasswordException;
+
+class CryptorMock implements Cryptor {
+
+	private static final int BUFSIZE = 32768;
+
+	@Override
+	public void randomizeMasterKey() {
+		// noop
+	}
+
+	@Override
+	public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
+		// noop
+	}
+
+	@Override
+	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
+		// noop
+	}
+
+	@Override
+	public String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep) {
+		return cleartextDirectoryId;
+	}
+
+	@Override
+	public String encryptFilename(String cleartextName) {
+		return cleartextName;
+	}
+
+	@Override
+	public String decryptFilename(String ciphertextName) throws DecryptFailedException {
+		return ciphertextName;
+	}
+
+	@Override
+	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException {
+		return encryptedFile.size();
+	}
+
+	@Override
+	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
+		ByteBuffer buf = ByteBuffer.allocate(BUFSIZE);
+		long numReadTotal = 0;
+		int numRead = 0;
+		while ((numRead = encryptedFile.read(buf)) != -1) {
+			numReadTotal += numRead;
+			buf.flip();
+			byte[] bytes = new byte[numRead];
+			buf.get(bytes);
+			plaintextFile.write(bytes);
+			buf.rewind();
+		}
+		return numReadTotal;
+	}
+
+	@Override
+	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException {
+		encryptedFile.position(pos);
+
+		ByteBuffer buf = ByteBuffer.allocate(BUFSIZE);
+		long numReadTotal = 0;
+		int numRead = 0;
+		while ((numRead = encryptedFile.read(buf)) != -1 && numReadTotal < length) {
+			int len = (int) Math.min(Math.min(numRead, BUFSIZE), length - numReadTotal); // known to fit into integer
+			numReadTotal += len;
+			buf.flip();
+			byte[] bytes = new byte[len];
+			buf.get(bytes);
+			plaintextFile.write(bytes);
+			buf.rewind();
+		}
+		return numReadTotal;
+	}
+
+	@Override
+	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
+		byte[] buf = new byte[BUFSIZE];
+		long numWrittenTotal = 0;
+		int numRead = 0;
+		while ((numRead = plaintextFile.read(buf)) != -1) {
+			numWrittenTotal += numRead;
+			encryptedFile.write(ByteBuffer.wrap(buf, 0, numRead));
+		}
+		return numWrittenTotal;
+	}
+
+}

+ 2 - 2
main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java

@@ -26,7 +26,7 @@ import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.PutMethod;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
-import org.cryptomator.crypto.aes256.Aes256Cryptor;
+import org.cryptomator.crypto.Cryptor;
 import org.cryptomator.webdav.WebDavServer;
 import org.cryptomator.webdav.WebDavServer.ServletLifeCycleAdapter;
 import org.junit.AfterClass;
@@ -41,7 +41,7 @@ import com.google.common.io.Files;
 public class RangeRequestTest {
 
 	private static final Logger LOG = LoggerFactory.getLogger(RangeRequestTest.class);
-	private static final Aes256Cryptor CRYPTOR = new Aes256Cryptor();
+	private static final Cryptor CRYPTOR = new CryptorMock();
 	private static final WebDavServer SERVER = new WebDavServer();
 	private static final File TMP_VAULT = Files.createTempDir();
 	private static ServletLifeCycleAdapter SERVLET;

+ 11 - 0
main/crypto-aes/pom.xml

@@ -65,5 +65,16 @@
 			<groupId>com.fasterxml.jackson.core</groupId>
 			<artifactId>jackson-databind</artifactId>
 		</dependency>
+		
+		<!-- DI -->
+		<dependency>
+			<groupId>com.google.dagger</groupId>
+			<artifactId>dagger</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.dagger</groupId>
+			<artifactId>dagger-compiler</artifactId>
+			<scope>provided</scope>
+		</dependency>
 	</dependencies>
 </project>

+ 5 - 8
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -103,14 +103,11 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 	/**
 	 * Creates a new Cryptor with a newly initialized PRNG.
 	 */
-	public Aes256Cryptor() {
-		try {
-			securePrng = SecureRandom.getInstanceStrong();
-			// No setSeed needed. See SecureRandom.getInstance(String):
-			// The first call to nextBytes will force the SecureRandom object to seed itself
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("PRNG algorithm should exist.", e);
-		}
+
+	Aes256Cryptor(SecureRandom securePrng) {
+		this.securePrng = securePrng;
+		// No setSeed needed. See SecureRandom.getInstance(String):
+		// The first call to nextBytes will force the SecureRandom object to seed itself
 	}
 
 	@Override

+ 15 - 0
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoComponent.java

@@ -0,0 +1,15 @@
+package org.cryptomator.crypto.aes256;
+
+import javax.inject.Singleton;
+
+import org.cryptomator.crypto.Cryptor;
+
+import dagger.Component;
+
+@Singleton
+@Component(modules = CryptoModule.class)
+interface CryptoComponent {
+
+	Cryptor cryptor();
+
+}

+ 29 - 0
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoModule.java

@@ -0,0 +1,29 @@
+package org.cryptomator.crypto.aes256;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import org.cryptomator.crypto.Cryptor;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class CryptoModule {
+
+	@Provides
+	SecureRandom provideRandomNumberGenerator() {
+		try {
+			return SecureRandom.getInstanceStrong();
+		} catch (NoSuchAlgorithmException e) {
+			// quote "Every implementation of the Java platform is required to support at least one strong SecureRandom implementation."
+			throw new AssertionError("No SecureRandom implementation available.");
+		}
+	}
+
+	@Provides
+	public Cryptor provideCryptor(SecureRandom secureRandom) {
+		return new Aes256Cryptor(secureRandom);
+	}
+
+}

+ 10 - 4
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java

@@ -19,6 +19,7 @@ import java.util.Arrays;
 import javax.security.auth.DestroyFailedException;
 
 import org.apache.commons.io.IOUtils;
+import org.cryptomator.crypto.Cryptor;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.EncryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
@@ -29,13 +30,18 @@ import org.junit.Test;
 
 public class Aes256CryptorTest {
 
-	private final Aes256Cryptor cryptor;
+	private final Cryptor cryptor;
 
 	public Aes256CryptorTest() {
-		cryptor = new Aes256Cryptor();
+		cryptor = DaggerCryptoTestComponent.create().cryptor();
 		cryptor.randomizeMasterKey();
 	}
 
+	@Test
+	public void testMultipleCryptorInstances() {
+		Assert.assertNotSame(DaggerCryptoTestComponent.create().cryptor(), DaggerCryptoTestComponent.create().cryptor());
+	}
+
 	@Test(timeout = 10000)
 	public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
 		final String pw = "asd";
@@ -44,7 +50,7 @@ public class Aes256CryptorTest {
 		cryptor.encryptMasterKey(out, pw);
 		cryptor.destroy();
 
-		final Aes256Cryptor decryptor = new Aes256Cryptor();
+		final Cryptor decryptor = DaggerCryptoTestComponent.create().cryptor();
 		final InputStream in = new ByteArrayInputStream(out.toByteArray());
 		decryptor.decryptMasterKey(in, pw);
 
@@ -63,7 +69,7 @@ public class Aes256CryptorTest {
 
 		// all these passwords are expected to fail.
 		final String[] wrongPws = {"a", "as", "asdf", "sdf", "das", "dsa", "foo", "bar", "baz"};
-		final Aes256Cryptor decryptor = new Aes256Cryptor();
+		final Cryptor decryptor = DaggerCryptoTestComponent.create().cryptor();
 		for (final String wrongPw : wrongPws) {
 			final InputStream in = new ByteArrayInputStream(out.toByteArray());
 			try {

+ 15 - 0
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestComponent.java

@@ -0,0 +1,15 @@
+package org.cryptomator.crypto.aes256;
+
+import javax.inject.Singleton;
+
+import org.cryptomator.crypto.Cryptor;
+
+import dagger.Component;
+
+@Singleton
+@Component(modules = CryptoTestModule.class)
+public interface CryptoTestComponent {
+
+	Cryptor cryptor();
+
+}

+ 25 - 0
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestModule.java

@@ -0,0 +1,25 @@
+package org.cryptomator.crypto.aes256;
+
+import java.security.SecureRandom;
+
+import org.cryptomator.crypto.Cryptor;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class CryptoTestModule {
+
+	@Provides
+	@SuppressWarnings("deprecation")
+	SecureRandom provideRandomNumberGenerator() {
+		// we use this class for testing only, as unit tests on CI servers tend to stall, if they rely on true randomness.
+		return new InsecureRandomMock();
+	}
+
+	@Provides
+	Cryptor provideCryptor(SecureRandom secureRandom) {
+		return new Aes256Cryptor(secureRandom);
+	}
+
+}

+ 23 - 0
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/InsecureRandomMock.java

@@ -0,0 +1,23 @@
+package org.cryptomator.crypto.aes256;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * <b>DO NOT USE</b>
+ * 
+ * This class is for testing only.
+ */
+@Deprecated // marked as deprecated and made package-private inside /src/test/java to avoid accidential use.
+class InsecureRandomMock extends SecureRandom {
+
+	private static final long serialVersionUID = 1505563778398085504L;
+	private final Random random = new Random();
+
+	@Override
+	public void nextBytes(byte[] bytes) {
+		// let the deterministic RNG do the work:
+		this.random.nextBytes(bytes);
+	}
+
+}

+ 2 - 2
main/pom.xml

@@ -136,12 +136,12 @@
 			<dependency>
 				<groupId>com.google.dagger</groupId>
 				<artifactId>dagger</artifactId>
-				<version>2.0.1</version>
+				<version>2.0.2</version>
 			</dependency>
 			<dependency>
 				<groupId>com.google.dagger</groupId>
 				<artifactId>dagger-compiler</artifactId>
-				<version>2.0.1</version>
+				<version>2.0.2</version>
 				<scope>provided</scope>
 			</dependency>
 

+ 5 - 4
main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java

@@ -9,7 +9,7 @@ import javax.inject.Singleton;
 
 import org.cryptomator.crypto.Cryptor;
 import org.cryptomator.crypto.SamplingCryptorDecorator;
-import org.cryptomator.crypto.aes256.Aes256Cryptor;
+import org.cryptomator.crypto.aes256.CryptoModule;
 import org.cryptomator.ui.model.VaultObjectMapperProvider;
 import org.cryptomator.ui.settings.Settings;
 import org.cryptomator.ui.settings.SettingsProvider;
@@ -26,7 +26,7 @@ import dagger.Module;
 import dagger.Provides;
 import javafx.application.Application;
 
-@Module
+@Module(includes = CryptoModule.class)
 class CryptomatorModule {
 
 	private final Application application;
@@ -90,8 +90,9 @@ class CryptomatorModule {
 	}
 
 	@Provides
-	Cryptor provideCryptor() {
-		return SamplingCryptorDecorator.decorate(new Aes256Cryptor());
+	@Named("SamplingCryptor")
+	Cryptor provideCryptor(Cryptor cryptor) {
+		return SamplingCryptorDecorator.decorate(cryptor);
 	}
 
 	private <T> T closeLater(T object, Closer<T> closer) {

+ 5 - 2
main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java

@@ -9,6 +9,7 @@
 package org.cryptomator.ui.controllers;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -25,6 +26,7 @@ import org.apache.commons.httpclient.HttpMethod;
 import org.apache.commons.httpclient.HttpStatus;
 import org.apache.commons.httpclient.cookie.CookiePolicy;
 import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.ui.settings.Settings;
 import org.slf4j.Logger;
@@ -140,8 +142,9 @@ public class WelcomeController extends AbstractFXMLViewController {
 		client.getParams().setConnectionManagerTimeout(5000);
 		try {
 			client.executeMethod(method);
-			if (method.getStatusCode() == HttpStatus.SC_OK) {
-				final byte[] responseData = method.getResponseBody();
+			final InputStream responseBodyStream = method.getResponseBodyAsStream();
+			if (method.getStatusCode() == HttpStatus.SC_OK && responseBodyStream != null) {
+				final byte[] responseData = IOUtils.toByteArray(responseBodyStream);
 				final ObjectMapper mapper = new ObjectMapper();
 				final Map<String, String> map = mapper.readValue(responseData, new TypeReference<HashMap<String, String>>() {
 				});

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java

@@ -3,6 +3,7 @@ package org.cryptomator.ui.model;
 import java.nio.file.Path;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.inject.Provider;
 import javax.inject.Singleton;
 
@@ -20,7 +21,7 @@ public class VaultFactory {
 	private final DeferredCloser closer;
 
 	@Inject
-	public VaultFactory(WebDavServer server, Provider<Cryptor> cryptorProvider, WebDavMounter mounter, DeferredCloser closer) {
+	public VaultFactory(WebDavServer server, @Named("SamplingCryptor") Provider<Cryptor> cryptorProvider, WebDavMounter mounter, DeferredCloser closer) {
 		this.server = server;
 		this.cryptorProvider = cryptorProvider;
 		this.mounter = mounter;