package hudson.plugins.jira; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import hudson.MarkupText; import hudson.model.AbstractProject; import hudson.model.FreeStyleBuild; import java.io.IOException; import java.net.URL; import java.util.Collections; import java.util.regex.Pattern; import javax.xml.rpc.ServiceException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.jvnet.hudson.test.Bug; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.google.common.collect.Sets; /** * @author Kohsuke Kawaguchi */ public class JiraChangeLogAnnotatorTest { private static final String TITLE = "title with $sign to confuse TextMarkup.replace"; private JiraSite site; @Before public void before() throws IOException, ServiceException { JiraSession session = mock(JiraSession.class); when(session.getProjectKeys()).thenReturn( Sets.newHashSet("DUMMY", "HUDSON")); this.site = mock(JiraSite.class); when(site.createSession()).thenReturn(session); when(site.getUrl(Mockito.anyString())).thenAnswer( new Answer<URL>() { public URL answer(InvocationOnMock invocation) throws Throwable { String id = invocation.getArguments()[0].toString(); return new URL("http://dummy/" + id); } }); when(site.existsIssue(Mockito.anyString())).thenCallRealMethod(); when(site.getProjectKeys()).thenCallRealMethod(); when(site.getIssuePattern()).thenCallRealMethod(); } @Test public void testAnnotate() throws Exception { FreeStyleBuild b = mock(FreeStyleBuild.class); when(b.getAction(JiraBuildAction.class)).thenReturn(new JiraBuildAction(b, Collections.singleton(new JiraIssue("DUMMY-1", TITLE)))); MarkupText text = new MarkupText("marking up DUMMY-1."); JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); annotator.annotate(b,null, text); // make sure '$' didn't confuse the JiraChangeLogAnnotator Assert.assertTrue(text.toString(false).contains(TITLE)); } /** * Hudson's MarkupText#findTokens() doesn't work in our case if * the whole pattern matches the following word boundary character * (but not matching group 1). * * Regression test for this. */ @Test public void testWordBoundaryProblem() throws Exception { JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); FreeStyleBuild b = mock(FreeStyleBuild.class); // old changelog annotator used MarkupText#findTokens // That broke because of the space after the issue id. MarkupText text = new MarkupText("DUMMY-4071 Text "); annotator.annotate(b,null, text); Assert.assertEquals("<a href='http://dummy/DUMMY-4071'>DUMMY-4071</a> Text ", text.toString(false)); text = new MarkupText("DUMMY-1,comment"); annotator.annotate(b,null, text); Assert.assertEquals("<a href='http://dummy/DUMMY-1'>DUMMY-1</a>,comment", text.toString(false)); text = new MarkupText("DUMMY-1.comment"); annotator.annotate(b,null, text); Assert.assertEquals("<a href='http://dummy/DUMMY-1'>DUMMY-1</a>.comment", text.toString(false)); text = new MarkupText("DUMMY-1!comment"); annotator.annotate(b,null, text); Assert.assertEquals("<a href='http://dummy/DUMMY-1'>DUMMY-1</a>!comment", text.toString(false)); text = new MarkupText("DUMMY-1\tcomment"); annotator.annotate(b,null, text); Assert.assertEquals("<a href='http://dummy/DUMMY-1'>DUMMY-1</a>\tcomment", text.toString(false)); } @Test public void testMatchMultipleIssueIds() { JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); FreeStyleBuild b = mock(FreeStyleBuild.class); MarkupText text = new MarkupText("DUMMY-1 Text DUMMY-2,DUMMY-3 DUMMY-4!"); annotator.annotate(b, null, text); Assert.assertEquals("<a href='http://dummy/DUMMY-1'>DUMMY-1</a> Text " + "<a href='http://dummy/DUMMY-2'>DUMMY-2</a>," + "<a href='http://dummy/DUMMY-3'>DUMMY-3</a> " + "<a href='http://dummy/DUMMY-4'>DUMMY-4</a>!", text.toString(false)); } @Test @Bug(4132) public void testCaseInsensitiveAnnotate() throws IOException, ServiceException { Assert.assertTrue(site.existsIssue("HUDSON-123")); Assert.assertTrue(site.existsIssue("huDsOn-123")); Assert.assertTrue(site.existsIssue("dummy-4711")); JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); MarkupText text = new MarkupText("fixed DUMMY-42"); annotator.annotate(mock(FreeStyleBuild.class), null, text); Assert.assertEquals("fixed <a href='http://dummy/DUMMY-42'>DUMMY-42</a>", text.toString(false)); } /** * Tests that missing issues - i.e. issues not saved to build - * are fetched from remote. */ @Test @Bug(5252) public void testGetIssueDetailsForMissingIssues() throws IOException, ServiceException { FreeStyleBuild b = mock(FreeStyleBuild.class); JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); JiraIssue issue = new JiraIssue("DUMMY-42", TITLE); when(site.getIssue(Mockito.anyString())).thenReturn(issue); MarkupText text = new MarkupText("fixed DUMMY-42"); annotator.annotate(b, null, text); Assert.assertTrue(text.toString(false).contains(TITLE)); } /** * Tests that no exception is thrown if user issue pattern is invalid (contains no groups) */ @Test public void testInvalidUserPattern() throws IOException, ServiceException { when(site.getIssuePattern()).thenReturn(Pattern.compile("[a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*")); JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); FreeStyleBuild b = mock(FreeStyleBuild.class); MarkupText text = new MarkupText("fixed DUMMY-42"); annotator.annotate(b, null, text); Assert.assertFalse(text.toString(false).contains(TITLE)); } /** * Tests that only the 1st matching group is hyperlinked and not the whole pattern. * Previous implementation did so. */ @Test public void testMatchOnlyMatchGroup1() throws IOException, ServiceException { JiraChangeLogAnnotator annotator = spy(new JiraChangeLogAnnotator()); doReturn(site).when(annotator).getSiteForProject((AbstractProject<?, ?>) Mockito.any()); when(site.getIssuePattern()).thenReturn(Pattern.compile("([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)abc")); MarkupText text = new MarkupText("fixed DUMMY-42abc"); annotator.annotate(mock(FreeStyleBuild.class), null, text); Assert.assertEquals("fixed <a href='http://dummy/DUMMY-42'>DUMMY-42</a>abc", text.toString(false)); // check again when issue != null: JiraIssue issue = new JiraIssue("DUMMY-42", TITLE); when(site.getIssue(Mockito.anyString())).thenReturn(issue); text = new MarkupText("fixed DUMMY-42abc"); annotator.annotate(mock(FreeStyleBuild.class), null, text); Assert.assertEquals("fixed <a href='http://dummy/DUMMY-42' tooltip='title with $sign to confuse TextMarkup.replace'>DUMMY-42</a>abc", text.toString(false)); } }