/*
* Copyright 2017 ThoughtWorks, Inc.
*
* Licensed 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 com.thoughtworks.go.domain.materials.svn;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;
import com.thoughtworks.go.config.materials.svn.SvnMaterial;
import com.thoughtworks.go.config.materials.svn.SvnMaterialConfig;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.RevisionContext;
import com.thoughtworks.go.domain.materials.TestSubprocessExecutionContext;
import com.thoughtworks.go.helper.MaterialConfigsMother;
import com.thoughtworks.go.helper.MaterialsMother;
import com.thoughtworks.go.security.GoCipher;
import com.thoughtworks.go.util.ClassMockery;
import com.thoughtworks.go.util.JsonValue;
import com.thoughtworks.go.util.ReflectionUtil;
import com.thoughtworks.go.util.TestFileUtil;
import com.thoughtworks.go.util.command.InMemoryStreamConsumer;
import com.thoughtworks.go.util.command.UrlArgument;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.hamcrest.Matchers;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static com.thoughtworks.go.util.JsonUtils.from;
import static com.thoughtworks.go.util.command.ProcessOutputStreamConsumer.inMemoryConsumer;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(JMock.class)
public class SvnMaterialTest {
private Mockery context = new ClassMockery();
private Subversion subversion;
private SvnMaterial svnMaterial;
private static final String URL = "svn://something";
SubversionRevision revision = new SubversionRevision("1");
private final ArrayList<File> tempFiles = new ArrayList<>();
private InMemoryStreamConsumer outputStreamConsumer = inMemoryConsumer();
@Before
public void setUp() {
subversion = context.mock(Subversion.class);
context.checking(new Expectations() {
{
allowing(subversion).getUrl();
will(returnValue(new UrlArgument(URL)));
allowing(subversion).getPassword();
will(returnValue(""));
allowing(subversion).getUserName();
will(returnValue(""));
allowing(subversion).isCheckExternals();
will(returnValue(false));
}
});
svnMaterial = SvnMaterial.createSvnMaterialWithMock(subversion);
svnMaterial.setUrl(URL);
}
@After
public void tearDown() throws Exception {
for (File tempFile : tempFiles) {
tempFile.delete();
}
}
private File createSvnWorkingCopy(boolean withDotSvnFolder) {
File folder = TestFileUtil.createTempFolder("testSvnWorkingCopy");
if (withDotSvnFolder) {
File dotSvnFolder = new File(folder, ".svn");
dotSvnFolder.mkdir();
tempFiles.add(dotSvnFolder);
}
tempFiles.add(folder);
return folder;
}
@Test
public void shouldNotDisplayPasswordInStringRepresentation() {
SvnMaterial svn = new SvnMaterial("my-url", "user", "loser", false);
assertThat(svn.toString(), not(containsString("loser")));
svn = new SvnMaterial("https://user:loser@foo.bar/baz?quux=bang", "user", "loser", false);
assertThat(svn.toString(), not(containsString("loser")));
}
@Test
public void shouldCheckoutWhenFolderDoesNotExist() {
final File workingCopy = new File("xyz");
context.checking(new Expectations() {
{
one(subversion).checkoutTo(outputStreamConsumer, workingCopy, revision);
}
});
updateMaterial(svnMaterial, revision, workingCopy);
}
@Test
public void shouldLogRepoInfoToConsoleOutWithOutFolder() throws Exception {
final File workingCopy = new File("xyz");
context.checking(new Expectations() {
{
one(subversion).checkoutTo(outputStreamConsumer, workingCopy, revision);
}
});
updateMaterial(svnMaterial, revision, workingCopy);
String stdout = outputStreamConsumer.getStdOut();
assertThat(stdout, containsString(
String.format("Start updating %s at revision %s from %s", "files", revision.getRevision(),
svnMaterial.getUrl())));
}
@Test
public void shouldCheckoutForInvalidSvnWorkingCopy() {
final File workingCopy = createSvnWorkingCopy(false);
context.checking(new Expectations() {
{
one(subversion).checkoutTo(outputStreamConsumer, workingCopy, revision);
}
});
updateMaterial(svnMaterial, revision, workingCopy);
assertThat(workingCopy.exists(), is(false));
}
private void updateMaterial(SvnMaterial svnMaterial, SubversionRevision revision, File workingCopy) {
svnMaterial.updateTo(outputStreamConsumer, workingCopy, new RevisionContext(revision), new TestSubprocessExecutionContext());
}
@Test
public void shouldCheckoutIfSvnRepositoryChanged() throws IOException {
final File workingCopy = createSvnWorkingCopy(true);
context.checking(new Expectations() {
{
one(subversion).workingRepositoryUrl(workingCopy);
will(returnValue("new url"));
one(subversion).checkoutTo(outputStreamConsumer, workingCopy, revision);
}
});
updateMaterial(svnMaterial, revision, workingCopy);
assertThat(workingCopy.exists(), is(false));
}
@Test
public void shouldUpdateForValidSvnWorkingCopy() throws IOException {
final File workingCopy = createSvnWorkingCopy(true);
context.checking(new Expectations() {
{
one(subversion).workingRepositoryUrl(workingCopy);
will(returnValue(URL));
one(subversion).cleanupAndRevert(outputStreamConsumer, workingCopy);
one(subversion).updateTo(outputStreamConsumer, workingCopy, revision);
}
});
updateMaterial(svnMaterial, revision, workingCopy);
}
@Test
public void shouldBeEqualWhenUrlSameForSvnMaterial() throws Exception {
final Material material1 = MaterialsMother.defaultSvnMaterialsWithUrl("url1").get(0);
final Material material = MaterialsMother.defaultSvnMaterialsWithUrl("url1").get(0);
assertComplementaryEquals(material1, material, true);
}
@Test
public void shouldNotBeEqualWhenUrlDifferent() throws Exception {
final Material material1 = MaterialsMother.defaultSvnMaterialsWithUrl("url1").get(0);
final Material material2 = MaterialsMother.defaultSvnMaterialsWithUrl("url2").get(0);
assertComplementaryEquals(material1, material2, false);
}
@Test
public void shouldNotBeEqualWhenTypeDifferent() throws Exception {
final Material hgMaterial = MaterialsMother.hgMaterials("url1", "hgdir").get(0);
final Material nonHgMaterial = MaterialsMother.defaultSvnMaterialsWithUrl("url1").get(0);
assertComplementaryEquals(hgMaterial, nonHgMaterial, false);
}
@Test
public void shouldNotBeEqualWhenAlternateFolderDifferent() throws Exception {
final SvnMaterial material1 = MaterialsMother.svnMaterial("url1");
final SvnMaterial material2 = MaterialsMother.svnMaterial("url1");
assertComplementaryEquals(material1, material2, true);
material1.setFolder("foo");
material2.setFolder(null);
assertComplementaryEquals(material1, material2, false);
material1.setFolder("foo");
material2.setFolder("bar");
assertComplementaryEquals(material1, material2, false);
}
@Test
public void shouldSerializeAndDeserializeCorrectly() throws Exception {
final SvnMaterial material1 = MaterialsMother.svnMaterial("url1", "foo");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream serialized = new ObjectOutputStream(buf);
serialized.writeObject(material1);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
assertThat(in.readObject(), is(material1));
}
@Test
public void shouldReturnNotEqualsWhenUrlIsChanged() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial("A");
SvnMaterial other = MaterialsMother.svnMaterial("B");
assertThat(material, is(not(other)));
}
@Test
public void shouldReturnNotEqualsWhenUserNameIsChanged() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial("url", "svnDir", "userName", null, false, "*.txt");
SvnMaterial other = MaterialsMother.svnMaterial("url", "svnDir", "userName1", null, false, "*.txt");
assertThat(material, is(not(other)));
}
@Test
public void shouldReturnEqualsEvenIfPasswordsAreDifferent() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial();
material.setPassword("password");
SvnMaterial other = MaterialsMother.svnMaterial();
other.setPassword("password1");
assertThat(material, is(other));
}
@Test
public void shouldReturnNotEqualsWhenCheckExternalsIsChanged() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial("url", "svnDir", null, null, true, "*.txt");
SvnMaterial other = MaterialsMother.svnMaterial("url", "svnDir", null, null, false, "*.txt");
assertThat(material, is(not(other)));
}
@Test
public void shouldReturnEqualsWhenEverythingIsSame() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial("URL", "dummy-folder", "userName", "password", true, "*.doc");
SvnMaterial other = MaterialsMother.svnMaterial("URL", "dummy-folder", "userName", "password", true, "*.doc");
assertThat(other, is(material));
}
/* TODO: *SBD* Move this test into SvnMaterialConfig test after mothers are moved. */
@Test
public void shouldReturnEqualsWhenEverythingIsSameForSvnMaterialConfigs() throws Exception {
SvnMaterialConfig svnMaterialConfig = MaterialConfigsMother.svnMaterialConfig();
svnMaterialConfig.setConfigAttributes(Collections.singletonMap(SvnMaterialConfig.CHECK_EXTERNALS, String.valueOf(true)));
svnMaterialConfig.setConfigAttributes(Collections.singletonMap(SvnMaterialConfig.USERNAME, "userName"));
svnMaterialConfig.setPassword("password");
svnMaterialConfig.setConfigAttributes(Collections.singletonMap(SvnMaterialConfig.URL, "URL"));
SvnMaterialConfig other = MaterialConfigsMother.svnMaterialConfig();
other.setConfigAttributes(Collections.singletonMap(SvnMaterialConfig.CHECK_EXTERNALS, String.valueOf(true)));
other.setConfigAttributes(Collections.singletonMap(SvnMaterialConfig.USERNAME, "userName"));
other.setPassword("password");
other.setConfigAttributes(Collections.singletonMap(SvnMaterialConfig.URL, "URL"));
assertThat(other, is(svnMaterialConfig));
}
@Test
public void shouldBeAbleToConvertToJson() {
SvnMaterial material = MaterialsMother.svnMaterial("url");
Map<String, Object> json = new LinkedHashMap<>();
material.toJson(json, revision);
JsonValue jsonValue = from(json);
assertThat(jsonValue.getString("scmType"), is("Subversion"));
assertThat(new File(jsonValue.getString("location")), is(new File(material.getUrl())));
assertThat(jsonValue.getString("action"), is("Modified"));
}
@Test
public void shouldAddTheForwardSlashAndApplyThePattern() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial();
assertThat(material.matches("/a.doc", "a.doc"), is(true));
assertThat(material.matches("a.doc", "a.doc"), is(false));
}
@Test
public void shouldApplyThePatternDirectly() throws Exception {
SvnMaterial material = MaterialsMother.svnMaterial();
assertThat(material.matches("/a.doc", "/a.doc"), is(true));
}
@Test
public void shouldGenerateSqlCriteriaMapInSpecificOrder() throws Exception {
SvnMaterial material = new SvnMaterial("url", "username", "password", true);
Map<String, Object> map = material.getSqlCriteria();
assertThat(map.size(), is(4));
Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
assertThat(iter.next().getKey(), is("type"));
assertThat(iter.next().getKey(), is("url"));
assertThat(iter.next().getKey(), is("username"));
assertThat(iter.next().getKey(), is("checkExternals"));
}
@Test
public void shouldGenerateFingerprintBasedOnSqlCriteria() throws Exception {
SvnMaterial one = new SvnMaterial("url", "username", "password", true);
SvnMaterial two = new SvnMaterial("url", "username", "password", false);
assertThat(one.getFingerprint(), is(Matchers.not(two.getFingerprint())));
assertThat(one.getFingerprint(), is(DigestUtils.sha256Hex("type=SvnMaterial<|>url=url<|>username=username<|>checkExternals=true")));
}
@Test
public void shouldGeneratePipelineUniqueFingerprintBasedOnFingerprintAndDest() throws Exception {
SvnMaterial one = new SvnMaterial("url", "username", "password", true, "folder1");
SvnMaterial two = new SvnMaterial("url", "username", "password", true, "folder2");
assertThat(one.getPipelineUniqueFingerprint(), is(Matchers.not(two.getFingerprint())));
assertThat(one.getPipelineUniqueFingerprint(), is(DigestUtils.sha256Hex("type=SvnMaterial<|>url=url<|>username=username<|>checkExternals=true<|>dest=folder1")));
}
@Test
public void shouldNotUsePasswordForEquality() {
SvnMaterial svnBoozer = new SvnMaterial("foo.com", "loser", "boozer", true);
SvnMaterial svnZooser = new SvnMaterial("foo.com", "loser", "zooser", true);
assertThat(svnBoozer.hashCode(), is(svnZooser.hashCode()));
assertThat(svnBoozer, is(svnZooser));
}
@Test
public void shouldEncryptSvnPasswordAndMarkPasswordAsNull() throws Exception {
GoCipher mockGoCipher = mock(GoCipher.class);
when(mockGoCipher.encrypt("password")).thenReturn("encrypted");
SvnMaterial material = new SvnMaterial("/foo", "username", "password", false, mockGoCipher);
material.ensureEncrypted();
assertThat(material.getPassword(), is(nullValue()));
assertThat(material.getEncryptedPassword(), is("encrypted"));
}
@Test
public void shouldDecryptSvnPassword() throws Exception {
GoCipher mockGoCipher = mock(GoCipher.class);
when(mockGoCipher.decrypt("encrypted")).thenReturn("password");
SvnMaterial material = new SvnMaterial("/foo", "username", null, false, mockGoCipher);
ReflectionUtil.setField(material, "encryptedPassword", "encrypted");
material.ensureEncrypted();
assertThat(material.getPassword(), is("password"));
}
@Test
public void shouldNotDecryptSvnPasswordIfPasswordIsNotNull() throws Exception {
GoCipher mockGoCipher = mock(GoCipher.class);
when(mockGoCipher.encrypt("password")).thenReturn("encrypted");
when(mockGoCipher.decrypt("encrypted")).thenReturn("password");
SvnMaterial material = new SvnMaterial("/foo", "username", "password", false, mockGoCipher);
material.ensureEncrypted();
when(mockGoCipher.encrypt("new_password")).thenReturn("new_encrypted");
material.setPassword("new_password");
when(mockGoCipher.decrypt("new_encrypted")).thenReturn("new_password");
assertThat(material.getPassword(), is("new_password"));
}
@Test
public void shouldErrorOutIfDecryptionFails() throws InvalidCipherTextException {
GoCipher mockGoCipher = mock(GoCipher.class);
String fakeCipherText = "fake cipher text";
when(mockGoCipher.decrypt(fakeCipherText)).thenThrow(new InvalidCipherTextException("exception"));
SvnMaterial material = new SvnMaterial("/foo", "username", null, false, mockGoCipher);
ReflectionUtil.setField(material, "encryptedPassword", fakeCipherText);
try {
material.getPassword();
fail("Should have thrown up");
}
catch (Exception e) {
assertThat(e.getMessage(), is("Could not decrypt the password to get the real password"));
}
}
@Test
public void shouldErrorOutIfEncryptionFails() throws Exception {
GoCipher mockGoCipher = mock(GoCipher.class);
when(mockGoCipher.encrypt("password")).thenThrow(new InvalidCipherTextException("exception"));
try {
new SvnMaterial("/foo", "username", "password", false, mockGoCipher);
fail("Should have thrown up");
}
catch (Exception e) {
assertThat(e.getMessage(), is("Password encryption failed. Please verify your cipher key."));
}
}
@Test
public void shouldGetLongDescriptionForMaterial(){
SvnMaterial material = new SvnMaterial("http://url/", "user", "password", true, "folder");
assertThat(material.getLongDescription(), is("URL: http://url/, Username: user, CheckExternals: true"));
}
@Test
public void shouldCopyOverPasswordWhenConvertingToConfig() throws Exception {
SvnMaterial material = new SvnMaterial("abc", "def", "ghi", false);
SvnMaterialConfig config = (SvnMaterialConfig) material.config();
assertThat(config.getEncryptedPassword(), is(not(Matchers.nullValue())));
assertThat(config.getPassword(), is("ghi"));
}
private void assertComplementaryEquals(Object o1, Object o2, boolean value) {
assertThat(o1.equals(o2), is(value));
assertThat(o2.equals(o1), is(value));
}
@Test
public void shouldGetAttributesWithSecureFields() {
SvnMaterial material = new SvnMaterial("http://username:password@svnrepo.com", "user", "password", true);
Map<String, Object> attributes = material.getAttributes(true);
assertThat(attributes.get("type"), is("svn"));
Map<String, Object> configuration = (Map<String, Object>) attributes.get("svn-configuration");
assertThat(configuration.get("url"), is("http://username:password@svnrepo.com"));
assertThat(configuration.get("username"), is("user"));
assertThat(configuration.get("password"), is("password"));
assertThat(configuration.get("check-externals"), is(true));
}
@Test
public void shouldGetAttributesWithoutSecureFields() {
SvnMaterial material = new SvnMaterial("http://username:password@svnrepo.com", "user", "password", true);
Map<String, Object> attributes = material.getAttributes(false);
assertThat(attributes.get("type"), is("svn"));
Map<String, Object> configuration = (Map<String, Object>) attributes.get("svn-configuration");
assertThat(configuration.get("url"), is("http://username:******@svnrepo.com"));
assertThat(configuration.get("username"), is("user"));
assertThat(configuration.get("password"), is(nullValue()));
assertThat(configuration.get("check-externals"), is(true));
}
}