package hudson.plugins.tfs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec;
import com.thoughtworks.xstream.XStream;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.ParametersAction;
import hudson.plugins.tfs.model.Project;
import hudson.util.Secret;
import hudson.util.SecretOverride;
import hudson.util.XStream2;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
@SuppressWarnings("unchecked")
public class TeamFoundationServerScmTest {
private FilePath workspace;
@After public void tearDown() throws Exception {
if (workspace != null) {
workspace.deleteRecursive();
workspace = null;
}
}
/**
Up until version 3.1.1, the plugin used to store the password in a base64-encoded string.
As of bd98b91ea614c307a6bb1e0af36d9dd2a5646e29, an encrypted version of the password is stored.
This test makes sure a job can be upgraded without loss of the password.
*/
@Test public void upgradeFromScrambledPassword() {
SecretOverride secretOverride = null;
try {
secretOverride = new SecretOverride();
final String xmlString =
"<scm class='hudson.plugins.tfs.TeamFoundationServerScm' plugin='tfs@3.1.1'>\n" +
" <serverUrl>http://example.tfs.server.invalid:8080/tfs</serverUrl>\n" +
" <projectPath>$/example/path</projectPath>\n" +
" <localPath>.</localPath>\n" +
" <workspaceName>Hudson-${JOB_NAME}-${NODE_NAME}</workspaceName>\n" +
" <userPassword>ZXhhbXBsZVBhc3N3b3Jk</userPassword>\n" +
" <userName>example\\tfsbuilder</userName>\n" +
" <useUpdate>false</useUpdate>\n" +
"</scm>";
final XStream serializer = new XStream2();
final TeamFoundationServerScm tfsScmObject = (TeamFoundationServerScm) serializer.fromXML(xmlString);
final String actual = tfsScmObject.getUserPassword();
assertEquals("examplePassword", actual);
assertEquals("examplePassword", Secret.toString(tfsScmObject.getPassword()));
final String expectedUpgradedXml =
"<hudson.plugins.tfs.TeamFoundationServerScm>\n" +
" <serverUrl>http://example.tfs.server.invalid:8080/tfs</serverUrl>\n" +
" <projectPath>$/example/path</projectPath>\n" +
" <localPath>.</localPath>\n" +
" <workspaceName>Hudson-${JOB_NAME}-${NODE_NAME}</workspaceName>\n" +
" <password>GZ3wK9L4iJXsMwXnJ4NieiVpOlxj0AVrthfe7MIr9w0=</password>\n" +
" <userName>example\\tfsbuilder</userName>\n" +
" <credentialsConfigurer class=\"hudson.plugins.tfs.model.ManualCredentialsConfigurer\"/>\n" +
" <useUpdate>false</useUpdate>\n" +
"</hudson.plugins.tfs.TeamFoundationServerScm>";
final String actualUpgradedXml = serializer.toXML(tfsScmObject);
assertEquals(expectedUpgradedXml, actualUpgradedXml);
final TeamFoundationServerScm tfsScmObject2 = (TeamFoundationServerScm) serializer.fromXML(actualUpgradedXml);
final String actual2 = tfsScmObject2.getUserPassword();
assertEquals("examplePassword", actual2);
assertEquals("examplePassword", Secret.toString(tfsScmObject.getPassword()));
}
finally {
if (secretOverride != null) {
try {
secretOverride.close();
} catch (IOException e) {
// ignore
}
}
}
}
@Test
public void assertWorkspaceNameReplacesJobName() {
AbstractBuild build = mock(AbstractBuild.class);
AbstractProject project = mock(AbstractProject.class);
when(build.getProject()).thenReturn(project);
when(project.getName()).thenReturn("ThisIsAJob");
TeamFoundationServerScm scm = new TeamFoundationServerScm(null, null, "erik_${JOB_NAME}");
assertEquals("Workspace name was incorrect", "erik_ThisIsAJob", scm.getWorkspaceName(build, mock(Computer.class)));
}
@Test
public void assertDoProjectPathCheckRegexWorks() {
assertFalse("Project path regex matched an invalid project path", TeamFoundationServerScm.DescriptorImpl.PROJECT_PATH_REGEX.matcher("tfsandbox").matches());
assertFalse("Project path regex matched an invalid project path", TeamFoundationServerScm.DescriptorImpl.PROJECT_PATH_REGEX.matcher("tfsandbox/with/sub/pathes").matches());
assertFalse("Project path regex matched an invalid project path", TeamFoundationServerScm.DescriptorImpl.PROJECT_PATH_REGEX.matcher("tfsandbox$/with/sub/pathes").matches());
assertTrue("Project path regex did not match a valid project path", TeamFoundationServerScm.DescriptorImpl.PROJECT_PATH_REGEX.matcher("$/tfsandbox").matches());
assertTrue("Project path regex did not match a valid project path", TeamFoundationServerScm.DescriptorImpl.PROJECT_PATH_REGEX.matcher("$/tfsandbox/path with space/subpath").matches());
}
@Test
public void assertDoWorkspaceNameCheckRegexWorks() {
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work space ").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work.space.").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work*space").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work/space").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work\"space").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("workspace*").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("workspace/").matches());
assertFalse("Workspace name regex matched an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("workspace\"").matches());
assertTrue("Workspace name regex dit not match an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work.space").matches());
assertTrue("Workspace name regex dit not match an invalid workspace name", TeamFoundationServerScm.DescriptorImpl.WORKSPACE_NAME_REGEX.matcher("work space").matches());
}
@Test
public void assertCloakedPathsCheckRegexWorks() {
final String shouldNotMatch = "Cloaked paths regex matched an invalid cloaked path";
assertFalse(shouldNotMatch, isCloakedPathValid("tfsandbox"));
assertFalse(shouldNotMatch, isCloakedPathValid("tfsandbox/with/sub/pathes"));
assertFalse(shouldNotMatch, isCloakedPathValid("tfsandbox$/with/sub/pathes"));
assertFalse(shouldNotMatch, isCloakedPathValid("$/tfsandbox/path1;$/tfsandbox/path2"));
assertFalse(shouldNotMatch, isCloakedPathValid("$/tfsandbox/path1 ; $/tfsandbox/path2 ; $/tfsandbox/path3"));
assertFalse(shouldNotMatch, isCloakedPathValid("$/foo/;$/bar/;$/baz/"));
assertFalse(shouldNotMatch, isCloakedPathValid("$/foo/;\n$/bar/;\n$/baz/"));
final String shoudMatch = "Cloaked paths regex did not match a valid cloaked path";
assertTrue(shoudMatch, isCloakedPathValid("$/tfsandbox"));
assertTrue(shoudMatch, isCloakedPathValid("$/tfsandbox/path with space/subpath"));
assertTrue(shoudMatch, isCloakedPathValid("$/tfsandbox/with/${parameter}/path"));
assertTrue(shoudMatch, isCloakedPathValid("$/foo/\n$/bar/\n$/baz/"));
assertTrue(shoudMatch, isCloakedPathValid(" $/foo/ \n $/bar/ \n $/baz/ "));
assertTrue(shoudMatch, isCloakedPathValid("\n$/foo/\n\n$/bar/\n\n$/baz/\n"));
}
private static boolean isCloakedPathValid(final String path) {
return TeamFoundationServerScm.DescriptorImpl.CLOAKED_PATHS_REGEX.matcher(path).matches();
}
@Test
public void serializeCloakedPathCollectionToString_one() {
final List<String> cloakedPaths = Collections.singletonList("$/foo");
final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths);
Assert.assertEquals("$/foo", actual);
}
@Test
public void serializeCloakedPathCollectionToString_two() {
final List<String> cloakedPaths = Arrays.asList("$/foo", "$/bar");
final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths);
Assert.assertEquals("$/foo\n$/bar", actual);
}
@Test
public void serializeCloakedPathCollectionToString_many() {
final List<String> cloakedPaths = Arrays.asList("$/foo/", "$/bar/", "$/baz/");
final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths);
Assert.assertEquals("$/foo/\n$/bar/\n$/baz/", actual);
}
@Test
public void splitCloakedPaths_one() {
final String input = "$/foo/";
final Collection<String> actual = TeamFoundationServerScm.splitCloakedPaths(input);
areEqual(actual, input);
}
@Test
public void splitCloakedPaths_newlinesMany() {
final String input = "$/foo/\n$/bar/\n$/baz/";
final Collection<String> actual = TeamFoundationServerScm.splitCloakedPaths(input);
areEqual(actual, "$/foo/", "$/bar/", "$/baz/");
}
@Test
public void splitCloakedPaths_newlinesWithLiberalSpacing() {
final String input = " $/foo/ \n $/bar/ \n $/baz/ ";
final Collection<String> actual = TeamFoundationServerScm.splitCloakedPaths(input);
areEqual(actual, "$/foo/", "$/bar/", "$/baz/");
}
@Test
public void splitCloakedPaths_newlinesWithBlankLines() {
final String input = "\n$/foo/\n\n$/bar/\n\n$/baz/\n";
final Collection<String> actual = TeamFoundationServerScm.splitCloakedPaths(input);
areEqual(actual, "$/foo/", "$/bar/", "$/baz/");
}
private static <T> void areEqual(final Collection<T> actual, T... expected) {
final Iterator<T> ai = actual.iterator();
int ei = 0;
while (ei < expected.length && ai.hasNext()) {
final T expectedItem = expected[ei];
final T actualItem = ai.next();
Assert.assertEquals(expectedItem, actualItem);
ei++;
}
if (ei == expected.length) {
if (ai.hasNext()) {
Assert.fail("There were more elements than expected");
}
}
else {
if (!ai.hasNext()) {
Assert.fail("Some elements were missing from actual.");
}
}
}
@Test
public void assertDefaultValueIsUsedForNullLocalPath() {
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "workspace");
assertEquals("Default value for work folder was incorrect", ".", scm.getLocalPath());
}
@Test
public void assertDefaultValueIsUsedForEmptyLocalPath() {
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "workspace");
scm.setLocalPath("");
assertEquals("Default value for work folder was incorrect", ".", scm.getLocalPath());
}
@Test
public void assertDefaultValueIsUsedForEmptyWorkspaceName() {
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "");
assertEquals("Default value for workspace was incorrect", "Hudson-${JOB_NAME}-${NODE_NAME}", scm.getWorkspaceName());
}
@Test
public void assertGetModuleRootReturnsWorkFolder() throws Exception {
workspace = Util.createTempFilePath();
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "");
scm.setLocalPath("workfolder");
FilePath moduleRoot = scm.getModuleRoot(workspace);
assertEquals("Name for module root was incorrect", "workfolder", moduleRoot.getName());
assertEquals("The parent for module root was incorrect", workspace.getName(), moduleRoot.getParent().getName());
}
@Test
public void assertGetModuleRootWorksForDotWorkFolder() throws Exception {
workspace = Util.createTempFilePath();
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "");
FilePath moduleRoot = scm.getModuleRoot(workspace);
assertTrue("The module root was reported as not existing even if its virtually the same as workspace",
moduleRoot.exists());
assertEquals("The module root was not the same as workspace", moduleRoot.lastModified(), workspace.lastModified());
}
@Test
public void assertWorkspaceNameIsAddedToEnvVars() throws Exception {
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "WORKSPACE_SAMPLE");
AbstractBuild build = mock(AbstractBuild.class);
AbstractProject project = mock(AbstractProject.class);
when(build.getProject()).thenReturn(project);
scm.getWorkspaceName(build, mock(Computer.class));
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(build, env);
assertEquals("The workspace name was incorrect", "WORKSPACE_SAMPLE", env.get(TeamFoundationServerScm.WORKSPACE_ENV_STR));
}
private TeamFoundationServerScm createForEnvVars() {
TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "WORKSPACE_SAMPLE", "user", null);
scm.setLocalPath("PATH");
return scm;
}
@Test
public void assertWorksfolderPathIsAddedToEnvVars() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
Map<String, String> env = new HashMap<String, String>();
env.put("WORKSPACE", "/this/is/a");
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("The workfolder path was incorrect", "/this/is/a" + File.separator + "PATH", env.get(TeamFoundationServerScm.WORKFOLDER_ENV_STR));
}
@Test
public void assertProjectPathIsAddedToEnvVars() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("The project path was incorrect", "projectpath", env.get(TeamFoundationServerScm.PROJECTPATH_ENV_STR));
}
@Test
public void assertServerUrlIsAddedToEnvVars() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("The server URL was incorrect", "serverurl", env.get(TeamFoundationServerScm.SERVERURL_ENV_STR));
}
@Test
public void assertTfsUserNameIsAddedToEnvVars() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("The TFS user name was incorrect", "user", env.get(TeamFoundationServerScm.USERNAME_ENV_STR));
}
@Test
public void assertTfsWorkspaceChangesetIsAddedToEnvVars() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
scm.setWorkspaceChangesetVersion("12345");
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("Workspace changeset version was incorrect", "12345", env.get(TeamFoundationServerScm.WORKSPACE_CHANGESET_ENV_STR));
}
@Test
public void assertTfsWorkspaceChangesetIsNotAddedToEnvVarsIfEmpty() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
scm.setWorkspaceChangesetVersion("");
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("Workspace changeset version was not null", null, env.get(TeamFoundationServerScm.WORKSPACE_CHANGESET_ENV_STR));
}
@Test
public void assertTfsWorkspaceChangesetIsNotAddedToEnvVarsIfNull() throws Exception {
final TeamFoundationServerScm scm = createForEnvVars();
scm.setWorkspaceChangesetVersion(null);
Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(mock(AbstractBuild.class), env );
assertEquals("Workspace changeset version was not null", null, env.get(TeamFoundationServerScm.WORKSPACE_CHANGESET_ENV_STR));
}
@Test public void recordWorkspaceChangesetVersion() throws Exception {
final TeamFoundationServerScm scm = new TeamFoundationServerScm("serverUrl", "projectPath", "workspace");
scm.setLocalPath("localPath");
final AbstractBuild build = mock(AbstractBuild.class);
when(build.getTimestamp()).thenReturn(new GregorianCalendar(2015, 03, 28, 22, 04));
final BuildListener listener = null;
final Project project = mock(Project.class);
when(project.getRemoteChangesetVersion(isA(VersionSpec.class))).thenReturn(42);
final String projectPath = "projectPath";
final String singleVersionSpec = null;
final int actual = scm.recordWorkspaceChangesetVersion(build, listener, project, projectPath, singleVersionSpec);
Assert.assertEquals(42, actual);
final Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(build, env);
assertEquals("42", env.get(TeamFoundationServerScm.WORKSPACE_CHANGESET_ENV_STR));
}
@Test public void recordWorkspaceChangesetVersionWithSingleVersionSpec() throws Exception {
final TeamFoundationServerScm scm = new TeamFoundationServerScm("serverUrl", "projectPath", "workspace");
scm.setLocalPath("localPath");
final AbstractBuild build = mock(AbstractBuild.class);
final BuildListener listener = null;
final Project project = mock(Project.class);
when(project.getRemoteChangesetVersion(isA(VersionSpec.class))).thenReturn(42);
final String projectPath = "projectPath";
final String singleVersionSpec = "Lfoo";
final int actual = scm.recordWorkspaceChangesetVersion(build, listener, project, projectPath, singleVersionSpec);
Assert.assertEquals(42, actual);
final Map<String, String> env = new HashMap<String, String>();
scm.buildEnvVars(build, env);
assertEquals("42", env.get(TeamFoundationServerScm.WORKSPACE_CHANGESET_ENV_STR));
}
/**
* Workspace name must be less than 64 characters, cannot end with a space or period, and cannot contain any of the following characters: "/:<>|*?
*/
@Test
public void assertWorkspaceNameReplacesInvalidChars() {
TeamFoundationServerScm scm = new TeamFoundationServerScm(null, null, "A\"B/C:D<E>F|G*H?I");
assertEquals("Workspace name contained invalid chars", "A_B_C_D_E_F_G_H_I", scm.getWorkspaceName(null, null));
}
/**
* Workspace name must be less than 64 characters, cannot end with a space or period, and cannot contain any of the following characters: "/:<>|*?
*/
@Test
public void assertWorkspaceNameReplacesEndingPeriod() {
TeamFoundationServerScm scm = new TeamFoundationServerScm(null, null, "Workspace.Name.");
assertEquals("Workspace name ends with period", "Workspace.Name_", scm.getWorkspaceName(null, null));
}
/**
* Workspace name must be less than 64 characters, cannot end with a space or period, and cannot contain any of the following characters: "/:<>|*?
*/
@Test
public void assertWorkspaceNameReplacesEndingSpace() {
TeamFoundationServerScm scm = new TeamFoundationServerScm(null, null, "Workspace Name ");
assertEquals("Workspace name ends with space", "Workspace Name_", scm.getWorkspaceName(null, null));
}
@Test public void assertServerUrlResolvesBuildVariables() {
ParametersAction action = mock(ParametersAction.class);
when(action.substitute(isA(AbstractBuild.class), isA(String.class))).thenReturn("https://RESOLVED.com");
AbstractBuild build = mock(AbstractBuild.class);
when(build.getAction(ParametersAction.class)).thenReturn(action);
TeamFoundationServerScm scm = new TeamFoundationServerScm("https://${PARAM}.com", null, "");
assertEquals("The server url wasnt resolved", "https://RESOLVED.com", scm.getServerUrl(build));
}
@Test public void assertProjectPathResolvesBuildVariables() {
ParametersAction action = mock(ParametersAction.class);
when(action.substitute(isA(AbstractBuild.class), isA(String.class))).thenReturn("$/RESOLVED/path");
AbstractBuild build = mock(AbstractBuild.class);
when(build.getAction(ParametersAction.class)).thenReturn(action);
TeamFoundationServerScm scm = new TeamFoundationServerScm(null, "$/$PARAM/path", "");
assertEquals("The project path wasnt resolved", "$/RESOLVED/path", scm.getProjectPath(build));
}
@Test public void assertWorkspaceNameResolvesBuildVariables() {
ParametersAction action = mock(ParametersAction.class);
when(action.substitute(isA(AbstractBuild.class), isA(String.class))).thenReturn("WS-RESOLVED");
AbstractBuild build = mock(AbstractBuild.class);
when(build.getAction(ParametersAction.class)).thenReturn(action);
TeamFoundationServerScm scm = new TeamFoundationServerScm(null, null, "WS-${PARAM}");
assertEquals("The workspace name wasnt resolved", "WS-RESOLVED", scm.getWorkspaceName(build, mock(Computer.class)));
}
@Test public void assertTfsWorkspaceIsntRemovedIfThereIsNoBuildWhenProcessWorkspaceBeforeDeletion() throws Exception {
AbstractProject project = mock(AbstractProject.class);
Node node = mock(Node.class);
TeamFoundationServerScm scm = new TeamFoundationServerScm("server", "projectpath", "workspace");
assertThat(scm.processWorkspaceBeforeDeletion(project, workspace, node), is(true));
verify(project).getLastBuild();
verifyNoMoreInteractions(project);
}
@Test public void assertWorkspaceIsntRemoveIfThereIsNoBuildOnSpecifiedNodeAndHudsonWantsToRemoveWorkspaceOnNode() throws Exception {
AbstractProject project = mock(AbstractProject.class);
AbstractBuild build = mock(AbstractBuild.class);
Node node = mock(Node.class);
Node inNode = mock(Node.class);
when(project.getLastBuild()).thenReturn(build);
when(build.getPreviousBuild()).thenReturn(build).thenReturn(null);
when(build.getBuiltOn()).thenReturn(node).thenReturn(node);
when(node.getNodeName()).thenReturn("node1").thenReturn("node2");
when(inNode.getNodeName()).thenReturn("needleNode").thenReturn("needleNode");
TeamFoundationServerScm scm = new TeamFoundationServerScm("server", "projectpath", "workspace");
assertThat( scm.processWorkspaceBeforeDeletion(project, workspace, inNode), is(true));
verify(project).getLastBuild();
verify(node, times(2)).getNodeName();
verify(build, times(2)).getBuiltOn();
verify(build, times(2)).getPreviousBuild();
verifyNoMoreInteractions(project);
verifyNoMoreInteractions(node);
verifyNoMoreInteractions(build);
}
}