/* * 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 java.net.URI; import java.net.URISyntaxException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import com.thoughtworks.go.config.CaseInsensitiveString; import com.thoughtworks.go.config.PipelineConfig; import com.thoughtworks.go.domain.DefaultSchedulingContext; import com.thoughtworks.go.domain.JobIdentifier; import com.thoughtworks.go.domain.MaterialRevision; import com.thoughtworks.go.domain.MaterialRevisions; import com.thoughtworks.go.domain.NotificationFilter; import com.thoughtworks.go.domain.Pipeline; import com.thoughtworks.go.domain.Stage; import com.thoughtworks.go.domain.StageConfigIdentifier; import com.thoughtworks.go.domain.StageEvent; import com.thoughtworks.go.domain.StageIdentifier; import com.thoughtworks.go.domain.User; import com.thoughtworks.go.domain.Users; import com.thoughtworks.go.domain.materials.Modification; import com.thoughtworks.go.domain.materials.ModifiedAction; import com.thoughtworks.go.domain.testinfo.TestStatus; import com.thoughtworks.go.domain.testinfo.TestSuite; import com.thoughtworks.go.helper.PipelineConfigMother; import com.thoughtworks.go.server.dao.sparql.ShineDao; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.server.messaging.InMemoryEmailNotificationTopic; import com.thoughtworks.go.util.SystemEnvironment; import com.thoughtworks.go.util.TimeProvider; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static com.thoughtworks.go.server.service.StageNotificationService.MATERIAL_SECTION_HEADER; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class StageNotificationServiceTest { private PipelineService pipelineService; private UserService userService; private SystemEnvironment systemEnvironment; private StageService stageService; private StageNotificationService stageNotificationService; private Pipeline pipeline; private InMemoryEmailNotificationTopic inMemoryEmailNotificationTopic; private StageIdentifier stageIdentifier; private ServerConfigService serverConfigService; private GoConfigService goConfigService; private ShineDao shineDao; private InstanceFactory instanceFactory; @Before public void setUp() { pipelineService = mock(PipelineService.class); userService = mock(UserService.class); systemEnvironment = mock(SystemEnvironment.class); stageService = mock(StageService.class); inMemoryEmailNotificationTopic = new InMemoryEmailNotificationTopic(); serverConfigService = mock(ServerConfigService.class); goConfigService = mock(GoConfigService.class); shineDao = mock(ShineDao.class); stageNotificationService = new StageNotificationService(pipelineService, userService, inMemoryEmailNotificationTopic, systemEnvironment, stageService, serverConfigService, shineDao); stageIdentifier = new StageIdentifier("go", 1, "go-1", "dev", "2"); instanceFactory = new InstanceFactory(); } @Test public void shouldSendEmailWithFailureDetails() throws Exception { final String expectedBaseUrl = String.format("http://test.host:8153"); String jezMail = prepareOneMatchedUser(); final Date date = new Date(); stubPipelineAndStage(date); final TestSuite suite1 = new TestSuite("com.thoughtworks.go.FailOne"); suite1.addTest("shouldCompile", TestStatus.Error, new JobIdentifier(stageIdentifier, "compile")); suite1.addTest("shouldPass", TestStatus.Failure, new JobIdentifier(stageIdentifier, "test")); suite1.addTest("shouldPass", TestStatus.Failure, new JobIdentifier(stageIdentifier, "twist")); suite1.addTest("shouldCompile2", TestStatus.Failure, new JobIdentifier(stageIdentifier, "compile")); final TestSuite suite2 = new TestSuite("com.thoughtworks.go.FailTwo"); suite2.addTest("shouldCompile", TestStatus.Error, new JobIdentifier(stageIdentifier, "test")); suite2.addTest("shouldTest", TestStatus.Failure, new JobIdentifier(stageIdentifier, "test")); when(serverConfigService.siteUrlFor(anyString(), eq(false))).thenAnswer(new Answer<String>() { public String answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); return morphURl((String)args[0], expectedBaseUrl); } }); when(systemEnvironment.isShineEnabled()).thenReturn(true); when(shineDao.failedTestsFor(stageIdentifier)).thenReturn(Arrays.asList(suite1, suite2)); stageNotificationService.sendNotifications(stageIdentifier, StageEvent.Fails, new Username(new CaseInsensitiveString("loser"))); String body = inMemoryEmailNotificationTopic.getBody(jezMail); assertThat(body, containsString(StageNotificationService.FAILED_TEST_SECTION)); String restOfThebody = textAfter(body, StageNotificationService.FAILED_TEST_SECTION); String failuresText = restOfThebody.substring(0, restOfThebody.indexOf(StageNotificationService.MATERIAL_SECTION_HEADER)); assertEquals("\n\nThe following tests failed in pipeline 'go' (instance 'go-1'):\n\n" + "* com.thoughtworks.go.FailOne\n" + " shouldCompile\n" + " Errored on 'compile' (" + expectedBaseUrl + "/go/tab/build/detail/go/1/dev/2/compile)\n" + " shouldCompile2\n" + " Failed on 'compile' (" + expectedBaseUrl + "/go/tab/build/detail/go/1/dev/2/compile)\n" + " shouldPass\n" + " Failed on 'test' (" + expectedBaseUrl + "/go/tab/build/detail/go/1/dev/2/test)\n" + " Failed on 'twist' (" + expectedBaseUrl + "/go/tab/build/detail/go/1/dev/2/twist)\n" + "\n\n* com.thoughtworks.go.FailTwo\n" + " shouldCompile\n" + " Errored on 'test' (" + expectedBaseUrl + "/go/tab/build/detail/go/1/dev/2/test)\n" + " shouldTest\n" + " Failed on 'test' (" + expectedBaseUrl + "/go/tab/build/detail/go/1/dev/2/test)\n\n\n", failuresText); } private String morphURl(String url, String expectedBaseUrl) throws URISyntaxException { URI uri = new URI(url); String blah = String.format("%s%s", expectedBaseUrl, uri.getPath()); return blah; } @Test public void shouldNotHaveFailedTestsSectionWhenThereAreNoFailedTests() { String jezMail = prepareOneMatchedUser(); stubPipelineAndStage(new Date()); when(systemEnvironment.isShineEnabled()).thenReturn(true); when(shineDao.failedTestsFor(stageIdentifier)).thenReturn(new ArrayList<>()); stageNotificationService.sendNotifications(stageIdentifier, StageEvent.Fails, new Username(new CaseInsensitiveString("loser"))); String body = inMemoryEmailNotificationTopic.getBody(jezMail); assertThat(body, not(containsString(StageNotificationService.FAILED_TEST_SECTION))); } @Test public void shouldHaveFailedTestsSectionWhenShineIsEnabledAndThereAreFailedTests() { String mail = prepareOneMatchedUser(); stubPipelineAndStage(new Date()); when(systemEnvironment.isShineEnabled()).thenReturn(true); ArrayList<TestSuite> testSuites = new ArrayList<>(); testSuites.add(new TestSuite("blah")); when(shineDao.failedTestsFor(stageIdentifier)).thenReturn(testSuites); stageNotificationService.sendNotifications(stageIdentifier, StageEvent.Fails, new Username(new CaseInsensitiveString("loser"))); String body = inMemoryEmailNotificationTopic.getBody(mail); assertThat(body, containsString(StageNotificationService.FAILED_TEST_SECTION)); } @Test public void shouldNotHaveFailedTestsSectionWhenShineIsDisabled() { String mail = prepareOneMatchedUser(); stubPipelineAndStage(new Date()); when(systemEnvironment.isShineEnabled()).thenReturn(false); stageNotificationService.sendNotifications(stageIdentifier, StageEvent.Fails, new Username(new CaseInsensitiveString("loser"))); String body = inMemoryEmailNotificationTopic.getBody(mail); assertThat(body, not(containsString(StageNotificationService.FAILED_TEST_SECTION))); } @Test public void shouldSendEmailWithModificationInfo() throws SQLException { String jezMail = prepareOneMatchedUser(); final Date date = new Date(); stubPipelineAndStage(date); stageNotificationService.sendNotifications(stageIdentifier, StageEvent.Fails, new Username(new CaseInsensitiveString("loser"))); String body = inMemoryEmailNotificationTopic.getBody(jezMail); assertThat(body, containsString(MATERIAL_SECTION_HEADER)); String materialBody = textAfter(body, MATERIAL_SECTION_HEADER); assertEquals("\n\nSubversion: http://some/svn/url\n" + String.format("revision: 123, modified by lgao on %s\n", date) + "Fixing the not checked in files\n" + "added build.xml\n" + "deleted some.xml\n\n" + "Sent by Go on behalf of jez", materialBody); } @Test public void shouldNotComputeFailedTestSuitesWhenThereAreNoSubscribers() throws Exception { when(userService.findValidSubscribers(stageIdentifier.stageConfigIdentifier())).thenReturn(new Users(new ArrayList<>())); stageNotificationService.sendNotifications(stageIdentifier, StageEvent.Fails, new Username(new CaseInsensitiveString("loser"))); verifyZeroInteractions(shineDao); verifyZeroInteractions(stageService); verifyZeroInteractions(pipelineService); } private String textAfter(String body, String sectionName) { return body.substring(body.indexOf(sectionName) + sectionName.length()); } private void stubPipelineAndStage(Date date) { final PipelineConfig pipelineConfig = PipelineConfigMother.createPipelineConfig("go", "dev", "compile", "test", "twist"); final Modification svnModification = new Modification("lgao", "Fixing the not checked in files", "jez@cruise.com", date, "123"); svnModification.createModifiedFile("build.xml", "some_dir", ModifiedAction.added); svnModification.createModifiedFile("some.xml", "other_dir", ModifiedAction.deleted); Pipeline pipeline = instanceFactory.createPipelineInstance(pipelineConfig, new ManualBuild(new Username(new CaseInsensitiveString("loser"))).onModifications(new MaterialRevisions(new MaterialRevision(new MaterialConfigConverter().toMaterials(pipelineConfig.materialConfigs()).get(0), svnModification)), false, null), new DefaultSchedulingContext("loser"), "md5-test", new TimeProvider()); Stage stage = pipeline.getStages().get(0); when(stageService.findStageWithIdentifier(stageIdentifier)).thenReturn(stage); stage.setPipelineId(100L); when(pipelineService.fullPipelineById(100)).thenReturn(pipeline); } private String prepareOneMatchedUser() { String jezMail = "jez@cruise.com"; User jez = new User("jez", new String[]{"lgao"}, jezMail, true); jez.setNotificationFilters(Arrays.asList(new NotificationFilter("go", "dev", StageEvent.All, true))); when(userService.findValidSubscribers(new StageConfigIdentifier("go", "dev"))).thenReturn(new Users(Arrays.asList(jez))); return jezMail; } }