/*
* Copyright 2016 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.config.preprocessor;
import java.util.Arrays;
import java.util.Collections;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.materials.AbstractMaterialConfig;
import com.thoughtworks.go.config.materials.ScmMaterialConfig;
import com.thoughtworks.go.config.materials.mercurial.HgMaterialConfig;
import com.thoughtworks.go.config.materials.perforce.P4MaterialConfig;
import com.thoughtworks.go.config.materials.perforce.P4MaterialViewConfig;
import com.thoughtworks.go.config.materials.svn.SvnMaterialConfig;
import com.thoughtworks.go.config.merge.MergePipelineConfigs;
import com.thoughtworks.go.helper.MaterialConfigsMother;
import com.thoughtworks.go.helper.PipelineConfigMother;
import org.junit.Before;
import org.junit.Test;
import static com.thoughtworks.go.util.ReflectionUtil.setField;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.isA;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
public class ParamResolverTest {
private ClassAttributeCache.FieldCache fieldCache;
@Before
public void setUp() throws Exception {
fieldCache = new ClassAttributeCache.FieldCache();
}
@Test
public void shouldResolve_ConfigValue_MappedAsObject() {
SecurityConfig securityConfig = new SecurityConfig();
securityConfig.adminsConfig().add(new AdminUser(new CaseInsensitiveString("lo#{foo}")));
securityConfig.addRole(new RoleConfig(new CaseInsensitiveString("boo#{bar}"), new RoleUser(new CaseInsensitiveString("choo#{foo}"))));
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "ser"), param("bar", "zer"))), fieldCache).resolve(securityConfig);
assertThat(CaseInsensitiveString.str(securityConfig.adminsConfig().get(0).getName()), is("loser"));
assertThat(CaseInsensitiveString.str(securityConfig.getRoles().get(0).getName()), is("boozer"));
assertThat(CaseInsensitiveString.str(securityConfig.getRoles().get(0).getUsers().get(0).getName()), is("chooser"));
}
@Test
public void shouldResolveTopLevelAttribute() {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}");
setField(pipelineConfig, "lock", "#{bool}ue");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bool", "tr"), param("COUNT", "quux"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}"));
assertThat(pipelineConfig.explicitLock(), is(true));
}
@Test
public void shouldNotTryToResolveNonStringAttributes() {//this tests replacement doesn't fail when non-string config-attributes are present, and non opt-out annotated
MailHost mailHost = new MailHost("host", 25, "loser", "passwd", true, false, "boozer@loser.com", "root@loser.com");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bool", "tr"))), fieldCache).resolve(mailHost);
}
@Test
public void shouldNotResolveOptedOutConfigAttributes() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise-#{foo}-#{bar}", "dev", "ant");
SvnMaterialConfig svn = (SvnMaterialConfig) pipelineConfig.materialConfigs().get(0);
svn.setPassword("#quux-#{foo}-#{bar}");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(pipelineConfig.name(), is(new CaseInsensitiveString("cruise-#{foo}-#{bar}")));
assertThat(((SvnMaterialConfig) pipelineConfig.materialConfigs().get(0)).getPassword(), is("#quux-#{foo}-#{bar}"));
assertThat(pipelineConfig.getClass().getDeclaredField("name").getAnnotation(SkipParameterResolution.class), isA(SkipParameterResolution.class));
}
@Test
public void shouldNotResolveOptedOutConfigSubtags() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
pipelineConfig.addParam(param("#{foo}-name", "#{foo}-#{bar}-baz"));
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(pipelineConfig.getParams().get(0), is(param("#{foo}-name", "#{foo}-#{bar}-baz")));
assertThat(pipelineConfig.getClass().getDeclaredField("params").getAnnotation(SkipParameterResolution.class), isA(SkipParameterResolution.class));
}
@Test
public void shouldNotInterpolateEscapedSequences() {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-##{foo}-bar-#{bar}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-#{foo}-bar-jj"));
}
@Test
public void shouldInterpolateLiteralEscapedSequences() {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-###{foo}-bar-#{bar}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-#pavan-bar-jj"));
}
@Test
public void shouldEscapeEscapedPatternStartSequences() {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#######{foo}-bar-####{bar}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-###pavan-bar-##{bar}"));
}
@Test
public void shouldNotRecursivelySubstituteParams() {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("#{foo}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "#{bar}"), param("bar", "baz"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("#{bar}"));
pipelineConfig.setLabelTemplate("#{foo}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "###"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("###"));
}
@Test
public void shouldResolveConfigValue() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
StageConfig stageConfig = pipelineConfig.get(0);
stageConfig.updateApproval(new Approval(new AuthConfig(new AdminUser(new CaseInsensitiveString("#{foo}")), new AdminUser(new CaseInsensitiveString("#{bar}")))));
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(stageConfig.getApproval().getAuthConfig(), is(new AuthConfig(new AdminUser(new CaseInsensitiveString("pavan")), new AdminUser(new CaseInsensitiveString("jj")))));
}
@Test
public void shouldResolveSubTags() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
TrackingTool trackingTool = new TrackingTool("http://#{foo}.com/#{bar}", "\\w+#{bar}");
pipelineConfig.setTrackingTool(trackingTool);
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(trackingTool.getLink(), is("http://pavan.com/jj"));
assertThat(trackingTool.getRegex(), is("\\w+jj"));
}
@Test
public void shouldResolveCollections() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
HgMaterialConfig materialConfig = MaterialConfigsMother.hgMaterialConfig("http://#{foo}.com/#{bar}");
pipelineConfig.addMaterialConfig(materialConfig);
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(pipelineConfig.materialConfigs().get(1).getUriForDisplay(), is("http://pavan.com/jj"));
}
@Test
public void shouldResolveInBasicPipelineConfigs() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
HgMaterialConfig materialConfig = MaterialConfigsMother.hgMaterialConfig("http://#{foo}.com/#{bar}");
pipelineConfig.addMaterialConfig(materialConfig);
BasicPipelineConfigs pipelines = new BasicPipelineConfigs(pipelineConfig);
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelines);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(pipelineConfig.materialConfigs().get(1).getUriForDisplay(), is("http://pavan.com/jj"));
}
@Test
public void shouldResolveInMergePipelineConfigs() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("2.1-${COUNT}-#{foo}-bar-#{bar}");
HgMaterialConfig materialConfig = MaterialConfigsMother.hgMaterialConfig("http://#{foo}.com/#{bar}");
pipelineConfig.addMaterialConfig(materialConfig);
MergePipelineConfigs merge = new MergePipelineConfigs(new BasicPipelineConfigs(),new BasicPipelineConfigs(pipelineConfig));
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(merge);
assertThat(pipelineConfig.getLabelTemplate(), is("2.1-${COUNT}-pavan-bar-jj"));
assertThat(pipelineConfig.materialConfigs().get(1).getUriForDisplay(), is("http://pavan.com/jj"));
}
@Test
public void shouldProvideContextWhenAnExceptionOccurs() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("#a");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.errors().on("labelTemplate"), is("Error when processing params for '#a' used in field 'labelTemplate', # must be followed by a parameter pattern or escaped by another #"));
}
@Test
public void shouldUseValidationErrorKeyAnnotationForFieldNameInCaseOfException() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant","nant");
FetchTask task = new FetchTask(new CaseInsensitiveString("cruise"), new CaseInsensitiveString("dev"), new CaseInsensitiveString("ant"), "#a", "dest");
pipelineConfig.get(0).getJobs().getJob(new CaseInsensitiveString("nant")).addTask(task);
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(task.errors().isEmpty(), is(false));
assertThat(task.errors().on(FetchTask.SRC), is("Error when processing params for '#a' used in field 'src', # must be followed by a parameter pattern or escaped by another #"));
}
@Test
public void shouldAddErrorTheMessageOnTheRightFieldOfTheRightElement() throws NoSuchFieldException {
Resource resource = new Resource();
resource.setName("#{not-found}");
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("#a");
pipelineConfig.get(0).getJobs().addJobWithoutValidityAssertion(new JobConfig(new CaseInsensitiveString("another"), new Resources(resource), new ArtifactPlans()));
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.errors().on("labelTemplate"), is("Error when processing params for '#a' used in field 'labelTemplate', # must be followed by a parameter pattern or escaped by another #"));
assertThat(resource.errors().on(JobConfig.RESOURCES), is("Parameter 'not-found' is not defined. All pipelines using this parameter directly or via a template must define it."));
}
@Test
public void shouldProvideContextWhenAnExceptionOccursBecauseOfHashAtEnd() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("abc#");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.errors().on("labelTemplate"), is("Error when processing params for 'abc#' used in field 'labelTemplate', # must be followed by a parameter pattern or escaped by another #"));
}
@Test
public void shouldProvideContextWhenAnExceptionOccursBecauseOfIncompleteParamAtEnd() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
pipelineConfig.setLabelTemplate("abc#{");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.errors().on("labelTemplate"), is("Incomplete param usage in 'abc#{'"));
}
@Test
public void shouldResolveInheritedAttributes() throws NoSuchFieldException {
PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
HgMaterialConfig materialConfig = MaterialConfigsMother.hgMaterialConfig();
materialConfig.setConfigAttributes(Collections.singletonMap(ScmMaterialConfig.FOLDER, "work/#{foo}/#{bar}/baz"));
pipelineConfig.addMaterialConfig(materialConfig);
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(pipelineConfig);
assertThat(pipelineConfig.materialConfigs().get(1).getFolder(), is("work/pavan/jj/baz"));
}
@Test
public void shouldAddResolutionErrorOnViewIfP4MaterialViewHasAnError() throws NoSuchFieldException {
P4MaterialViewConfig p4MaterialViewConfig = new P4MaterialViewConfig("#");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(p4MaterialViewConfig);
assertThat(p4MaterialViewConfig.errors().on(P4MaterialConfig.VIEW), is("Error when processing params for '#' used in field 'view', # must be followed by a parameter pattern or escaped by another #"));
}
@Test
public void shouldErrorOutIfCannotResolveParamForP4View() {
P4MaterialConfig p4MaterialConfig = new P4MaterialConfig("server:port", "#");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "pavan"), param("bar", "jj"))), fieldCache).resolve(p4MaterialConfig);
assertThat(p4MaterialConfig.getP4MaterialView().errors().on(P4MaterialConfig.VIEW), is("Error when processing params for '#' used in field 'view', # must be followed by a parameter pattern or escaped by another #"));
}
@Test
public void shouldLexicallyScopeTheParameters() throws NoSuchFieldException {
PipelineConfig withParams = PipelineConfigMother.createPipelineConfig("cruise", "dev", "ant");
withParams.addParam(param("foo", "pipeline"));
PipelineConfig withoutParams = PipelineConfigMother.createPipelineConfig("mingle", "dev", "ant");
CruiseConfig cruiseConfig = new BasicCruiseConfig();
cruiseConfig.addPipeline("group", withParams);
cruiseConfig.addPipeline("group", withoutParams);
cruiseConfig.server().setArtifactsDir("/#{foo}/#{bar}");
HgMaterialConfig materialConfig = MaterialConfigsMother.hgMaterialConfig();
materialConfig.setConfigAttributes(Collections.singletonMap(ScmMaterialConfig.FOLDER, "work/#{foo}/#{bar}/baz"));
withParams.addMaterialConfig(materialConfig);
withParams.setLabelTemplate("2.0.#{foo}-#{bar}");
withoutParams.setLabelTemplate("2.0.#{foo}-#{bar}");
new ParamResolver(new ParamSubstitutionHandlerFactory(params(param("foo", "global"), param("bar", "global-only"))), fieldCache).resolve(cruiseConfig);
assertThat(withParams.materialConfigs().get(1).getFolder(), is("work/pipeline/global-only/baz"));
assertThat(withParams.getLabelTemplate(), is("2.0.pipeline-global-only"));
assertThat(withoutParams.getLabelTemplate(), is("2.0.global-global-only"));
}
@Test
public void shouldSkipResolution() throws NoSuchFieldException {
Object[] specs = new Object[] {
BasicCruiseConfig.class, "serverConfig",
BasicCruiseConfig.class, "templatesConfig",
BasicCruiseConfig.class, "environments",
BasicCruiseConfig.class, "agents",
BasicPipelineConfigs.class, "authorization",
PipelineConfig.class, "name",
PipelineConfig.class, "params",
PipelineConfig.class, "templateName",
StageConfig.class, "name",
AbstractMaterialConfig.class, "name",
ArtifactPropertiesGenerator.class, "name",
Approval.class, "type",
JobConfig.class, "jobName",
RunIfConfig.class, "status",
};
for (int i = 0; i < specs.length; i += 2) {
Class clz = (Class) specs[i];
String field = (String) specs[i + 1];
assertSkipsResolution(clz, field);
}
}
private void assertSkipsResolution(Class clz, String fieldName) throws NoSuchFieldException {
assertThat(String.format("Field %s on class %s does not skip param resolution", clz.getName(), fieldName),
clz.getDeclaredField(fieldName).getAnnotation(SkipParameterResolution.class), is(not(nullValue())));
}
private ParamConfig param(String name, String value) {
return new ParamConfig(name, value);
}
private ParamsConfig params(ParamConfig... configs) {
ParamsConfig paramsConfig = new ParamsConfig();
paramsConfig.addAll(Arrays.asList(configs));
return paramsConfig;
}
}