/*
* Copyright 2015 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.thoughtworks.go.domain.cctray;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.domain.activity.ProjectStatus;
import com.thoughtworks.go.domain.cctray.viewers.AllowedViewers;
import com.thoughtworks.go.domain.cctray.viewers.Viewers;
import com.thoughtworks.go.helper.GoConfigMother;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import static com.thoughtworks.go.util.DataStructureUtils.m;
import static com.thoughtworks.go.util.DataStructureUtils.s;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
public class CcTrayConfigChangeHandlerTest {
@Mock
private CcTrayCache cache;
@Mock
private CcTrayStageStatusLoader stageStatusLoader;
@Mock
private CcTrayViewAuthority ccTrayViewAuthority;
@Captor
ArgumentCaptor<List<ProjectStatus>> statusesCaptor;
private GoConfigMother goConfigMother;
private CcTrayConfigChangeHandler handler;
@Before
public void setUp() throws Exception {
initMocks(this);
goConfigMother = new GoConfigMother();
handler = new CcTrayConfigChangeHandler(cache, stageStatusLoader, ccTrayViewAuthority);
}
@Test
public void shouldProvideCCTrayCacheWithAListOfAllProjectsInOrder() throws Exception {
ProjectStatus pipeline1_stage1 = new ProjectStatus("pipeline1 :: stage", "Activity1", "Status1", "Label1", new Date(), "stage1-url");
ProjectStatus pipeline1_stage1_job = new ProjectStatus("pipeline1 :: stage :: job", "Activity1-Job", "Status1-Job", "Label1-Job", new Date(), "job1-url");
ProjectStatus pipeline2_stage1 = new ProjectStatus("pipeline2 :: stage", "Activity2", "Status2", "Label2", new Date(), "stage2-url");
ProjectStatus pipeline2_stage1_job = new ProjectStatus("pipeline2 :: stage :: job", "Activity2-Job", "Status2-Job", "Label2-Job", new Date(), "job2-url");
when(cache.get("pipeline1 :: stage")).thenReturn(pipeline1_stage1);
when(cache.get("pipeline1 :: stage :: job")).thenReturn(pipeline1_stage1_job);
when(cache.get("pipeline2 :: stage")).thenReturn(pipeline2_stage1);
when(cache.get("pipeline2 :: stage :: job")).thenReturn(pipeline2_stage1_job);
handler.call(GoConfigMother.configWithPipelines("pipeline2", "pipeline1")); /* Adds pipeline1 first in config. Then pipeline2. */
verify(cache).replaceAllEntriesInCacheWith(eq(asList(pipeline1_stage1, pipeline1_stage1_job, pipeline2_stage1, pipeline2_stage1_job)));
verifyZeroInteractions(stageStatusLoader);
}
@Test
public void shouldPopulateNewCacheWithProjectsFromOldCacheWhenTheyExist() throws Exception {
String stageProjectName = "pipeline1 :: stage";
String jobProjectName = "pipeline1 :: stage :: job";
ProjectStatus existingStageStatus = new ProjectStatus(stageProjectName, "OldActivity", "OldStatus", "OldLabel", new Date(), "stage-url");
when(cache.get(stageProjectName)).thenReturn(existingStageStatus);
ProjectStatus existingJobStatus = new ProjectStatus(jobProjectName, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job-url");
when(cache.get(jobProjectName)).thenReturn(existingJobStatus);
handler.call(GoConfigMother.configWithPipelines("pipeline1"));
verify(cache).replaceAllEntriesInCacheWith(eq(asList(existingStageStatus, existingJobStatus)));
verifyZeroInteractions(stageStatusLoader);
}
@Test
public void shouldPopulateNewCacheWithStageAndJobFromDB_WhenAStageIsNotFoundInTheOldCache() throws Exception {
CruiseConfig config = GoConfigMother.configWithPipelines("pipeline1");
String stageProjectName = "pipeline1 :: stage";
String jobProjectName = "pipeline1 :: stage :: job";
ProjectStatus statusOfStageInDB = new ProjectStatus(stageProjectName, "OldActivity", "OldStatus", "OldLabel", new Date(), "stage-url");
ProjectStatus statusOfJobInDB = new ProjectStatus(jobProjectName, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job-url");
when(cache.get(stageProjectName)).thenReturn(null);
when(stageStatusLoader.getStatusesForStageAndJobsOf(pipelineConfigFor(config, "pipeline1"), stageConfigFor(config, "pipeline1", "stage")))
.thenReturn(asList(statusOfStageInDB, statusOfJobInDB));
handler.call(config);
verify(cache).replaceAllEntriesInCacheWith(eq(asList(statusOfStageInDB, statusOfJobInDB)));
}
@Test
public void shouldHandleNewStagesInConfig_ByReplacingStagesMissingInDBWithNullStagesAndJobs() throws Exception {
CruiseConfig config = new BasicCruiseConfig();
goConfigMother.addPipeline(config, "pipeline1", "stage1", "job1");
goConfigMother.addStageToPipeline(config, "pipeline1", "stage2", "job2");
String stage1ProjectName = "pipeline1 :: stage1";
String job1ProjectName = "pipeline1 :: stage1 :: job1";
String stage2ProjectName = "pipeline1 :: stage2";
String job2ProjectName = "pipeline1 :: stage2 :: job2";
ProjectStatus statusOfStage1InCache = new ProjectStatus(stage1ProjectName, "OldActivity", "OldStatus", "OldLabel", new Date(), "stage-url");
ProjectStatus statusOfJob1InCache = new ProjectStatus(job1ProjectName, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job1-url");
when(cache.get(stage1ProjectName)).thenReturn(statusOfStage1InCache);
when(cache.get(job1ProjectName)).thenReturn(statusOfJob1InCache);
when(cache.get(stage2ProjectName)).thenReturn(null);
when(stageStatusLoader.getStatusesForStageAndJobsOf(pipelineConfigFor(config, "pipeline1"), stageConfigFor(config, "pipeline1", "stage2")))
.thenReturn(Collections.<ProjectStatus>emptyList());
handler.call(config);
ProjectStatus expectedNullStatusForStage2 = new ProjectStatus.NullProjectStatus(stage2ProjectName);
ProjectStatus expectedNullStatusForJob2 = new ProjectStatus.NullProjectStatus(job2ProjectName);
verify(cache).replaceAllEntriesInCacheWith(eq(asList(statusOfStage1InCache, statusOfJob1InCache, expectedNullStatusForStage2, expectedNullStatusForJob2)));
}
/* Simulate adding a job, when server is down. DB does not know anything about that job. */
@Test
public void shouldHandleNewJobsInConfig_ByReplacingJobsMissingInDBWithNullJob() throws Exception {
CruiseConfig config = new BasicCruiseConfig();
goConfigMother.addPipeline(config, "pipeline1", "stage1", "job1", "NEW_JOB_IN_CONFIG");
String stage1ProjectName = "pipeline1 :: stage1";
String job1ProjectName = "pipeline1 :: stage1 :: job1";
String projectNameOfNewJob = "pipeline1 :: stage1 :: NEW_JOB_IN_CONFIG";
ProjectStatus statusOfStage1InDB = new ProjectStatus(stage1ProjectName, "OldActivity", "OldStatus", "OldLabel", new Date(), "stage-url");
ProjectStatus statusOfJob1InDB = new ProjectStatus(job1ProjectName, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job1-url");
when(cache.get(stage1ProjectName)).thenReturn(null);
when(stageStatusLoader.getStatusesForStageAndJobsOf(pipelineConfigFor(config, "pipeline1"), stageConfigFor(config, "pipeline1", "stage1")))
.thenReturn(asList(statusOfStage1InDB, statusOfJob1InDB));
handler.call(config);
ProjectStatus expectedNullStatusForNewJob = new ProjectStatus.NullProjectStatus(projectNameOfNewJob);
verify(cache).replaceAllEntriesInCacheWith(eq(asList(statusOfStage1InDB, statusOfJob1InDB, expectedNullStatusForNewJob)));
}
/* Simulate adding a job, in a running system. Cache has the stage info, but not the job info. */
@Test
public void shouldHandleNewJobsInConfig_ByReplacingJobsMissingInConfigWithNullJob() throws Exception {
String stage1ProjectName = "pipeline1 :: stage1";
String job1ProjectName = "pipeline1 :: stage1 :: job1";
String projectNameOfNewJob = "pipeline1 :: stage1 :: NEW_JOB_IN_CONFIG";
ProjectStatus statusOfStage1InCache = new ProjectStatus(stage1ProjectName, "OldActivity", "OldStatus", "OldLabel", new Date(), "stage-url");
ProjectStatus statusOfJob1InCache = new ProjectStatus(job1ProjectName, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job1-url");
when(cache.get(stage1ProjectName)).thenReturn(statusOfStage1InCache);
when(cache.get(job1ProjectName)).thenReturn(statusOfJob1InCache);
when(cache.get(projectNameOfNewJob)).thenReturn(null);
CruiseConfig config = new BasicCruiseConfig();
goConfigMother.addPipeline(config, "pipeline1", "stage1", "job1", "NEW_JOB_IN_CONFIG");
handler.call(config);
ProjectStatus expectedNullStatusForNewJob = new ProjectStatus.NullProjectStatus(projectNameOfNewJob);
verify(cache).replaceAllEntriesInCacheWith(eq(asList(statusOfStage1InCache, statusOfJob1InCache, expectedNullStatusForNewJob)));
verifyZeroInteractions(stageStatusLoader);
}
@Test
public void shouldRemoveExtraJobsFromCache_WhichAreNoLongerInConfig() throws Exception {
String stage1ProjectName = "pipeline1 :: stage1";
String job1ProjectName = "pipeline1 :: stage1 :: job1";
String projectNameOfJobWhichWillBeRemoved = "pipeline1 :: stage1 :: JOB_IN_OLD_CONFIG";
ProjectStatus statusOfStage1InCache = new ProjectStatus(stage1ProjectName, "OldActivity", "OldStatus", "OldLabel", new Date(), "stage-url");
ProjectStatus statusOfJob1InCache = new ProjectStatus(job1ProjectName, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job1-url");
ProjectStatus statusOfOldJobInCache = new ProjectStatus(projectNameOfJobWhichWillBeRemoved, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "job2-url");
when(cache.get(stage1ProjectName)).thenReturn(statusOfStage1InCache);
when(cache.get(job1ProjectName)).thenReturn(statusOfJob1InCache);
when(cache.get(projectNameOfJobWhichWillBeRemoved)).thenReturn(statusOfOldJobInCache);
CruiseConfig config = new BasicCruiseConfig();
goConfigMother.addPipeline(config, "pipeline1", "stage1", "job1");
handler.call(config);
verify(cache).replaceAllEntriesInCacheWith(eq(asList(statusOfStage1InCache, statusOfJob1InCache)));
verifyZeroInteractions(stageStatusLoader);
}
@Test
public void shouldUpdateViewPermissionsForEveryProjectBasedOnViewPermissionsOfTheGroup() throws Exception {
ProjectStatus pipeline1_stage1 = new ProjectStatus("pipeline1 :: stage", "Activity1", "Status1", "Label1", new Date(), "stage1-url");
ProjectStatus pipeline1_stage1_job = new ProjectStatus("pipeline1 :: stage :: job", "Activity1-Job", "Status1-Job", "Label1-Job", new Date(), "job1-url");
ProjectStatus pipeline2_stage2 = new ProjectStatus("pipeline2 :: stage", "Activity2", "Status2", "Label2", new Date(), "stage2-url");
ProjectStatus pipeline2_stage2_job = new ProjectStatus("pipeline2 :: stage :: job", "Activity2-Job", "Status2-Job", "Label2-Job", new Date(), "job2-url");
when(cache.get("pipeline1 :: stage1")).thenReturn(pipeline1_stage1);
when(cache.get("pipeline1 :: stage1 :: job1")).thenReturn(pipeline1_stage1_job);
when(cache.get("pipeline2 :: stage2")).thenReturn(pipeline2_stage2);
when(cache.get("pipeline2 :: stage2 :: job2")).thenReturn(pipeline2_stage2_job);
when(ccTrayViewAuthority.groupsAndTheirViewers()).thenReturn(m("group1", viewers("user1", "user2"), "group2", viewers("user3")));
CruiseConfig config = GoConfigMother.defaultCruiseConfig();
goConfigMother.addPipelineWithGroup(config, "group2", "pipeline2", "stage2", "job2");
goConfigMother.addPipelineWithGroup(config, "group1", "pipeline1", "stage1", "job1");
handler.call(config);
verify(cache).replaceAllEntriesInCacheWith(statusesCaptor.capture());
List<ProjectStatus> statuses = statusesCaptor.getValue();
assertThat(statuses.size(), is(4));
assertThat(statuses.get(0).name(), is("pipeline1 :: stage1"));
assertThat(statuses.get(0).canBeViewedBy("user1"), is(true));
assertThat(statuses.get(0).canBeViewedBy("user2"), is(true));
assertThat(statuses.get(0).canBeViewedBy("user3"), is(false));
assertThat(statuses.get(1).name(), is("pipeline1 :: stage1 :: job1"));
assertThat(statuses.get(1).canBeViewedBy("user1"), is(true));
assertThat(statuses.get(1).canBeViewedBy("user2"), is(true));
assertThat(statuses.get(1).canBeViewedBy("user3"), is(false));
assertThat(statuses.get(2).name(), is("pipeline2 :: stage2"));
assertThat(statuses.get(2).canBeViewedBy("user1"), is(false));
assertThat(statuses.get(2).canBeViewedBy("user2"), is(false));
assertThat(statuses.get(2).canBeViewedBy("user3"), is(true));
assertThat(statuses.get(3).name(), is("pipeline2 :: stage2 :: job2"));
assertThat(statuses.get(3).canBeViewedBy("user1"), is(false));
assertThat(statuses.get(3).canBeViewedBy("user2"), is(false));
assertThat(statuses.get(3).canBeViewedBy("user3"), is(true));
}
@Test
public void shouldUpdateCacheWithPipelineDetailsWhenPipelineConfigChanges(){
String pipeline1Stage = "pipeline1 :: stage1";
String pipeline1job = "pipeline1 :: stage1 :: job1";
String pipeline2stage = "pipeline2 :: stage1";
String pipeline2job = "pipeline2 :: stage1 :: job1";
ProjectStatus statusOfPipeline1StageInCache = new ProjectStatus(pipeline1Stage, "OldActivity", "OldStatus", "OldLabel", new Date(), "p1-stage-url");
ProjectStatus statusOfPipeline1JobInCache = new ProjectStatus(pipeline1job, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "p1-job-url");
ProjectStatus statusOfPipeline2StageInCache = new ProjectStatus(pipeline1Stage, "OldActivity", "OldStatus", "OldLabel", new Date(), "p2-stage-url");
ProjectStatus statusOfPipeline2JobInCache = new ProjectStatus(pipeline1job, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "p2-job2-url");
when(cache.get(pipeline1Stage)).thenReturn(statusOfPipeline1StageInCache);
when(cache.get(pipeline1job)).thenReturn(statusOfPipeline1JobInCache);
when(cache.get(pipeline2stage)).thenReturn(statusOfPipeline2StageInCache);
when(cache.get(pipeline2job)).thenReturn(statusOfPipeline2JobInCache);
PipelineConfig pipeline1Config = GoConfigMother.pipelineHavingJob("pipeline1", "stage1", "job1", "arts", "dir").pipelineConfigByName(new CaseInsensitiveString("pipeline1"));
handler.call(pipeline1Config, "group1");
ArgumentCaptor<ArrayList<ProjectStatus>> argumentCaptor = new ArgumentCaptor<>();
verify(cache).putAll(argumentCaptor.capture());
List<ProjectStatus> allValues = argumentCaptor.getValue();
assertThat(allValues.get(0).name(), is(pipeline1Stage));
assertThat(allValues.get(1).name(), is(pipeline1job));
verify(cache, atLeastOnce()).get(pipeline1Stage);
verify(cache, atLeastOnce()).get(pipeline1job);
verifyNoMoreInteractions(cache);
}
@Test
public void shouldUpdateCacheWithAppropriateViewersForProjectStatusWhenPipelineConfigChanges(){
String pipeline1Stage = "pipeline1 :: stage1";
String pipeline1job = "pipeline1 :: stage1 :: job1";
ProjectStatus statusOfPipeline1StageInCache = new ProjectStatus(pipeline1Stage, "OldActivity", "OldStatus", "OldLabel", new Date(), "p1-stage-url");
ProjectStatus statusOfPipeline1JobInCache = new ProjectStatus(pipeline1job, "OldActivity-Job", "OldStatus-Job", "OldLabel-Job", new Date(), "p1-job-url");
when(cache.get(pipeline1Stage)).thenReturn(statusOfPipeline1StageInCache);
when(cache.get(pipeline1job)).thenReturn(statusOfPipeline1JobInCache);
when(ccTrayViewAuthority.groupsAndTheirViewers()).thenReturn(m("group1", viewers("user1", "user2"), "group2", viewers("user3")));
PipelineConfig pipeline1Config = GoConfigMother.pipelineHavingJob("pipeline1", "stage1", "job1", "arts", "dir").pipelineConfigByName(new CaseInsensitiveString("pipeline1"));
handler.call(pipeline1Config, "group1");
ArgumentCaptor<ArrayList<ProjectStatus>> argumentCaptor = new ArgumentCaptor<>();
verify(cache).putAll(argumentCaptor.capture());
List<ProjectStatus> allValues = argumentCaptor.getValue();
assertThat(allValues.get(0).name(), is(pipeline1Stage));
assertThat(allValues.get(0).viewers().contains("user1"), is(true));
assertThat(allValues.get(0).viewers().contains("user2"), is(true));
assertThat(allValues.get(0).viewers().contains("user3"), is(false));
assertThat(allValues.get(1).name(), is(pipeline1job));
assertThat(allValues.get(1).viewers().contains("user1"), is(true));
assertThat(allValues.get(1).viewers().contains("user2"), is(true));
assertThat(allValues.get(1).viewers().contains("user3"), is(false));
}
private Viewers viewers(String... users) {
return new AllowedViewers(s(users));
}
private PipelineConfig pipelineConfigFor(CruiseConfig config, String pipelineName) {
return config.pipelineConfigByName(new CaseInsensitiveString(pipelineName));
}
private StageConfig stageConfigFor(CruiseConfig config, String pipelineName, String stageName) {
return pipelineConfigFor(config, pipelineName).getStage(new CaseInsensitiveString(stageName));
}
}