Index: test/net/sourceforge/cruisecontrol/publishers/JiraPublisherTest.java =================================================================== --- test/net/sourceforge/cruisecontrol/publishers/JiraPublisherTest.java (revision 3558) +++ test/net/sourceforge/cruisecontrol/publishers/JiraPublisherTest.java (working copy) @@ -41,155 +41,210 @@ import net.sourceforge.cruisecontrol.util.XMLLogHelper; import org.jdom.Element; +import javax.xml.rpc.ServiceException; +import java.rmi.RemoteException; + /** - * Unit test for the Jabber publisher which publishes - * a link to the build results via Jabber Instant Messaging framework. - * Currently, tests are limited to message generation and parameter - * validation. Testing of the XMPPConnection mechanism will require a + * Unit test for the Jira publisher which creates a new issue + * for newly failed build via SOAP to a Jira instance. + * + * Testing of the SOAP publishing mechanism requires a * publicly accessible Jabber server to order to login as the recipient. - * Only then can this test represent 100% code coverage. + * Only then can this test represent 100% code coverage. This functional test is disabled. * - * @see net.sourceforge.cruisecontrol.publishers.JabberPublisher - * @see net.sourceforge.cruisecontrol.publishers.LinkJabberPublisher + * @see JiraPublisher * - * @author Jonas Edgeworth - * @version 1.0 + * @author Jerome Lacoste */ -public class LinkJabberPublisherTest extends TestCase { +public class JiraPublisherTest extends TestCase { - private LinkJabberPublisher publisher; + private JiraPublisher publisher; + private Element successLog; private XMLLogHelper successLogHelper; + private Element newFailureLog; + private Element reFailedLog; + private Element buildFixedLog; private String baseURLString = "http://mybuildserver.com:8080/buildservlet/BuildServlet"; protected void setUp() throws Exception { - successLogHelper = createLogHelper(true, true); - publisher = new LinkJabberPublisher(); + successLog = createLog(true, true); + successLogHelper = new XMLLogHelper(successLog); + newFailureLog = createLog(false, true); + reFailedLog = createLog(false, false); + buildFixedLog = createLog(true, false); + publisher = new JiraPublisher(); } protected XMLLogHelper createLogHelper(boolean success, boolean lastBuildSuccess) { + Element cruisecontrolElement = createLog(success, lastBuildSuccess); + + return new XMLLogHelper(cruisecontrolElement); + } + + private Element createLog(boolean success, boolean lastBuildSuccess) { Element cruisecontrolElement = new Element("cruisecontrol"); Element infoElement = new Element("info"); Element logFileElement = new Element("property"); logFileElement.setAttribute("name", "logfile"); - logFileElement.setAttribute("value", "log20020206120000.xml"); + logFileElement.setAttribute("value", "log20070927232937.xml"); infoElement.addContent(logFileElement); + cruisecontrolElement.addContent(infoElement); + Element timestampElement = new Element("property"); + timestampElement.setAttribute("name", "cctimestamp"); + timestampElement.setAttribute("value", "20070927232937"); + infoElement.addContent(timestampElement); + Element projectNameElement = new Element("property"); projectNameElement.setAttribute("name", "projectname"); - projectNameElement.setAttribute("value", "Jabbertest"); + projectNameElement.setAttribute("value", "Jiratest"); infoElement.addContent(projectNameElement); + Element lastBuildElement = new Element("property"); + lastBuildElement.setAttribute("name", "lastbuildsuccessful"); + lastBuildElement.setAttribute("value", "" + lastBuildSuccess); + infoElement.addContent(lastBuildElement); + Element buildElement = new Element("build"); if (!success) { buildElement.setAttribute("error", "Something went wrong"); } cruisecontrolElement.addContent(buildElement); - - return new XMLLogHelper(cruisecontrolElement); + return cruisecontrolElement; } /** - * Test the message creation process assuring that the - * generated string matches the associated build URL - * @throws CruiseControlException + * This one should be disabled most of the time as it publishes against official Jira. + * Cf http://jira.atlassian.com/browse/TST-12633 for example + * @throws Exception */ - public void testCreateMessage() throws CruiseControlException { - publisher.setBuildResultsURL(baseURLString); - assertEquals( - "Build results for successful build of project Jabbertest: " + baseURLString + "?log=log20020206120000", - publisher.createMessage(successLogHelper)); + public void disabled_testIntegrationTestPublishToOfficialJira() throws Exception { + JiraPublisherTest test = new JiraPublisherTest(); + test.setUp(); + Element log = test.createLog(false, true); + + // cf com.atlassian.jira_soapclient.ClientConstants + test.publisher.setUsername("soaptester"); + test.publisher.setPassword("soaptester"); + test.publisher.setJiraProject("TST"); + + test.publisher.setJiraUrl("http://jira.atlassian.com"); + + test.publisher.setBuildResultsURL(baseURLString); + + test.publisher.validate(); + test.publisher.publish(log); } + static class MockJiraPublisher extends JiraPublisher { + private XMLLogHelper helper = null; + + protected void createJiraIssue(XMLLogHelper helper) throws ServiceException, RemoteException, + CruiseControlException { + this.helper = helper; + } + + public boolean hasSentIssue() { + return helper != null; + } + } + + public void testOnlyNewBrokenBuildsTriggerIssueCreation1() throws CruiseControlException { + ensureValidateDoesntFailWhenNoMissingRequiredAttribute("url", "project", "username", "password", baseURLString); + + assertPublishEquals("don't send upon success", false, successLog); + } + + public void testOnlyNewBrokenBuildsTriggerIssueCreation2() throws CruiseControlException { + ensureValidateDoesntFailWhenNoMissingRequiredAttribute("url", "project", "username", "password", baseURLString); + + assertPublishEquals("only send upon new failure", true, newFailureLog); + } + + public void testOnlyNewBrokenBuildsTriggerIssueCreation3() throws CruiseControlException { + ensureValidateDoesntFailWhenNoMissingRequiredAttribute("url", "project", "username", "password", baseURLString); + + assertPublishEquals("don't send upon existing failure", false, reFailedLog); + } + + public void testOnlyNewBrokenBuildsTriggerIssueCreation4() throws CruiseControlException { + ensureValidateDoesntFailWhenNoMissingRequiredAttribute("url", "project", "username", "password", baseURLString); + + assertPublishEquals("don't send upon build fixed", false, buildFixedLog); + } + + private void assertPublishEquals(String message, boolean expectedPublished, Element log) + throws CruiseControlException { + MockJiraPublisher pub1 = new MockJiraPublisher(); + pub1.publish(log); + assertEquals(message, expectedPublished, pub1.hasSentIssue()); + } + + /** * Test the message creation process assuring that the * generated string matches the associated build URL - * with additional parameters passed included. - * @throws CruiseControlException + * @throws net.sourceforge.cruisecontrol.CruiseControlException */ - public void testQuestionMarkInBuildResultsURL() throws CruiseControlException { - publisher.setBuildResultsURL(baseURLString + "?key=value"); - + public void testCreateDescription() throws CruiseControlException { + publisher.setBuildResultsURL(baseURLString); assertEquals( - "Build results for successful build of project Jabbertest: " - + baseURLString - + "?key=value&log=log20020206120000", - publisher.createMessage(successLogHelper)); + "CruiseControl build failed for project Jiratest\n" + + "\n" + + "Check the build results at: " + baseURLString + "?log=log20070927232937", + publisher.createDescription(successLogHelper)); } /** * Test all validation scenarios related to the - * configuration parameters for the LinkJabberPublisher. + * configuration parameters for the JiraPublisher. * Currently, the validation checks the following:
- * If host is null
- * If username is null
- * If password is null
- * If recipient is null
- * If buildResultsURL is null
- * If username is not of the form username@myserver.com
- * If recipient is of the form recipient@myserver.com
- * Validation has not been tested against groupchat configurations.
+ * validation fails if one required attribute is missing
+ * validation doesn't fail if no required attribute is missing
*/
public void testValidate() {
- // Test if host is null
- publisher.setPassword("asdfdsaf");
- publisher.setRecipient("recipient");
- publisher.setBuildResultsURL("http://foo.com");
+ ensureValidateFailsOnRequiredMissingAttribute(null, "project", "username", "password", baseURLString);
+ ensureValidateFailsOnRequiredMissingAttribute("url", null, "username", "password", baseURLString);
+ ensureValidateFailsOnRequiredMissingAttribute("url", "project", null, "password", baseURLString);
+ ensureValidateFailsOnRequiredMissingAttribute("url", "project", "username", null, baseURLString);
+ ensureValidateFailsOnRequiredMissingAttribute("url", "project", "username", "password", null);
+
+ ensureValidateDoesntFailWhenNoMissingRequiredAttribute("url", "project", "username", "password", baseURLString);
+ }
+
+ private void ensureValidateFailsOnRequiredMissingAttribute(String url, String jiraProject, String password,
+ String username, String buildResultsURL) {
+ publisher.setJiraUrl(url);
+ publisher.setJiraProject(jiraProject);
+ publisher.setUsername(username);
+ publisher.setPassword(password);
+ publisher.setBuildResultsURL(buildResultsURL);
+
try {
publisher.validate();
- fail("should throw exception if host not set");
+ fail("should throw exception if one required attribute is not set");
} catch (CruiseControlException e) {
}
- // Test if username is null
- publisher.setHost("host");
- publisher.setUsername(null);
+ }
+
+ private void ensureValidateDoesntFailWhenNoMissingRequiredAttribute(String url, String jiraProject, String password,
+ String username, String buildResultsURL) {
+ publisher.setJiraUrl(url);
+ publisher.setJiraProject(jiraProject);
+ publisher.setUsername(username);
+ publisher.setPassword(password);
+ publisher.setBuildResultsURL(buildResultsURL);
+
try {
publisher.validate();
- fail("should throw exception if username not set");
} catch (CruiseControlException e) {
+ fail("should not throw exception if all required attribute are set");
}
- // Test is username is of the incorrect form
- publisher.setUsername("username@adsfdsaf.com");
- try {
- publisher.validate();
- fail("should throw exception if username is of the wrong form");
- } catch (CruiseControlException e) {
- }
- // Test if password is null
- publisher.setUsername("username");
- publisher.setPassword(null);
- try {
- publisher.validate();
- fail("should throw exception if password not set");
- } catch (CruiseControlException e) {
- }
- // Test if recipient is null
- publisher.setPassword("adsfadsf");
- publisher.setRecipient(null);
- try {
- publisher.validate();
- fail("should throw exception if recipient not set");
- } catch (CruiseControlException e) {
- }
- // Test if recipient is of the incorrect form
- publisher.setRecipient("recipient");
- try {
- publisher.validate();
- fail("should throw exception if recipient is of the wrong form");
- } catch (CruiseControlException e) {
- }
- // Test if build results url is null
- publisher.setRecipient("recipient@foo.com");
- publisher.setBuildResultsURL(null);
- try {
- publisher.validate();
- fail("should throw exception if BuildResultURL not set");
- } catch (CruiseControlException e) {
- }
}
}
\ No newline at end of file
Index: src/net/sourceforge/cruisecontrol/publishers/JiraPublisher.java
===================================================================
--- src/net/sourceforge/cruisecontrol/publishers/JiraPublisher.java (revision 0)
+++ src/net/sourceforge/cruisecontrol/publishers/JiraPublisher.java (revision 0)
@@ -0,0 +1,188 @@
+/********************************************************************************
+ * CruiseControl, a Continuous Integration Toolkit
+ * Copyright (c) 2001-2003, ThoughtWorks, Inc.
+ * 200 E. Randolph, 25th Floor
+ * Chicago, IL 60601 USA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * + Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * + Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ********************************************************************************/
+package net.sourceforge.cruisecontrol.publishers;
+
+import _soapclient.JiraSoapService;
+import _soapclient.JiraSoapServiceServiceLocator;
+import com.atlassian.jira.rpc.soap.beans.RemoteIssue;
+import net.sourceforge.cruisecontrol.CruiseControlException;
+import net.sourceforge.cruisecontrol.Publisher;
+import net.sourceforge.cruisecontrol.util.ValidationHelper;
+import net.sourceforge.cruisecontrol.util.XMLLogHelper;
+import org.apache.log4j.Logger;
+import org.jdom.Element;
+
+import java.util.Calendar;
+
+/**
+ * Publish issues to Jira via SOAP upon newly failed builds.
+ *
+ * @author Jerome Lacoste
+ * @version 1.0
+ */
+
+public class JiraPublisher implements Publisher {
+
+ private static final Logger LOG = Logger.getLogger(JiraPublisher.class);
+
+ private String jiraUrl;
+ private String username;
+ private String password;
+ private String jiraProject;
+ private String issueType = "1";
+ private String issuePriority = "1";
+
+ private String buildResultsURL;
+
+ public void setBuildResultsURL(String buildResultsURL) {
+ this.buildResultsURL = buildResultsURL;
+ }
+
+ public void setJiraUrl(String jiraUrl) {
+ this.jiraUrl = jiraUrl;
+ }
+
+ public void setJiraProject(String jiraProject) {
+ this.jiraProject = jiraProject;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public void setIssueType(String issueType) {
+ this.issueType = issueType;
+ }
+
+ public void setIssuePriority(String issuePriority) {
+ this.issuePriority = issuePriority;
+ }
+
+ /**
+ * Validate that all the mandatory parameters were specified in order
+ * to properly initial the Jabber client service. Note that this is called
+ * after the configuration file is read.
+ *
+ * @throws net.sourceforge.cruisecontrol.CruiseControlException if there was a configuration error.
+ */
+ public void validate() throws CruiseControlException {
+
+ ValidationHelper.assertIsSet(jiraProject, "jiraProject", this.getClass());
+ ValidationHelper.assertIsSet(username, "username", this.getClass());
+
+ ValidationHelper.assertIsSet(password, "password", this.getClass());
+ ValidationHelper.assertIsSet(jiraUrl, "jiraUrl", this.getClass());
+
+ ValidationHelper.assertIsSet(buildResultsURL, "buildresulturl", this.getClass());
+ }
+
+ /**
+ * Publish the results to the Jabber transport via an instant message.
+ *
+ * @param cruisecontrolLog
+ * @throws net.sourceforge.cruisecontrol.CruiseControlException
+ */
+ public void publish(Element cruisecontrolLog) throws CruiseControlException {
+ XMLLogHelper helper = new XMLLogHelper(cruisecontrolLog);
+ if (!helper.isBuildNewlyBroken()) {
+ LOG.debug("No new issue to report. Skipping.");
+ return;
+ }
+
+ try {
+ createJiraIssue(helper);
+ } catch (Throwable e) {
+ LOG.error("Unable to send message to Jira", e);
+ }
+ }
+
+ protected void createJiraIssue(XMLLogHelper helper) throws javax.xml.rpc.ServiceException, java.rmi.RemoteException,
+ CruiseControlException {
+ LOG.info("Creating a test issue on " + jiraUrl + " ...");
+ JiraSoapServiceServiceLocator jiraSoapServiceGetter = new JiraSoapServiceServiceLocator();
+
+ jiraSoapServiceGetter.setJirasoapserviceV2EndpointAddress(jiraUrl + "/rpc/soap/jirasoapservice-v2");
+ JiraSoapService jiraSoapService = jiraSoapServiceGetter.getJirasoapserviceV2();
+
+ String token = jiraSoapService.login(username, password);
+
+ // Create the issue
+ RemoteIssue issue = new RemoteIssue();
+ issue.setProject(jiraProject);
+ issue.setType(issueType);
+ issue.setPriority(issuePriority);
+ issue.setDuedate(Calendar.getInstance());
+
+ // RemoteComponent component = new RemoteComponent();
+ // component.setId("10242");
+ // issue.setComponents(new RemoteComponent[]{component});
+
+ issue.setSummary("CI build failed: " + helper.getBuildTimestamp());
+
+ // optional
+ // RemoteVersion version = new RemoteVersion();
+ // version.setId("10330");
+ // RemoteVersion[] remoteVersions = new RemoteVersion[]{version};
+ // issue.setFixVersions(remoteVersions);
+
+ String description = createDescription(helper);
+
+ issue.setDescription(description);
+
+ RemoteIssue returnedIssue = jiraSoapService.createIssue(token, issue);
+ LOG.info("Successfully created issue " + jiraUrl + "/browse/" + returnedIssue.getKey());
+ }
+
+ /**
+ * Creates the Issue description to be sent to Jira.
+ *
+ * @return String that makes up the body of the Issue description
+ * @throws net.sourceforge.cruisecontrol.CruiseControlException
+ */
+ protected String createDescription(XMLLogHelper logHelper) throws CruiseControlException {
+ StringBuffer message = new StringBuffer();
+ message.append("CruiseControl build failed for project ").append(logHelper.getProjectName());
+ message.append("\n").append("\n");
+ message.append("Check the build results at: ");
+ message.append(logHelper.createBuildResultsURL(buildResultsURL));
+ return message.toString();
+ }
+}
\ No newline at end of file