/*
* 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.server.service;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.exceptions.ConfigFileHasChangedException;
import com.thoughtworks.go.config.exceptions.GoConfigInvalidException;
import com.thoughtworks.go.config.exceptions.PipelineGroupNotFoundException;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.config.materials.PluggableSCMMaterialConfig;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig;
import com.thoughtworks.go.config.materials.svn.SvnMaterialConfig;
import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry;
import com.thoughtworks.go.config.remote.RepoConfigOrigin;
import com.thoughtworks.go.config.server.security.ldap.BaseConfig;
import com.thoughtworks.go.config.server.security.ldap.BasesConfig;
import com.thoughtworks.go.config.update.ConfigUpdateResponse;
import com.thoughtworks.go.config.update.FullConfigUpdateCommand;
import com.thoughtworks.go.config.update.UiBasedConfigUpdateCommand;
import com.thoughtworks.go.config.update.UpdateConfigFromUI;
import com.thoughtworks.go.config.validation.GoConfigValidity;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.helper.GoConfigMother;
import com.thoughtworks.go.helper.MaterialConfigsMother;
import com.thoughtworks.go.helper.PipelineConfigMother;
import com.thoughtworks.go.helper.StageConfigMother;
import com.thoughtworks.go.i18n.Localizer;
import com.thoughtworks.go.listener.BaseUrlChangeListener;
import com.thoughtworks.go.listener.ConfigChangedListener;
import com.thoughtworks.go.security.GoCipher;
import com.thoughtworks.go.server.cache.GoCache;
import com.thoughtworks.go.server.dao.UserDao;
import com.thoughtworks.go.server.domain.PipelineConfigDependencyGraph;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.domain.user.PipelineSelections;
import com.thoughtworks.go.server.persistence.PipelineRepository;
import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult;
import com.thoughtworks.go.service.ConfigRepository;
import com.thoughtworks.go.util.*;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.core.IsInstanceOf;
import org.jdom2.input.JDOMParseException;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.File;
import java.util.*;
import static com.thoughtworks.go.helper.ConfigFileFixture.configWith;
import static com.thoughtworks.go.helper.PipelineConfigMother.createGroup;
import static com.thoughtworks.go.helper.PipelineConfigMother.pipelineConfig;
import static com.thoughtworks.go.helper.PipelineTemplateConfigMother.createTemplate;
import static java.lang.String.format;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
public class GoConfigServiceTest {
private GoConfigDao goConfigDao;
private GoConfigService goConfigService;
private PipelineRepository pipelineRepository;
private static final String PIPELINE = "pipeline1";
private static final String STAGE = "stage1";
private static final String JOB = "Job1";
private CruiseConfig cruiseConfig;
private Clock clock;
private GoCache goCache;
private ConfigRepository configRepo;
private UserDao userDao;
public PipelinePauseService pipelinePauseService;
private InstanceFactory instanceFactory;
private SystemEnvironment systemEnvironment;
@Before
public void setup() throws Exception {
new SystemEnvironment().setProperty(SystemEnvironment.ENFORCE_SERVERID_MUTABILITY, "N");
configRepo = mock(ConfigRepository.class);
goConfigDao = mock(GoConfigDao.class);
pipelineRepository = mock(PipelineRepository.class);
pipelinePauseService = mock(PipelinePauseService.class);
systemEnvironment = mock(SystemEnvironment.class);
cruiseConfig = unchangedConfig();
expectLoad(cruiseConfig);
this.clock = mock(Clock.class);
goCache = mock(GoCache.class);
instanceFactory = mock(InstanceFactory.class);
userDao = mock(UserDao.class);
stub(systemEnvironment.optimizeFullConfigSave()).toReturn(false);
ConfigElementImplementationRegistry registry = ConfigElementImplementationRegistryMother.withNoPlugins();
goConfigService = new GoConfigService(goConfigDao, pipelineRepository, this.clock, new GoConfigMigration(configRepo, new TimeProvider(), new ConfigCache(),
registry), goCache, configRepo, registry,
instanceFactory, mock(CachedGoPartials.class), systemEnvironment);
}
@Test
public void shouldUnderstandIfAnEnvironmentVariableIsConfiguredForAPipeline() throws Exception {
final PipelineConfigs newPipeline = new BasicPipelineConfigs();
PipelineConfig otherPipeline = createPipelineConfig("pipeline_other", "stage_other", "plan_other");
otherPipeline.setVariables(GoConfigFileHelper.env("OTHER_PIPELINE_LEVEL", "other pipeline"));
otherPipeline.first().setVariables(GoConfigFileHelper.env("OTHER_STAGE_LEVEL", "other stage"));
otherPipeline.first().jobConfigByConfigName(new CaseInsensitiveString("plan_other")).setVariables(GoConfigFileHelper.env("OTHER_JOB_LEVEL", "other job"));
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
pipelineConfig.setVariables(GoConfigFileHelper.env("PIPELINE_LEVEL", "pipeline value"));
StageConfig stageConfig = pipelineConfig.first();
stageConfig.setVariables(GoConfigFileHelper.env("STAGE_LEVEL", "stage value"));
stageConfig.jobConfigByConfigName(new CaseInsensitiveString("plan")).setVariables(GoConfigFileHelper.env("JOB_LEVEL", "job value"));
newPipeline.add(pipelineConfig);
newPipeline.add(otherPipeline);
CruiseConfig cruiseConfig = new BasicCruiseConfig(newPipeline);
EnvironmentConfig environmentConfig = cruiseConfig.addEnvironment("uat");
environmentConfig.addPipeline(new CaseInsensitiveString("pipeline"));
environmentConfig.addEnvironmentVariable("ENV_LEVEL", "env value");
expectLoad(cruiseConfig);
assertThat(goConfigService.hasVariableInScope("pipeline", "NOT_IN_SCOPE"), is(false));
assertThat(goConfigService.hasVariableInScope("pipeline", "ENV_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline", "PIPELINE_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline", "STAGE_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline", "JOB_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline", "OTHER_PIPELINE_LEVEL"), is(false));
assertThat(goConfigService.hasVariableInScope("pipeline", "OTHER_STAGE_LEVEL"), is(false));
assertThat(goConfigService.hasVariableInScope("pipeline", "OTHER_JOB_LEVEL"), is(false));
assertThat(goConfigService.hasVariableInScope("pipeline_other", "ENV_LEVEL"), is(false));
assertThat(goConfigService.hasVariableInScope("pipeline_other", "OTHER_PIPELINE_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline_other", "OTHER_STAGE_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline_other", "OTHER_JOB_LEVEL"), is(true));
assertThat(goConfigService.hasVariableInScope("pipeline_other", "NOT_IN_SCOPE"), is(false));
}
@Test
public void shouldUnderstandIfAStageHasFetchMaterialsConfigured() throws Exception {
PipelineConfig pipeline = createPipelineConfig("cruise", "dev", "test");
StageConfig stage = pipeline.first();
stage.setFetchMaterials(false);
CruiseConfig cruiseConfig = new BasicCruiseConfig(new BasicPipelineConfigs(pipeline));
expectLoad(cruiseConfig);
assertThat(goConfigService.shouldFetchMaterials("cruise", "dev"), is(false));
}
private void expectLoad(final CruiseConfig result) throws Exception {
when(goConfigDao.load()).thenReturn(result);
}
private void expectLoadForEditing(final CruiseConfig result) throws Exception {
when(goConfigDao.loadForEditing()).thenReturn(result);
}
private CruiseConfig unchangedConfig() {
return configWith(createPipelineConfig(PIPELINE, STAGE, JOB));
}
@Test
public void shouldGetAllStagesWithOne() throws Exception {
final PipelineConfigs newPipeline = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
newPipeline.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(newPipeline));
assertThat(goConfigService.stageConfigNamed("pipeline", "name"), is(pipelineConfig.findBy(new CaseInsensitiveString("name"))));
}
@Test
public void shouldTellIfAnUSerIsGroupAdministrator() throws Exception {
final PipelineConfigs newPipeline = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
newPipeline.add(pipelineConfig);
newPipeline.setAuthorization(new Authorization(new AdminsConfig(new AdminUser(new CaseInsensitiveString("dawg")))));
expectLoad(new BasicCruiseConfig(newPipeline));
final Username dawg = new Username(new CaseInsensitiveString("dawg"));
assertThat(goConfigService.isGroupAdministrator(dawg.getUsername()), is(true));
}
@Test
public void shouldTellIfAnEnvironmentExists() throws Exception {
BasicEnvironmentConfig first = new BasicEnvironmentConfig(new CaseInsensitiveString("first"));
BasicEnvironmentConfig second = new BasicEnvironmentConfig(new CaseInsensitiveString("second"));
CruiseConfig config = new BasicCruiseConfig();
config.addEnvironment(first);
config.addEnvironment(second);
expectLoad(config);
assertThat(goConfigService.hasEnvironmentNamed(new CaseInsensitiveString("first")), is(true));
assertThat(goConfigService.hasEnvironmentNamed(new CaseInsensitiveString("second")), is(true));
assertThat(goConfigService.hasEnvironmentNamed(new CaseInsensitiveString("SECOND")), is(true));
assertThat(goConfigService.hasEnvironmentNamed(new CaseInsensitiveString("fourth")), is(false));
}
@Test
public void shouldTellIfOnlyKnownUsersAreAllowedToLogin() throws Exception {
CruiseConfig config = new BasicCruiseConfig();
config.server().security().setAllowOnlyKnownUsersToLogin(true);
expectLoad(config);
assertThat(goConfigService.isOnlyKnownUserAllowedToLogin(), is(true));
}
@Test
public void shouldTellIfAnAgentExists() throws Exception {
CruiseConfig config = new BasicCruiseConfig();
config.agents().add(new AgentConfig("uuid"));
expectLoad(config);
assertThat(goConfigService.hasAgent("uuid"), is(true));
assertThat(goConfigService.hasAgent("doesnt-exist"), is(false));
}
@Test
public void shouldReturnTrueIfStageHasTestsAndFalseIfItDoesnt() throws Exception {
PipelineConfigs newPipelines = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
pipelineConfig.add(StageConfigMother.stageConfigWithArtifact("stage1", "job1", ArtifactType.unit));
pipelineConfig.add(StageConfigMother.stageConfigWithArtifact("stage2", "job2", ArtifactType.file));
newPipelines.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(newPipelines));
assertThat(goConfigService.stageHasTests("pipeline", "stage1"), is(true));
assertThat(goConfigService.stageHasTests("pipeline", "stage2"), is(false));
}
@Test
public void shouldGetCommentRenderer() throws Exception {
PipelineConfigs newPipeline = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
pipelineConfig.setTrackingTool(new TrackingTool("link", "regex"));
newPipeline.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(newPipeline));
assertEquals(goConfigService.getCommentRendererFor("pipeline"), new TrackingTool("link", "regex"));
pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
pipelineConfig.setMingleConfig(new MingleConfig("baseUrl", "projIdentifier", "mql"));
newPipeline = new BasicPipelineConfigs();
newPipeline.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(newPipeline));
assertEquals(goConfigService.getCommentRendererFor("pipeline"), new MingleConfig("baseUrl", "projIdentifier", "mql"));
pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
newPipeline = new BasicPipelineConfigs();
newPipeline.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(newPipeline));
assertEquals(goConfigService.getCommentRendererFor("pipeline"), new TrackingTool());
}
@Test
public void shouldUnderstandIfAPipelineIsLockable() throws Exception {
PipelineConfigs group = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
group.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(group));
assertThat(goConfigService.isLockable("pipeline"), is(false));
pipelineConfig.lockExplicitly();
expectLoad(new BasicCruiseConfig(group));
assertThat(goConfigService.isLockable("pipeline"), is(true));
}
@Test
public void shouldUnderstandIfLdapIsConfigured() throws Exception {
CruiseConfig config = new BasicCruiseConfig();
config.setServerConfig(new ServerConfig(null, new SecurityConfig(new LdapConfig("test", "test", "test", null, true, new BasesConfig(new BaseConfig("test")), "test"), null, true, null)));
expectLoad(config);
assertThat("Ldap is configured", goConfigService.isLdapConfigured(), is(true));
}
@Test
public void shouldRememberValidityWhenCruiseConfigLoaderHasInvalidConfigFile() throws Exception {
GoConfigService service = goConfigServiceWithInvalidStatus();
assertThat(service.checkConfigFileValid().isValid(), is(false));
assertThat(service.checkConfigFileValid().errorMessage(), is("JDom exception"));
}
@Test
public void shouldNotHaveErrorMessageWhenConfigFileValid() throws Exception {
when(goConfigDao.checkConfigFileValid()).thenReturn(GoConfigValidity.valid());
GoConfigValidity configValidity = goConfigService.checkConfigFileValid();
assertThat(configValidity.isValid(), is(true));
assertThat(configValidity.errorMessage(), is(""));
}
private CruiseConfig configWithPipeline() {
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "stage", "first");
pipelineConfig.addMaterialConfig(MaterialConfigsMother.hgMaterialConfig());
CruiseConfig config = configWith(pipelineConfig);
config.server().setArtifactsDir("/var/logs");
return config;
}
@Test
public void shouldReturnInvalidWhenWholeConfigIsInvalidAndShouldUpgrade() throws Exception {
CruiseConfig config = configWithPipeline();
when(goConfigDao.loadForEditing()).thenReturn(config);
String configContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<cruise xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"cruise-config.xsd\" schemaVersion=\"14\">\n"
+ "<server artifactsdir='artifactsDir'/><unknown/></cruise>";
GoConfigValidity validity = goConfigService.fileSaver(true).saveXml(configContent, "md5");
assertThat(validity.errorMessage(), is("Cruise config file with version 14 is invalid. Unable to upgrade."));
}
@Test
public void shouldReturnInvalidWhenWholeConfigIsInvalid() throws Exception {
CruiseConfig config = configWithPipeline();
when(goConfigDao.loadForEditing()).thenReturn(config);
String configContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<cruise xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"cruise-config.xsd\" schemaVersion=\"" + GoConstants.CONFIG_SCHEMA_VERSION + "\">\n"
+ "<server artifactsdir='artifactsDir'/><unknown/></cruise>";
GoConfigValidity validity = goConfigService.fileSaver(false).saveXml(configContent, "md5");
assertThat(validity.errorMessage(), containsString("Invalid content was found starting with element 'unknown'"));
}
@Test
public void shouldReturnvariablesForAPipeline() {
EnvironmentConfig env = cruiseConfig.addEnvironment("environment");
env.addEnvironmentVariable("foo", "env-fooValue");
env.addEnvironmentVariable("bar", "env-barValue");
env.addPipeline(new CaseInsensitiveString(PIPELINE));
PipelineConfig pipeline = cruiseConfig.pipelineConfigByName(new CaseInsensitiveString(PIPELINE));
pipeline.addEnvironmentVariable("foo", "pipeline-fooValue");
pipeline.addEnvironmentVariable("blah", "pipeline-blahValue");
EnvironmentVariablesConfig variables = goConfigService.variablesFor(PIPELINE);
assertThat(variables.size(), is(3));
assertThat(variables, hasItems(
new EnvironmentVariableConfig("foo", "pipeline-fooValue"),
new EnvironmentVariableConfig("bar", "env-barValue"),
new EnvironmentVariableConfig("blah", "pipeline-blahValue")));
}
@Test
public void shouldReturnvariablesForAPipelineNotInAnEnvironment() {
PipelineConfig pipeline = cruiseConfig.pipelineConfigByName(new CaseInsensitiveString(PIPELINE));
pipeline.addEnvironmentVariable("foo", "pipeline-fooValue");
pipeline.addEnvironmentVariable("blah", "pipeline-blahValue");
EnvironmentVariablesConfig variables = goConfigService.variablesFor(PIPELINE);
assertThat(variables.size(), is(2));
assertThat(variables, hasItems(
new EnvironmentVariableConfig("foo", "pipeline-fooValue"),
new EnvironmentVariableConfig("blah", "pipeline-blahValue")));
}
private PipelineConfig pipelineWithTemplate() {
PipelineConfig pipeline = PipelineConfigMother.pipelineConfig("pipeline");
pipeline.clear();
pipeline.setTemplateName(new CaseInsensitiveString("foo"));
PipelineTemplateConfig template = new PipelineTemplateConfig(new CaseInsensitiveString("foo"), StageConfigMother.custom("stage", "job"));
pipeline.usingTemplate(template);
return pipeline;
}
@Test
public void shouldNotThrowExceptionWhenUpgradeFailsForConfigFileUpdate() throws Exception {
expectLoadForEditing(configWith(createPipelineConfig("pipeline", "stage", "build")));
GoConfigService.XmlPartialSaver saver = goConfigService.fileSaver(true);
GoConfigValidity validity = saver.saveXml("some_junk", "junk_md5");
assertThat(validity.isValid(), is(false));
assertThat(validity.errorMessage(), is("Error on line 1: Content is not allowed in prolog."));
}
@Test
public void shouldProvideDetailsWhenXmlConfigDomIsInvalid() throws Exception {
expectLoadForEditing(configWith(createPipelineConfig("pipeline", "stage", "build")));
GoConfigService.XmlPartialSaver saver = goConfigService.fileSaver(false);
String configContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<cruise xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"cruise-config.xsd\" schemaVersion=\"" + GoConstants.CONFIG_SCHEMA_VERSION + "\">\n"
+ "<server artifactsdir='artifactsDir></cruise>";
GoConfigValidity validity = saver.saveXml(configContent, "junk_md5");
assertThat(validity.isValid(), is(false));
assertThat(validity.errorMessage(),
is("Invalid Configuration - Error on line 3: The value of attribute \"artifactsdir\" associated with an element type \"server\" must not contain the '<' character."));
}
@Test
public void xmlPartialSaverShouldReturnTheRightXMLThroughAsXml() throws Exception {
expectLoadForEditing(new GoConfigMother().defaultCruiseConfig());
GoConfigService.XmlPartialSaver saver = goConfigService.fileSaver(true);
assertThat(saver.asXml(), containsString(String.format("schemaVersion=\"%s\"", GoConstants.CONFIG_SCHEMA_VERSION)));
assertThat(saver.asXml(), containsString("xsi:noNamespaceSchemaLocation=\"cruise-config.xsd\""));
}
@Test
public void shouldRegisterListenerWithTheConfigDAO() throws Exception {
final ConfigChangedListener listener = mock(ConfigChangedListener.class);
goConfigService.register(listener);
verify(goConfigDao).registerListener(listener);
}
private CruiseConfig configWithAgents(AgentConfig... agentConfigs) {
CruiseConfig cruiseConfig = unchangedConfig();
cruiseConfig.agents().addAll(Arrays.asList(agentConfigs));
return cruiseConfig;
}
@Test
public void shouldFixJobNameCase() throws Exception {
expectLoad(unchangedConfig());
JobConfigIdentifier translated = goConfigService.translateToActualCase(
new JobConfigIdentifier(PIPELINE.toUpperCase(), STAGE.toUpperCase(), JOB.toUpperCase()));
assertThat(translated, is(new JobConfigIdentifier(PIPELINE, STAGE, JOB)));
}
@Test
public void shouldNotLoseUUIDWhenRunOnAllAgents() throws Exception {
expectLoad(unchangedConfigWithRunOnAllAgents());
JobConfigIdentifier translated = goConfigService.translateToActualCase(
new JobConfigIdentifier(PIPELINE.toUpperCase(), STAGE.toUpperCase(), RunOnAllAgents.CounterBasedJobNameGenerator.appendMarker(JOB.toUpperCase(), 2)));
assertThat(translated, is(new JobConfigIdentifier(PIPELINE, STAGE, RunOnAllAgents.CounterBasedJobNameGenerator.appendMarker(JOB, 2))));
}
@Test
public void shouldNotBeInstanceOfWhenRunOnAllAgentsWithMissingAgent() throws Exception {
expectLoad(unchangedConfigWithRunOnAllAgents());
String missingJobName = JOB + "-missing";
try {
goConfigService.translateToActualCase(new JobConfigIdentifier(PIPELINE, STAGE, missingJobName));
fail("Should not be able to find job with missing agent");
} catch (JobNotFoundException expected) {
assertThat(expected.getMessage(), is(format("Job '%s' not found in pipeline '%s' stage '%s'", missingJobName, PIPELINE, STAGE)));
}
}
private CruiseConfig unchangedConfigWithRunOnAllAgents() {
PipelineConfig pipelineConfig = createPipelineConfig(PIPELINE, STAGE, JOB);
pipelineConfig.get(0).jobConfigByConfigName(new CaseInsensitiveString(JOB)).setRunOnAllAgents(true);
return configWith(pipelineConfig);
}
@Test
public void shouldThrowJobNotFoundExceptionWhenJobDoesNotExist() throws Exception {
expectLoad(unchangedConfig());
try {
goConfigService.translateToActualCase(new JobConfigIdentifier(PIPELINE, STAGE, "invalid-job"));
fail("should throw exception if job does not exist");
} catch (Exception e) {
assertThat(e, instanceOf(JobNotFoundException.class));
assertThat(e.getMessage(), containsString("invalid-job"));
}
}
@Test
public void shouldThrowStageNotFoundExceptionWhenStageDoesNotExist() throws Exception {
expectLoad(unchangedConfig());
try {
goConfigService.translateToActualCase(new JobConfigIdentifier(PIPELINE, "invalid-stage", JOB));
fail("should throw exception if stage does not exist");
} catch (Exception e) {
assertThat(e, instanceOf(StageNotFoundException.class));
assertThat(e.getMessage(), containsString("invalid-stage"));
}
}
@Test
public void shouldThrowPipelineNotFoundExceptionWhenStageDoesNotExist() throws Exception {
expectLoad(unchangedConfig());
try {
goConfigService.translateToActualCase(new JobConfigIdentifier("invalid-pipeline", STAGE, JOB));
fail("should throw exception if pipeline does not exist");
} catch (Exception e) {
assertThat(e, instanceOf(PipelineNotFoundException.class));
assertThat(e.getMessage(), containsString("invalid-pipeline"));
}
}
@Test
public void shouldThrowIfCruiseHasNoReadPermissionOnArtifactsDir() throws Exception {
if (SystemUtil.isWindows()) {
return;
}
File artifactsDir = FileUtil.createTempFolder();
artifactsDir.setReadable(false, false);
cruiseConfig.setServerConfig(new ServerConfig(artifactsDir.getAbsolutePath(), new SecurityConfig()));
expectLoad(cruiseConfig);
try {
goConfigService.initialize();
fail("should throw when cruise has no read permission on artifacts dir " + artifactsDir.getAbsolutePath());
} catch (Exception e) {
assertThat(e.getMessage(), is("Cruise does not have read permission on " + artifactsDir.getAbsolutePath()));
} finally {
FileUtil.deleteFolder(artifactsDir);
}
}
@Test
public void shouldThrowIfCruiseHasNoWritePermissionOnArtifactsDir() throws Exception {
if (SystemUtil.isWindows()) {
return;
}
File artifactsDir = FileUtil.createTempFolder();
artifactsDir.setWritable(false, false);
cruiseConfig.setServerConfig(new ServerConfig(artifactsDir.getAbsolutePath(), new SecurityConfig()));
expectLoad(cruiseConfig);
try {
goConfigService.initialize();
fail("should throw when cruise has no write permission on artifacts dir " + artifactsDir.getAbsolutePath());
} catch (Exception e) {
assertThat(e.getMessage(), is("Cruise does not have write permission on " + artifactsDir.getAbsolutePath()));
} finally {
FileUtil.deleteFolder(artifactsDir);
}
}
@Test
public void shouldFindMaterialByPipelineUniqueFingerprint() throws Exception {
SvnMaterialConfig svnMaterialConfig = new SvnMaterialConfig("repo", null, null, false);
svnMaterialConfig.setName(new CaseInsensitiveString("foo"));
cruiseConfig = configWith(GoConfigMother.createPipelineConfigWithMaterialConfig(svnMaterialConfig));
when(goConfigDao.load()).thenReturn(cruiseConfig);
assertThat(goConfigService.findMaterial(new CaseInsensitiveString("pipeline"), svnMaterialConfig.getPipelineUniqueFingerprint()), is(svnMaterialConfig));
assertThat(goConfigService.findMaterial(new CaseInsensitiveString("piPelIne"), svnMaterialConfig.getPipelineUniqueFingerprint()), is(svnMaterialConfig));
}
@Test
public void shouldReturnNullIfNoMaterialMatches() throws Exception {
DependencyMaterialConfig dependencyMaterialConfig = new DependencyMaterialConfig(new CaseInsensitiveString("upstream-pipeline"), new CaseInsensitiveString("upstream-stage"));
cruiseConfig = configWith(GoConfigMother.createPipelineConfigWithMaterialConfig(dependencyMaterialConfig));
when(goConfigDao.load()).thenReturn(cruiseConfig);
assertThat(goConfigService.findMaterial(new CaseInsensitiveString("pipeline"), "missing"), is(nullValue()));
}
@Test
public void shouldFindMaterialConfigBasedOnFingerprint() throws Exception {
SvnMaterialConfig expected = new SvnMaterialConfig("repo", null, null, false);
cruiseConfig = configWith(GoConfigMother.createPipelineConfigWithMaterialConfig(expected));
when(goConfigDao.load()).thenReturn(cruiseConfig);
MaterialConfig actual = goConfigService.materialForPipelineWithFingerprint("pipeline", expected.getFingerprint());
assertThat(actual, is(expected));
}
@Test
public void shouldThrowExceptionWhenUnableToFindMaterialBasedOnFingerprint() throws Exception {
SvnMaterialConfig svnMaterialConfig = new SvnMaterialConfig("repo", null, null, false);
cruiseConfig = configWith(GoConfigMother.createPipelineConfigWithMaterialConfig(svnMaterialConfig));
when(goConfigDao.load()).thenReturn(cruiseConfig);
try {
goConfigService.materialForPipelineWithFingerprint("pipeline", "bad-fingerprint");
fail("Shouldn't be able to find material with incorrect fingerprint");
} catch (Exception expected) {
assertThat(expected.getMessage(), is("Pipeline [pipeline] does not have a material with fingerprint [bad-fingerprint]"));
}
}
@Test
public void shouldReturnDependentPiplinesForAGivenPipeline() throws Exception {
PipelineConfig up = createPipelineConfig("blahPipeline", "blahStage");
up.addMaterialConfig(MaterialConfigsMother.hgMaterialConfig());
PipelineConfig down1 = GoConfigMother.createPipelineConfigWithMaterialConfig("down1", new DependencyMaterialConfig(new CaseInsensitiveString("blahPipeline"), new CaseInsensitiveString("blahStage")));
PipelineConfig down2 = GoConfigMother.createPipelineConfigWithMaterialConfig("down2", new DependencyMaterialConfig(new CaseInsensitiveString("blahPipeline"), new CaseInsensitiveString("blahStage")));
when(goConfigDao.load()).thenReturn(configWith(
up, down1, down2, GoConfigMother.createPipelineConfigWithMaterialConfig("otherPipeline", new DependencyMaterialConfig(new CaseInsensitiveString("someotherpipeline"),
new CaseInsensitiveString("blahStage")))
));
assertThat(goConfigService.downstreamPipelinesOf("blahPipeline"), is(Arrays.asList(down1, down2)));
}
@Test
public void shouldReturnUpstreamDependencyGraphForAGivenPipeline() throws Exception {
PipelineConfig current = GoConfigMother.createPipelineConfigWithMaterialConfig("current", new DependencyMaterialConfig(new CaseInsensitiveString("up1"), new CaseInsensitiveString("first")),
new DependencyMaterialConfig(new CaseInsensitiveString("up2"), new CaseInsensitiveString("first")));
PipelineConfig up1 = GoConfigMother.createPipelineConfigWithMaterialConfig("up1", new DependencyMaterialConfig(new CaseInsensitiveString("uppest"), new CaseInsensitiveString("first")));
PipelineConfig up2 = GoConfigMother.createPipelineConfigWithMaterialConfig("up2", new DependencyMaterialConfig(new CaseInsensitiveString("uppest"), new CaseInsensitiveString("first")));
PipelineConfig uppest = GoConfigMother.createPipelineConfigWithMaterialConfig("uppest", MaterialConfigsMother.hgMaterialConfig());
when(goConfigDao.load()).thenReturn(configWith(current, up1, up2, uppest));
assertThat(goConfigService.upstreamDependencyGraphOf("current"), is(
new PipelineConfigDependencyGraph(current,
new PipelineConfigDependencyGraph(up1, new PipelineConfigDependencyGraph(uppest)),
new PipelineConfigDependencyGraph(up2, new PipelineConfigDependencyGraph(uppest))
)));
/*
uppest
/ \
up1 up2
\ /
current
*/
}
@Test
public void shouldDetermineIfStageExistsInCurrentConfig() throws Exception {
PipelineConfigs pipelineConfigs = new BasicPipelineConfigs();
pipelineConfigs.add(createPipelineConfig("pipeline", "stage", "job"));
expectLoad(new BasicCruiseConfig(pipelineConfigs));
assertThat(goConfigService.stageExists("pipeline", "randomstage"), is(false));
assertThat(goConfigService.stageExists("pipeline", "stage"), is(true));
}
@Test
public void shouldPersistPipelineSelections_WhenSecurityIsDisabled() {
Date date = new DateTime(2000, 1, 1, 1, 1, 1, 1).toDate();
when(clock.currentTime()).thenReturn(date);
mockConfig();
Matcher<PipelineSelections> pipelineSelectionsMatcher = hasValues(Arrays.asList("pipelineX", "pipeline3"), Arrays.asList("pipeline1", "pipeline2"), date, null);
when(pipelineRepository.saveSelectedPipelines(argThat(pipelineSelectionsMatcher))).thenReturn(2L);
assertThat(goConfigService.persistSelectedPipelines(null, null, Arrays.asList("pipelineX", "pipeline3"), true), is(2l));
verify(pipelineRepository).saveSelectedPipelines(argThat(pipelineSelectionsMatcher));
}
@Test
public void shouldPersistPipelineSelectionsAgainstUser_AlreadyHavingSelections() {
Date date = new DateTime(2000, 1, 1, 1, 1, 1, 1).toDate();
when(clock.currentTime()).thenReturn(date);
mockConfigWithSecurity();
User user = getUser("badger", 10L);
PipelineSelections pipelineSelections = new PipelineSelections(Arrays.asList("pipeline2"), new Date(), user.getId(), true);
when(pipelineRepository.findPipelineSelectionsByUserId(user.getId())).thenReturn(pipelineSelections);
when(pipelineRepository.saveSelectedPipelines(pipelineSelections)).thenReturn(2L);
long pipelineSelectionId = goConfigService.persistSelectedPipelines("1", user.getId(), Arrays.asList("pipelineX", "pipeline3"), true);
assertThat(pipelineSelections.getSelections(), is("pipeline1,pipeline2"));
assertThat(pipelineSelectionId, is(2l));
verify(pipelineRepository).saveSelectedPipelines(pipelineSelections);
verify(pipelineRepository).findPipelineSelectionsByUserId(user.getId());
verify(pipelineRepository, never()).findPipelineSelectionsById("1");
}
@Test
public void shouldPersistPipelineSelectionsAgainstUser_WhenUserHasNoSelections() {
Date date = new DateTime(2000, 1, 1, 1, 1, 1, 1).toDate();
when(clock.currentTime()).thenReturn(date);
mockConfigWithSecurity();
User user = getUser("badger", 10L);
Matcher<PipelineSelections> pipelineSelectionsMatcher = hasValues(Arrays.asList("pipelineX", "pipeline3"), Arrays.asList("pipeline1", "pipeline2"), date, user.getId());
when(pipelineRepository.findPipelineSelectionsByUserId(user.getId())).thenReturn(null);
when(pipelineRepository.saveSelectedPipelines(argThat(pipelineSelectionsMatcher))).thenReturn(2L);
long pipelineSelectionsId = goConfigService.persistSelectedPipelines("1", user.getId(), Arrays.asList("pipelineX", "pipeline3"), true);
assertThat(pipelineSelectionsId, is(2l));
verify(pipelineRepository).saveSelectedPipelines(argThat(pipelineSelectionsMatcher));
verify(pipelineRepository).findPipelineSelectionsByUserId(user.getId());
verify(pipelineRepository, never()).findPipelineSelectionsById("1");
}
@Test
public void shouldPersistPipelineSelectionsShouldRemovePipelinesFromSelectedGroups() {
CruiseConfig config = configWith(
createGroup("group1", pipelineConfig("pipeline1"), pipelineConfig("pipeline2")),
createGroup("group2", pipelineConfig("pipelineX")),
createGroup("group3", pipelineConfig("pipeline3"), pipelineConfig("pipeline4")));
when(goConfigDao.load()).thenReturn(config);
goConfigService.persistSelectedPipelines(null, null, Arrays.asList("pipeline1", "pipeline2", "pipeline3"), true);
verify(pipelineRepository).saveSelectedPipelines(argThat(hasValues(Arrays.asList("pipeline1", "pipeline2", "pipeline3"), Arrays.asList("pipelineX", "pipeline4"), clock.currentTime(), null)));
}
@Test
public void shouldPersistInvertedListOfPipelineSelections_WhenBlacklistIsSelected() {
Date date = new DateTime(2000, 1, 1, 1, 1, 1, 1).toDate();
when(clock.currentTime()).thenReturn(date);
mockConfigWithSecurity();
User user = getUser("badger", 10L);
PipelineSelections blacklistPipelineSelections = new PipelineSelections(new ArrayList<>(), date, user.getId(), false);
when(pipelineRepository.findPipelineSelectionsByUserId(user.getId())).thenReturn(blacklistPipelineSelections);
goConfigService.persistSelectedPipelines(null, user.getId(), Arrays.asList("pipelineX", "pipeline3"), true);
verify(pipelineRepository).saveSelectedPipelines(argThat(isAPipelineSelectionsInstanceWith(true, "pipeline1", "pipeline2")));
}
@Test
public void shouldPersistNonInvertedListOfPipelineSelections_WhenWhitelistIsSelected() {
Date date = new DateTime(2000, 1, 1, 1, 1, 1, 1).toDate();
when(clock.currentTime()).thenReturn(date);
mockConfigWithSecurity();
User user = getUser("badger", 10L);
PipelineSelections whitelistPipelineSelections = new PipelineSelections(new ArrayList<>(), date, user.getId(), true);
when(pipelineRepository.findPipelineSelectionsByUserId(user.getId())).thenReturn(whitelistPipelineSelections);
goConfigService.persistSelectedPipelines(null, user.getId(), Arrays.asList("pipelineX", "pipeline3"), false);
verify(pipelineRepository).saveSelectedPipelines(argThat(isAPipelineSelectionsInstanceWith(false, "pipelineX", "pipeline3")));
}
@Test
public void shouldUpdateAlreadyPersistedSelection_WhenSecurityIsDisabled() {
Date date = new DateTime(2000, 1, 1, 1, 1, 1, 1).toDate();
when(clock.currentTime()).thenReturn(date);
mockConfig();
PipelineSelections pipelineSelections = new PipelineSelections(Arrays.asList("pip1"));
when(pipelineRepository.findPipelineSelectionsById("123")).thenReturn(pipelineSelections);
List<String> newPipelines = Arrays.asList("pipeline1", "pipeline2");
goConfigService.persistSelectedPipelines("123", null, newPipelines, true);
assertHasSelected(pipelineSelections, newPipelines);
assertThat(pipelineSelections.lastUpdated(), is(date));
verify(pipelineRepository).findPipelineSelectionsById("123");
verify(pipelineRepository).saveSelectedPipelines(argThat(hasValues(Arrays.asList("pipeline1", "pipeline2"), Arrays.asList("pipelineX", "pipeline3"), clock.currentTime(), null)));
}
@Test
public void shouldReturnPersistedPipelineSelectionsAgainstCookieId_WhenSecurityisDisabled() {
PipelineSelections pipelineSelections = new PipelineSelections(Arrays.asList("pip1"));
when(pipelineRepository.findPipelineSelectionsById("123")).thenReturn(pipelineSelections);
assertThat(goConfigService.getSelectedPipelines("123", null), is(pipelineSelections));
assertThat(goConfigService.getSelectedPipelines("", null), is(PipelineSelections.ALL));
assertThat(goConfigService.getSelectedPipelines("345", null), is(PipelineSelections.ALL));
}
@Test
public void shouldReturnPersistedPipelineSelectionsAgainstUser_WhenSecurityIsEnabled() {
User loser = getUser("loser", 10L);
User newUser = getUser("new user", 20L);
when(userDao.findUser("new user")).thenReturn(newUser);
mockConfigWithSecurity();
PipelineSelections pipelineSelections = new PipelineSelections(Arrays.asList("pip1"));
when(pipelineRepository.findPipelineSelectionsByUserId(loser.getId())).thenReturn(pipelineSelections);
assertThat(goConfigService.getSelectedPipelines("1", loser.getId()), is(pipelineSelections));
assertThat(goConfigService.getSelectedPipelines("1", newUser.getId()), is(PipelineSelections.ALL));
}
@Test
public void shouldReturnAllPipelineSelections_WhenSecurityIsEnabled_AndNoPersistedSelections() {
User user = getUser("loser", 10L);
User newUser = getUser("new user", 20L);
when(userDao.findUser("new user")).thenReturn(newUser);
mockConfigWithSecurity();
when(pipelineRepository.findPipelineSelectionsByUserId(user.getId())).thenReturn(null);
when(pipelineRepository.findPipelineSelectionsById("1")).thenReturn(null);
assertThat(goConfigService.getSelectedPipelines("1", newUser.getId()), is(PipelineSelections.ALL));
}
@Test
public void shouldReturnPersistedPipelineSelectionsAgainstCookieId_WhenSecurityIsEnabled_AndUserSelectionsDoesNotExist() {
User user = getUser("loser", 10L);
mockConfigWithSecurity();
PipelineSelections pipelineSelections = new PipelineSelections(Arrays.asList("pip1"));
when(pipelineRepository.findPipelineSelectionsByUserId(user.getId())).thenReturn(null);
when(pipelineRepository.findPipelineSelectionsById("1")).thenReturn(pipelineSelections);
assertThat(goConfigService.getSelectedPipelines("1", user.getId()), is(pipelineSelections));
}
@Test
public void shouldRegisterBaseUrlChangeListener() throws Exception {
CruiseConfig cruiseConfig = new GoConfigMother().cruiseConfigWithOnePipelineGroup();
stub(goConfigDao.load()).toReturn(cruiseConfig);
goConfigService.initialize();
verify(goConfigDao).registerListener(any(BaseUrlChangeListener.class));
}
@Test
public void getConfigAtVersion_shouldFetchRequiredVersion() throws Exception {
GoConfigRevision revision = new GoConfigRevision("v1", "md5-1", "loser", "100.3.9.1", new TimeProvider());
when(configRepo.getRevision("md5-1")).thenReturn(revision);
assertThat(goConfigService.getConfigAtVersion("md5-1"), is(revision));
}
@Test
public void getNotThrowUpWhenRevisionIsNotFound() throws Exception {
when(configRepo.getRevision("md5-1")).thenThrow(new IllegalArgumentException("did not find the revision"));
try {
assertThat(goConfigService.getConfigAtVersion("md5-1"), is(nullValue()));
} catch (Exception e) {
fail("should not have thrown up");
}
}
@Test
public void shouldReturnListOfUpstreamPipelineConfigValidForFetchArtifact() {
PipelineConfig unrelatedPipeline = PipelineConfigMother.pipelineConfig("some.random.pipeline");
PipelineConfig upstream = PipelineConfigMother.createPipelineConfig("upstream", "upstream.stage", "upstream.job");
upstream.add(StageConfigMother.stageConfig("upstream.stage.2"));
upstream.add(StageConfigMother.stageConfig("upstream.stage.3"));
PipelineConfig downstream = PipelineConfigMother.createPipelineConfig("pipeline", "stage.1", "jobs");
downstream.add(StageConfigMother.stageConfig("stage.2"));
downstream.add(StageConfigMother.stageConfig("current.stage"));
downstream.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("upstream"), new CaseInsensitiveString("upstream.stage.2")));
CruiseConfig cruiseConfig = configWith(upstream, downstream, unrelatedPipeline);
when(goConfigDao.load()).thenReturn(cruiseConfig);
List<PipelineConfig> fetchablePipelines = goConfigService.pipelinesForFetchArtifacts("pipeline");
assertThat(fetchablePipelines.size(), is(2));
assertThat(fetchablePipelines, hasItem(upstream));
assertThat(fetchablePipelines, hasItem(downstream));
}
@Test
public void uiBasedUpdateCommandShouldReturnTheConfigPassedByUpdateOperation() {
UiBasedConfigUpdateCommand command = new UiBasedConfigUpdateCommand("md5", null, null, null) {
public boolean canContinue(CruiseConfig cruiseConfig) {
return true;
}
};
CruiseConfig after = new BasicCruiseConfig();
command.afterUpdate(after);
assertThat(command.configAfter(), sameInstance(after));
}
@Test
public void shouldUseInstanceFactoryToCreateAStageInstanceForTheSpecifiedPipelineStageCombination() throws Exception {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("foo-pipeline", "foo-stage", "foo-job");
DefaultSchedulingContext schedulingContext = new DefaultSchedulingContext("loser");
String md5 = "foo-md5";
CruiseConfig config = mock(BasicCruiseConfig.class);
when(config.pipelineConfigByName(new CaseInsensitiveString("foo-pipeline"))).thenReturn(pipelineConfig);
when(config.getMd5()).thenReturn(md5);
when(goConfigDao.load()).thenReturn(config);
goConfigService.scheduleStage("foo-pipeline", "foo-stage", schedulingContext);
verify(instanceFactory).createStageInstance(pipelineConfig, new CaseInsensitiveString("foo-stage"), schedulingContext, md5, clock);
}
@Test
public void shouldReturnFalseIfMD5DoesNotMatch() {
String staleMd5 = "oldmd5";
when(goConfigDao.md5OfConfigFile()).thenReturn("newmd5");
assertThat(goConfigService.doesMd5Match(staleMd5), is(false));
}
@Test
public void shouldReturnTrueifMd5Matches() {
String staleMd5 = "md5";
when(goConfigDao.md5OfConfigFile()).thenReturn("md5");
assertThat(goConfigService.doesMd5Match(staleMd5), is(true));
}
@Test
public void shouldThrowExceptionIfGroupDoesNotExist_WhenUserIsAdmin() {
CaseInsensitiveString adminName = new CaseInsensitiveString("admin");
GoConfigMother mother = new GoConfigMother();
mother.enableSecurityWithPasswordFile(cruiseConfig);
cruiseConfig.server().security().adminsConfig().add(new AdminUser(adminName));
String groupName = String.format("group_%s", UUID.randomUUID());
try {
goConfigService.isUserAdminOfGroup(adminName, groupName);
fail("Should fail since group does not exist");
} catch (Exception e) {
assertThat(e, is(instanceOf(PipelineGroupNotFoundException.class)));
}
}
@Test
public void shouldThrowExceptionIfGroupDoesNotExist_WhenUserIsNonAdmin() {
CaseInsensitiveString adminName = new CaseInsensitiveString("admin");
String groupName = String.format("group_%s", UUID.randomUUID());
GoConfigMother mother = new GoConfigMother();
mother.enableSecurityWithPasswordFile(cruiseConfig);
cruiseConfig.server().security().adminsConfig().add(new AdminUser(adminName));
try {
goConfigService.isUserAdminOfGroup(new CaseInsensitiveString("foo"), groupName);
fail("Should fail since group does not exist");
} catch (Exception e) {
assertThat(e, is(instanceOf(PipelineGroupNotFoundException.class)));
}
}
@Test
public void shouldReturnTrueIfUserIsTheAdminForGroup() {
CaseInsensitiveString adminName = new CaseInsensitiveString("admin");
String groupName = String.format("group_%s", UUID.randomUUID());
GoConfigMother mother = new GoConfigMother();
mother.enableSecurityWithPasswordFile(cruiseConfig);
cruiseConfig.server().security().adminsConfig().add(new AdminUser(adminName));
mother.addPipelineWithGroup(cruiseConfig, groupName, "pipeline", "stage");
mother.addAdminUserForPipelineGroup(cruiseConfig, "user", groupName);
assertThat(goConfigService.isUserAdminOfGroup(new CaseInsensitiveString("user"), groupName), is(true));
}
@Test
public void shouldReturnValidOnUpdateXml() throws Exception {
String groupName = "group_name";
String md5 = "md5";
cruiseConfig = new BasicCruiseConfig();
expectLoad(cruiseConfig);
new GoConfigMother().addPipelineWithGroup(cruiseConfig, groupName, "pipeline_name", "stage_name", "job_name");
expectLoadForEditing(cruiseConfig);
when(goConfigDao.md5OfConfigFile()).thenReturn(md5);
GoConfigService.XmlPartialSaver partialSaver = goConfigService.groupSaver(groupName);
String renamedGroupName = "renamed_group_name";
GoConfigValidity validity = partialSaver.saveXml(groupXml(renamedGroupName), md5);
assertThat(validity.isValid(), Matchers.is(true));
assertThat(validity.errorMessage(), Matchers.is(""));
verify(goConfigDao).updateConfig(argThat(cruiseConfigIsUpdatedWith(renamedGroupName, "new_name", "${COUNT}-#{foo}")));
}
@Test
public void shouldUpdateXmlUsingNewFlowIfEnabled() throws Exception {
String groupName = "group_name";
String md5 = "md5";
cruiseConfig = new BasicCruiseConfig();
ArgumentCaptor<FullConfigUpdateCommand> commandArgumentCaptor = ArgumentCaptor.forClass(FullConfigUpdateCommand.class);
expectLoad(cruiseConfig);
new GoConfigMother().addPipelineWithGroup(cruiseConfig, groupName, "pipeline_name", "stage_name", "job_name");
expectLoadForEditing(cruiseConfig);
when(goConfigDao.md5OfConfigFile()).thenReturn(md5);
when(systemEnvironment.optimizeFullConfigSave()).thenReturn(true);
when(goConfigDao.updateFullConfig(commandArgumentCaptor.capture())).thenReturn(null);
GoConfigService.XmlPartialSaver partialSaver = goConfigService.groupSaver(groupName);
String renamedGroupName = "renamed_group_name";
GoConfigValidity validity = partialSaver.saveXml(groupXml(renamedGroupName), md5);
assertThat(validity.isValid(), Matchers.is(true));
assertThat(validity.errorMessage(), Matchers.is(""));
CruiseConfig updatedConfig = commandArgumentCaptor.getValue().configForEdit();
PipelineConfigs group = updatedConfig.findGroup(renamedGroupName);
PipelineConfig pipeline = group.findBy(new CaseInsensitiveString("new_name"));
assertThat(pipeline.name(), is(new CaseInsensitiveString("new_name")));
assertThat(pipeline.getLabelTemplate(), is("${COUNT}-#{foo}"));
assertThat(pipeline.materialConfigs().first(), is(IsInstanceOf.instanceOf(SvnMaterialConfig.class)));
assertThat(pipeline.materialConfigs().first().getUriForDisplay(), is("file:///tmp/foo"));
}
@Test
public void shouldReturnInvalidWhenPipelineGroupPartialIsInvalid() throws Exception {
String groupName = "group_name";
String md5 = "md5";
cruiseConfig = new BasicCruiseConfig();
expectLoad(cruiseConfig);
new GoConfigMother().addPipelineWithGroup(cruiseConfig, groupName, "pipeline_name", "stage_name", "job_name");
expectLoadForEditing(cruiseConfig);
when(goConfigDao.md5OfConfigFile()).thenReturn(md5);
String pipelineGroupContent = groupXmlWithInvalidElement(groupName);
GoConfigValidity validity = goConfigService.groupSaver(groupName).saveXml(pipelineGroupContent, "md5");
assertThat(validity.isValid(), Matchers.is(false));
assertThat(validity.errorMessage(), containsString("Invalid content was found starting with element 'unknown'"));
verify(goConfigDao, never()).updateConfig(any(UpdateConfigCommand.class));
}
@Test
public void shouldReturnInvalidWhenPipelineGroupPartialHasInvalidAttributeValue() throws Exception {
String groupName = "group_name";
String md5 = "md5";
cruiseConfig = new BasicCruiseConfig();
expectLoad(cruiseConfig);
new GoConfigMother().addPipelineWithGroup(cruiseConfig, groupName, "pipeline_name", "stage_name", "job_name");
expectLoadForEditing(cruiseConfig);
when(goConfigDao.md5OfConfigFile()).thenReturn(md5);
String pipelineGroupContent = groupXmlWithInvalidAttributeValue(groupName);
GoConfigValidity validity = goConfigService.groupSaver(groupName).saveXml(pipelineGroupContent, "md5");
assertThat(validity.isValid(), Matchers.is(false));
assertThat(validity.errorMessage(), containsString("Name is invalid. \"pipeline@$^\""));
verify(goConfigDao, never()).updateConfig(any(UpdateConfigCommand.class));
}
@Test
public void shouldReturnInvalidWhenPipelineGroupPartialXmlIsInvalid() throws Exception {
String groupName = "group_name";
String md5 = "md5";
cruiseConfig = new BasicCruiseConfig();
expectLoad(cruiseConfig);
new GoConfigMother().addPipelineWithGroup(cruiseConfig, groupName, "pipeline_name", "stage_name", "job_name");
expectLoadForEditing(cruiseConfig);
when(goConfigDao.md5OfConfigFile()).thenReturn(md5);
GoConfigValidity validity = goConfigService.groupSaver(groupName).saveXml("<foobar>", "md5");
assertThat(validity.isValid(), Matchers.is(false));
assertThat(validity.errorMessage(), containsString("XML document structures must start and end within the same entity"));
verify(goConfigDao, never()).updateConfig(any(UpdateConfigCommand.class));
}
@Test
public void shouldFindConfigChangesForGivenConfigMd5() throws Exception {
goConfigService.configChangesFor("md5-5", "md5-4", new HttpLocalizedOperationResult());
verify(configRepo).configChangesFor("md5-5", "md5-4");
}
@Test
public void shouldUpdateResultAsConfigRevisionNotFoundWhenConfigChangeIsNotFound() throws Exception {
HttpLocalizedOperationResult result = new HttpLocalizedOperationResult();
Localizer localizer = mock(Localizer.class);
when(configRepo.configChangesFor("md5-5", "md5-4")).thenThrow(new IllegalArgumentException("something"));
goConfigService.configChangesFor("md5-5", "md5-4", result);
assertThat(result.isSuccessful(), is(false));
assertThat(result.httpCode(), is(SC_BAD_REQUEST));
result.message(localizer);
verify(localizer).localize("CONFIG_VERSION_NOT_FOUND", new Object[]{});
}
@Test
public void shouldUpdateResultAsCouldNotRetrieveConfigDiffWhenGenericExceptionOccurs() throws Exception {
HttpLocalizedOperationResult result = new HttpLocalizedOperationResult();
Localizer localizer = mock(Localizer.class);
when(configRepo.configChangesFor("md5-5", "md5-4")).thenThrow(new RuntimeException("something"));
goConfigService.configChangesFor("md5-5", "md5-4", result);
assertThat(result.isSuccessful(), is(false));
assertThat(result.httpCode(), is(SC_INTERNAL_SERVER_ERROR));
result.message(localizer);
verify(localizer).localize("COULD_NOT_RETRIEVE_CONFIG_DIFF", new Object[]{});
}
@Test
public void shouldReturnWasMergedInConfigUpdateResponse_WhenConfigIsMerged() {
when(goConfigDao.updateConfig(org.mockito.Matchers.<UpdateConfigCommand>any())).thenReturn(ConfigSaveState.MERGED);
ConfigUpdateResponse configUpdateResponse = goConfigService.updateConfigFromUI(mock(UpdateConfigFromUI.class), "md5", new Username(new CaseInsensitiveString("user")),
new HttpLocalizedOperationResult());
assertThat(configUpdateResponse.wasMerged(), is(true));
}
@Test
public void shouldReturnNotMergedInConfigUpdateResponse_WhenConfigIsUpdated() {
when(goConfigDao.updateConfig(org.mockito.Matchers.<UpdateConfigCommand>any())).thenReturn(ConfigSaveState.UPDATED);
ConfigUpdateResponse configUpdateResponse = goConfigService.updateConfigFromUI(mock(UpdateConfigFromUI.class), "md5", new Username(new CaseInsensitiveString("user")),
new HttpLocalizedOperationResult());
assertThat(configUpdateResponse.wasMerged(), is(false));
}
@Test
public void shouldReturnNotMergedInConfigUpdateResponse_WhenConfigUpdateFailed() throws Exception {
when(goConfigDao.updateConfig(org.mockito.Matchers.<UpdateConfigCommand>any())).thenThrow(new ConfigFileHasChangedException());
expectLoadForEditing(cruiseConfig);
ConfigUpdateResponse configUpdateResponse = goConfigService.updateConfigFromUI(mock(UpdateConfigFromUI.class), "md5", new Username(new CaseInsensitiveString("user")),
new HttpLocalizedOperationResult());
assertThat(configUpdateResponse.wasMerged(), is(false));
}
@Test
public void badConfigShouldContainOldMD5_WhenConfigUpdateFailed(){
when(goConfigDao.updateConfig(org.mockito.Matchers.<UpdateConfigCommand>any())).thenThrow(new RuntimeException(getGoConfigInvalidException()));
ConfigUpdateResponse configUpdateResponse = goConfigService.updateConfigFromUI(mock(UpdateConfigFromUI.class), "old-md5", new Username(new CaseInsensitiveString("user")),
new HttpLocalizedOperationResult());
assertThat(configUpdateResponse.wasMerged(), is(false));
assertThat(configUpdateResponse.getCruiseConfig().getMd5(), is("old-md5"));
}
@Test
public void configShouldContainOldMD5_WhenConfigMergeFailed(){
when(goConfigDao.loadForEditing()).thenReturn(new BasicCruiseConfig());
when(goConfigDao.updateConfig(org.mockito.Matchers.<UpdateConfigCommand>any())).thenThrow(new ConfigFileHasChangedException());
ConfigUpdateResponse configUpdateResponse = goConfigService.updateConfigFromUI(mock(UpdateConfigFromUI.class), "old-md5", new Username(new CaseInsensitiveString("user")),
new HttpLocalizedOperationResult());
assertThat(configUpdateResponse.wasMerged(), is(false));
assertThat(configUpdateResponse.getCruiseConfig().getMd5(), is("old-md5"));
}
@Test
public void shouldReturnConfigStateFromDaoLayer_WhenUpdatingServerConfig() {
ConfigSaveState expectedSaveState = ConfigSaveState.MERGED;
when(goConfigDao.updateConfig(org.mockito.Matchers.<UpdateConfigCommand>any())).thenReturn(expectedSaveState);
ConfigSaveState configSaveState = goConfigService.updateServerConfig(new MailHost(new GoCipher()), null, null, true, "md5", null, null, null, null, "http://site",
"https://site", "location");
assertThat(configSaveState, is(expectedSaveState));
}
@Test
public void shouldDelegateToConfig_getAllPipelinesInGroup() throws Exception {
CruiseConfig cruiseConfig = mock(BasicCruiseConfig.class);
expectLoad(cruiseConfig);
goConfigService.getAllPipelinesInGroup("group");
verify(cruiseConfig).pipelines("group");
}
@Test
public void shouldNotUpdatePipelineSelectionsWhenTheUserIsAnonymousAndHasNeverSelectedPipelines() {
goConfigService.updateUserPipelineSelections(null, null, new CaseInsensitiveString("pipelineNew"));
verify(pipelineRepository, times(0)).saveSelectedPipelines(argThat(Matchers.any(PipelineSelections.class)));
}
@Test
public void shouldNotUpdatePipelineSelectionsWhenTheUserIsAnonymousAndHasSelectedPipelines_WithBlacklist() {
when(pipelineRepository.findPipelineSelectionsById("1")).thenReturn(new PipelineSelections(Arrays.asList("pipeline1", "pipeline2"), null, null, true));
goConfigService.updateUserPipelineSelections("1", null, new CaseInsensitiveString("pipelineNew"));
verify(pipelineRepository).findPipelineSelectionsById("1");
verify(pipelineRepository, times(0)).saveSelectedPipelines(argThat(Matchers.any(PipelineSelections.class)));
}
@Test
public void shouldUpdatePipelineSelectionsWhenTheUserIsAnonymousAndHasSelectedPipelines_WithWhitelist() {
when(pipelineRepository.findPipelineSelectionsById("1")).thenReturn(new PipelineSelections(Arrays.asList("pipeline1", "pipeline2"), null, null, false));
goConfigService.updateUserPipelineSelections("1", null, new CaseInsensitiveString("pipelineNew"));
verify(pipelineRepository).findPipelineSelectionsById("1");
verify(pipelineRepository, times(1)).saveSelectedPipelines(argThat(isAPipelineSelectionsInstanceWith(false, "pipeline1", "pipeline2", "pipelineNew")));
}
@Test
public void shouldNotUpdatePipelineSelectionsWhenTheUserIsLoggedIn_WithBlacklist() {
mockConfigWithSecurity();
when(pipelineRepository.findPipelineSelectionsByUserId(1L)).thenReturn(new PipelineSelections(Arrays.asList("pipeline1", "pipeline2"), null, null, true));
goConfigService.updateUserPipelineSelections(null, 1L, new CaseInsensitiveString("pipelineNew"));
verify(pipelineRepository).findPipelineSelectionsByUserId(1L);
verify(pipelineRepository, times(0)).saveSelectedPipelines(argThat(Matchers.any(PipelineSelections.class)));
}
@Test
public void shouldUpdatePipelineSelectionsWhenTheUserIsLoggedIn_WithWhitelist() {
mockConfigWithSecurity();
when(pipelineRepository.findPipelineSelectionsByUserId(1L)).thenReturn(new PipelineSelections(Arrays.asList("pipeline1", "pipeline2"), null, null, false));
goConfigService.updateUserPipelineSelections(null, 1L, new CaseInsensitiveString("pipelineNew"));
verify(pipelineRepository).findPipelineSelectionsByUserId(1L);
verify(pipelineRepository, times(1)).saveSelectedPipelines(argThat(isAPipelineSelectionsInstanceWith(false, "pipeline1", "pipeline2", "pipelineNew")));
}
@Test
public void pipelineEditableViaUI_shouldReturnFalseWhenPipelineIsRemote() throws Exception
{
PipelineConfigs group = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
pipelineConfig.setOrigin(new RepoConfigOrigin());
group.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(group));
assertThat(goConfigService.isPipelineEditableViaUI("pipeline"), is(false));
}
@Test
public void pipelineEditableViaUI_shouldReturnTrueWhenPipelineIsLocal() throws Exception
{
PipelineConfigs group = new BasicPipelineConfigs();
PipelineConfig pipelineConfig = createPipelineConfig("pipeline", "name", "plan");
group.add(pipelineConfig);
expectLoad(new BasicCruiseConfig(group));
assertThat(goConfigService.isPipelineEditableViaUI("pipeline"), is(true));
}
@Test
public void shouldTellIfAnUserIsAdministrator() throws Exception {
final Username user = new Username(new CaseInsensitiveString("user"));
expectLoad(mock(BasicCruiseConfig.class));
goConfigService.isAdministrator(user.getUsername());
verify(goConfigDao.load()).isAdministrator(user.getUsername().toString());
}
@Test
public void shouldBeAbleToListAllDependencyMaterialConfigs() {
BasicCruiseConfig config = mock(BasicCruiseConfig.class);
DependencyMaterialConfig dependencyMaterialConfig = MaterialConfigsMother.dependencyMaterialConfig();
SvnMaterialConfig svnMaterialConfig = MaterialConfigsMother.svnMaterialConfig();
PluggableSCMMaterialConfig pluggableSCMMaterialConfig = MaterialConfigsMother.pluggableSCMMaterialConfig();
HashSet<MaterialConfig> materialConfigs = new HashSet<>(Arrays.asList(dependencyMaterialConfig, svnMaterialConfig, pluggableSCMMaterialConfig));
when(goConfigService.getCurrentConfig()).thenReturn(config);
when(config.getAllUniqueMaterialsBelongingToAutoPipelinesAndConfigRepos()).thenReturn(materialConfigs);
Set<DependencyMaterialConfig> schedulableDependencyMaterials = goConfigService.getSchedulableDependencyMaterials();
assertThat(schedulableDependencyMaterials.size(), is(1));
assertTrue(schedulableDependencyMaterials.contains(dependencyMaterialConfig));
}
@Test
public void shouldBeAbleToListAllSCMMaterialConfigs() {
BasicCruiseConfig config = mock(BasicCruiseConfig.class);
DependencyMaterialConfig dependencyMaterialConfig = MaterialConfigsMother.dependencyMaterialConfig();
SvnMaterialConfig svnMaterialConfig = MaterialConfigsMother.svnMaterialConfig();
PluggableSCMMaterialConfig pluggableSCMMaterialConfig = MaterialConfigsMother.pluggableSCMMaterialConfig();
HashSet<MaterialConfig> materialConfigs = new HashSet<>(Arrays.asList(dependencyMaterialConfig, svnMaterialConfig, pluggableSCMMaterialConfig));
when(goConfigService.getCurrentConfig()).thenReturn(config);
when(config.getAllUniqueMaterialsBelongingToAutoPipelinesAndConfigRepos()).thenReturn(materialConfigs);
Set<MaterialConfig> schedulableDependencyMaterials = goConfigService.getSchedulableSCMMaterials();
assertThat(schedulableDependencyMaterials.size(), is(2));
assertTrue(schedulableDependencyMaterials.contains(svnMaterialConfig));
assertTrue(schedulableDependencyMaterials.contains(pluggableSCMMaterialConfig));
}
private PipelineConfig createPipelineConfig(String pipelineName, String stageName, String... buildNames) {
PipelineConfig pipeline = new PipelineConfig(new CaseInsensitiveString(pipelineName), new MaterialConfigs());
pipeline.add(new StageConfig(new CaseInsensitiveString(stageName), jobConfigs(buildNames)));
return pipeline;
}
private JobConfigs jobConfigs(String... buildNames) {
JobConfigs jobConfigs = new JobConfigs();
for (String buildName : buildNames) {
jobConfigs.add(new JobConfig(buildName));
}
return jobConfigs;
}
private GoConfigService goConfigServiceWithInvalidStatus() throws Exception {
goConfigDao = mock(GoConfigDao.class, "badCruiseConfigManager");
when(goConfigDao.checkConfigFileValid()).thenReturn(GoConfigValidity.invalid(new JDOMParseException("JDom exception", new RuntimeException())));
return new GoConfigService(goConfigDao, pipelineRepository, new SystemTimeClock(), mock(GoConfigMigration.class), goCache, null,
ConfigElementImplementationRegistryMother.withNoPlugins(), instanceFactory, null, null);
}
private CruiseConfig mockConfig() {
CruiseConfig config = configWith(
createGroup("group0", pipelineConfig("pipeline1"), pipelineConfig("pipeline2")),
createGroup("group1", pipelineConfig("pipelineX")),
createGroup("group2", pipelineConfig("pipeline3")));
when(goConfigDao.load()).thenReturn(config);
return config;
}
private CruiseConfig mockConfigWithSecurity() {
CruiseConfig config = mockConfig();
config.server().useSecurity(new SecurityConfig(null, new PasswordFileConfig("path"), true));
return config;
}
private GoConfigInvalidException getGoConfigInvalidException() {
ConfigErrors configErrors = new ConfigErrors();
configErrors.add("command", "command cannot be empty");
AllConfigErrors list = new AllConfigErrors();
list.add(configErrors);
return new GoConfigInvalidException(new BasicCruiseConfig(), list.asString());
}
private Matcher<UpdateConfigCommand> cruiseConfigIsUpdatedWith(final String groupName, final String newPipelineName, final String labelTemplate) {
return new Matcher<UpdateConfigCommand>() {
@Override
public boolean matches(Object item) {
UpdateConfigCommand configCommand = (UpdateConfigCommand) item;
CruiseConfig updatedConfig = null;
try {
updatedConfig = configCommand.update(new BasicCruiseConfig());
} catch (Exception e) {
Assert.fail(String.format("Updating config through exception : %s", e));
}
PipelineConfigs group = updatedConfig.findGroup(groupName);
PipelineConfig pipeline = group.findBy(new CaseInsensitiveString(newPipelineName));
assertThat(pipeline.name(), is(new CaseInsensitiveString(newPipelineName)));
assertThat(pipeline.getLabelTemplate(), is(labelTemplate));
assertThat(pipeline.materialConfigs().first(), is(IsInstanceOf.instanceOf(SvnMaterialConfig.class)));
assertThat(pipeline.materialConfigs().first().getUriForDisplay(), is("file:///tmp/foo"));
return true;
}
@Override
public void describeMismatch(Object o, Description description) {
description.appendText("There was a mismatch!");
}
@Override
public void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
}
@Override
public void describeTo(Description description) {
}
};
}
private User getUser(final String userName, long id) {
long userId = id;
User user = new User(userName);
user.setId(userId);
when(userDao.findUser(userName)).thenReturn(user);
return user;
}
private Matcher<PipelineSelections> hasValues(final List<String> isVisible, final List<String> isNotVisible, final Date today, final Long userId) {
return new BaseMatcher<PipelineSelections>() {
public boolean matches(Object o) {
PipelineSelections pipelineSelections = (PipelineSelections) o;
assertHasSelected(pipelineSelections, isVisible);
assertHasSelected(pipelineSelections, isNotVisible, false);
assertThat(pipelineSelections.lastUpdated(), is(today));
assertThat(pipelineSelections.userId(), is(userId));
return true;
}
public void describeTo(Description description) {
}
};
}
private Matcher<PipelineSelections> isAPipelineSelectionsInstanceWith(final boolean isBlacklist, final String... pipelineSelectionsInInstance) {
return new BaseMatcher<PipelineSelections>() {
public boolean matches(Object o) {
PipelineSelections pipelineSelections = (PipelineSelections) o;
assertThat(pipelineSelections.isBlacklist(), is(isBlacklist));
List<String> expectedSelectionsAsList = Arrays.asList(pipelineSelectionsInInstance);
assertEquals(pipelineSelections.getSelections(), ListUtil.join(expectedSelectionsAsList, ","));
return true;
}
public void describeTo(Description description) {
}
};
}
private void assertHasSelected(PipelineSelections pipelineSelections, List<String> pipelines) {
assertHasSelected(pipelineSelections, pipelines, true);
}
private void assertHasSelected(PipelineSelections pipelineSelections, List<String> pipelines, boolean has) {
String message = "Expected: " + pipelines + " to include " + pipelineSelections + ": (" + has + ").";
for (String pipeline : pipelines) {
assertThat(message + ". Failed to find: " + pipeline, pipelineSelections.includesPipeline(pipelineConfig(pipeline)), is(has));
}
}
private String groupXml(final String groupName) {
return "<pipelines group=\"" + groupName + "\">\n"
+ " <pipeline name=\"new_name\" labeltemplate=\"${COUNT}-#{foo}\">\n"
+ " <params>\n"
+ " <param name=\"foo\">test</param>\n"
+ " </params>"
+ " <materials>\n"
+ " <svn url=\"file:///tmp/foo\" />\n"
+ " </materials>\n"
+ " <stage name=\"stage_name\">\n"
+ " <jobs>\n"
+ " <job name=\"job_name\" />\n"
+ " </jobs>\n"
+ " </stage>\n"
+ " </pipeline>\n"
+ "</pipelines>";
}
private String groupXmlWithInvalidElement(final String groupName) {
return "<pipelines group='" + groupName + "'>"
+ " <unknown/>"
+ "<pipeline name='pipeline'>\n"
+ " <materials>\n"
+ " <svn url ='svnurl' dest='a'/>\n"
+ " </materials>\n"
+ " <stage name='firstStage'>"
+ " <jobs>"
+ " <job name='jobName'/>"
+ " </jobs>"
+ " </stage>"
+ "</pipeline>"
+ "</pipelines>";
}
private String groupXmlWithInvalidAttributeValue(final String groupName) {
return "<pipelines group='" + groupName + "'>"
+ "<pipeline name='pipeline@$^'>\n"
+ " <materials>\n"
+ " <svn url ='svnurl' dest='a'/>\n"
+ " </materials>\n"
+ " <stage name='firstStage'>"
+ " <jobs>"
+ " <job name='jobName'/>"
+ " </jobs>"
+ " </stage>"
+ "</pipeline>"
+ "</pipelines>";
}
}