From 60160e919b16a2e727c4df46651b95ab8e927afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Schu=CC=88ttel?= Date: Sat, 21 Feb 2015 20:41:12 +0100 Subject: [PATCH] Allow to customize the message that gets printed in the chat room closes jenkinsci/hipchat-plugin#25 --- .../plugins/hipchat/HipChatNotifier.java | 88 ++++++- .../plugins/hipchat/NotificationType.java | 217 +++++++++++------- .../hipchat/NotificationTypeUtils.java | 59 +++++ .../hipchat/HipChatNotifier/config.jelly | 40 ++++ .../plugins/hipchat/Messages.properties | 18 +- .../help-projectConfig-hipChatMessages.html | 19 ++ .../hipchat/HipChatNotifierBuilder.java | 110 +++++++++ .../plugins/hipchat/NotificationTypeTest.java | 217 ++++++++++++++++++ 8 files changed, 669 insertions(+), 99 deletions(-) create mode 100644 src/main/java/jenkins/plugins/hipchat/NotificationTypeUtils.java create mode 100644 src/main/webapp/help-projectConfig-hipChatMessages.html create mode 100644 src/test/java/jenkins/plugins/hipchat/HipChatNotifierBuilder.java create mode 100644 src/test/java/jenkins/plugins/hipchat/NotificationTypeTest.java diff --git a/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java b/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java index 0d18804..5de89f8 100644 --- a/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java +++ b/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java @@ -1,5 +1,6 @@ package jenkins.plugins.hipchat; +import com.google.common.base.Preconditions; import hudson.Extension; import hudson.Launcher; import hudson.Util; @@ -42,10 +43,20 @@ public class HipChatNotifier extends Notifier { private boolean notifyFailure; private boolean notifyBackToNormal; + private String messageStarted; + private String messageBackToNormal; + private String messageSuccess; + private String messageFailure; + private String messageAborted; + private String messageNotBuilt; + private String messageUnstable; + @DataBoundConstructor public HipChatNotifier(String token, String room, boolean startNotification, boolean notifySuccess, boolean notifyAborted, boolean notifyNotBuilt, boolean notifyUnstable, boolean notifyFailure, - boolean notifyBackToNormal) { + boolean notifyBackToNormal, + String messageStarted, String messageBackToNormal, String messageSuccess, String messageFailure, + String messageAborted, String messageNotBuilt, String messageUnstable) { this.token = token; this.room = room; this.startNotification = startNotification; @@ -55,8 +66,17 @@ public HipChatNotifier(String token, String room, boolean startNotification, boo this.notifyUnstable = notifyUnstable; this.notifyFailure = notifyFailure; this.notifyBackToNormal = notifyBackToNormal; + this.messageStarted = messageStarted; + this.messageBackToNormal = messageBackToNormal; + this.messageSuccess = messageSuccess; + this.messageFailure = messageFailure; + this.messageAborted = messageAborted; + this.messageNotBuilt = messageNotBuilt; + this.messageUnstable = messageUnstable; } + /* notification enabled disabled setter/getter */ + public boolean isStartNotification() { return startNotification; } @@ -113,6 +133,70 @@ public void setNotifyBackToNormal(boolean notifyBackToNormal) { this.notifyBackToNormal = notifyBackToNormal; } + /* notification message configuration*/ + + public String getMessageStarted() { + return messageStarted; + } + + public void setMessageStarted(String messageStarted) { + this.messageStarted = messageStarted; + } + + public String getMessageBackToNormal() { + return messageBackToNormal; + } + + public void setMessageBackToNormal(String messageBackToNormal) { + this.messageBackToNormal = messageBackToNormal; + } + + public String getMessageSuccess() { + return messageSuccess; + } + + public void setMessageSuccess(String messageSuccess) { + this.messageSuccess = messageSuccess; + } + + public String getMessageFailure() { + return messageFailure; + } + + public void setMessageFailure(String messageFailure) { + this.messageFailure = messageFailure; + } + + public String getMessageAborted() { + return messageAborted; + } + + public void setMessageAborted(String messageAborted) { + this.messageAborted = messageAborted; + } + + public String getMessageNotBuilt() { + return messageNotBuilt; + } + + public void setMessageNotBuilt(String messageNotBuilt) { + this.messageNotBuilt = messageNotBuilt; + } + + public String getMessageUnstable() { + return messageUnstable; + } + + public void setMessageUnstable(String messageUnstable) { + this.messageUnstable = messageUnstable; + } + + public String getMessageDefault(String enumName) { + NotificationType type = NotificationType.valueOf(enumName); + Preconditions.checkNotNull(type, "unknown NotificationType %s", enumName); + return type.getDefaultTemplate(); + } + public String getRoom() { return StringUtils.isBlank(room) ? getDescriptor().getRoom() : room; } @@ -179,7 +263,7 @@ private Result findPreviousBuildResult(AbstractBuild build) { private void publishNotificationIfEnabled(NotificationType notificationType, AbstractBuild build) { if (isNotificationEnabled(notificationType)) { - getHipChatService().publish(notificationType.getMessage(build), notificationType.getColor()); + getHipChatService().publish(notificationType.getMessage(build, this), notificationType.getColor()); } } diff --git a/src/main/java/jenkins/plugins/hipchat/NotificationType.java b/src/main/java/jenkins/plugins/hipchat/NotificationType.java index 01fd058..f74b613 100644 --- a/src/main/java/jenkins/plugins/hipchat/NotificationType.java +++ b/src/main/java/jenkins/plugins/hipchat/NotificationType.java @@ -1,84 +1,119 @@ package jenkins.plugins.hipchat; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import hudson.EnvVars; import hudson.model.AbstractBuild; -import hudson.model.CauseAction; import hudson.model.Result; import hudson.scm.ChangeLogSet; +import hudson.util.LogTaskListener; +import hudson.util.VariableResolver; +import org.apache.commons.lang.StringUtils; + +import java.io.IOException; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.logging.Level; import java.util.logging.Logger; -import jenkins.model.Jenkins; -import org.apache.commons.lang.StringUtils; + +import static com.google.common.base.Throwables.propagate; +import static com.google.common.collect.Maps.newHashMap; +import static hudson.Util.replaceMacro; +import static java.util.logging.Level.INFO; public enum NotificationType { STARTED("green") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageStarted(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - String changes = getStatusMessageWithChanges(build); - if (changes != null) { - return changes; - } else { - CauseAction cause = build.getAction(CauseAction.class); - if (cause != null) { - return cause.getShortDescription(); - } else { - return Messages.Starting(); - } - } - } - }, + @Override + public String getDefaultTemplate() { + return Messages.Started(); + } + }, ABORTED("gray") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageAborted(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - return Messages.Aborted(build.getDurationString()); - } - }, + @Override + public String getDefaultTemplate() { + return Messages.Aborted(); + } + }, SUCCESS("green") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageSuccess(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - return Messages.Success(build.getDurationString()); - } - }, + @Override + public String getDefaultTemplate() { + return Messages.Success(); + } + }, FAILURE("red") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageFailure(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - return Messages.Failure(build.getDurationString()); - } - }, + @Override + public String getDefaultTemplate() { + return Messages.Failure(); + } + }, NOT_BUILT("gray") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageNotBuilt(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - return Messages.NotBuilt(); - } - }, + @Override + public String getDefaultTemplate() { + return Messages.NotBuilt(); + } + }, BACK_TO_NORMAL("green") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageBackToNormal(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - return Messages.BackToNormal(build.getDurationString()); - } - }, + @Override + public String getDefaultTemplate() { + return Messages.BackToNormal(); + } + }, UNSTABLE("yellow") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + return notifier.getMessageUnstable(); + } - @Override - protected String getStatusMessage(AbstractBuild build) { - return Messages.Unstable(build.getDurationString()); - } - }, + @Override + public String getDefaultTemplate() { + return Messages.Unstable(); + } + }, UNKNOWN("purple") { + @Override + protected String getConfiguredTemplateFor(HipChatNotifier notifier) { + throw new IllegalStateException(); + } + + @Override + public String getDefaultTemplate() { + return null; + } + }; - @Override - protected String getStatusMessage(AbstractBuild build) { - throw new IllegalStateException("Unable to generate status message for UNKNOWN notification type"); - } - }; private static final Logger logger = Logger.getLogger(NotificationType.class.getName()); private final String color; @@ -86,50 +121,56 @@ private NotificationType(String color) { this.color = color; } - protected abstract String getStatusMessage(AbstractBuild build); + protected abstract String getConfiguredTemplateFor(HipChatNotifier notifier); + public abstract String getDefaultTemplate(); public String getColor() { return color; } - private static String getStatusMessageWithChanges(AbstractBuild build) { - if (!build.hasChangeSetComputed()) { - logger.log(Level.FINE, "No changeset computed for job {0}", build.getProject().getFullDisplayName()); - return null; - } - - Set authors = new HashSet(); - int changedFiles = 0; - for (Object o : build.getChangeSet().getItems()) { - ChangeLogSet.Entry entry = (ChangeLogSet.Entry) o; - logger.log(Level.FINEST, "Entry {0}", entry); - authors.add(entry.getAuthor().getDisplayName()); - try { - changedFiles += entry.getAffectedFiles().size(); - } catch (UnsupportedOperationException e) { - logger.log(Level.INFO, "Unable to collect the affected files for job {0}", - build.getProject().getFullDisplayName()); - return null; - } - } - if (changedFiles == 0) { - logger.log(Level.FINE, "No changes detected"); - return null; - } + public final String getMessage(AbstractBuild build, HipChatNotifier notifier) { + String format = getTemplateFor(notifier); + Map messageVariables = collectParametersFor(build); - return Messages.StartWithChanges(StringUtils.join(authors, ", "), changedFiles); + return replaceMacro(format, new VariableResolver.ByMap(messageVariables)); } - public final String getMessage(AbstractBuild build) { - String rootUrl = Jenkins.getInstance().getRootUrl(); - StringBuilder sb = new StringBuilder(150); - sb.append(Messages.MessageStart(build.getProject().getFullDisplayName(), build.getDisplayName())); - sb.append(' '); - sb.append(getStatusMessage(build)); + private String getTemplateFor(HipChatNotifier notifier) { + String userConfig = this.getConfiguredTemplateFor(notifier); + String defaultConfig = this.getDefaultTemplate(); + if (userConfig == null || userConfig.trim().isEmpty()) { + Preconditions.checkState(defaultConfig != null, "default config not set for %s", this); + return defaultConfig; + } else { + return userConfig; + } + } - sb.append(" (Open)"); + private Map collectParametersFor(AbstractBuild build) { + Map merged = newHashMap(); + merged.putAll(build.getBuildVariables()); + merged.putAll(getEnvVars(build)); + + String cause = NotificationTypeUtils.getCause(build); + String changes = NotificationTypeUtils.getChanges(build); + + merged.put("DURATION", build.getDurationString()); + merged.put("URL", NotificationTypeUtils.getUrl(build)); + merged.put("CAUSE", cause); + merged.put("CHANGES_OR_CAUSE", changes != null ? changes : cause); + merged.put("CHANGES", changes); + merged.put("PRINT_FULL_ENV", merged.toString()); + return merged; + } - return sb.toString(); + private EnvVars getEnvVars(AbstractBuild build) { + try { + return build.getEnvironment(new LogTaskListener(logger, INFO)); + } catch (IOException e) { + throw propagate(e); + } catch (InterruptedException e) { + throw propagate(e); + } } public static final NotificationType fromResults(Result previousResult, Result result) { diff --git a/src/main/java/jenkins/plugins/hipchat/NotificationTypeUtils.java b/src/main/java/jenkins/plugins/hipchat/NotificationTypeUtils.java new file mode 100644 index 0000000..16c79a4 --- /dev/null +++ b/src/main/java/jenkins/plugins/hipchat/NotificationTypeUtils.java @@ -0,0 +1,59 @@ +package jenkins.plugins.hipchat; + +import com.google.common.collect.Sets; +import hudson.model.AbstractBuild; +import hudson.model.CauseAction; +import hudson.scm.ChangeLogSet; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; + +import java.util.Set; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; + + +public final class NotificationTypeUtils { + private static final Logger logger = Logger.getLogger(NotificationTypeUtils.class.getName()); + + private NotificationTypeUtils() { + /* utility method */ + } + + public static String getCause(AbstractBuild build) { + CauseAction cause = build.getAction(CauseAction.class); + if (cause != null) { + return cause.getShortDescription(); + } else { + return null; + } + } + + public static String getUrl(AbstractBuild build) { + return Jenkins.getInstance().getRootUrl() + build.getUrl(); + } + + public static String getChanges(AbstractBuild build) { + if (!build.hasChangeSetComputed()) { + return null; + } + Set authors = Sets.newHashSet(); + int changedFiles = 0; + for (Object o : build.getChangeSet().getItems()) { + ChangeLogSet.Entry entry = (ChangeLogSet.Entry) o; + authors.add(entry.getAuthor().getDisplayName()); + try { + changedFiles += entry.getAffectedFiles().size(); + } catch (UnsupportedOperationException e) { + logger.log(INFO, "Unable to collect the affected files for job {0}", + build.getProject().getFullDisplayName()); + return null; + } + } + if (changedFiles == 0) { + return null; + } + + return Messages.StartWithChanges(StringUtils.join(authors, ", "), changedFiles); + } +} diff --git a/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly b/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly index 25abe37..152896f 100644 --- a/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly +++ b/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly @@ -1,3 +1,4 @@ + @@ -36,4 +37,43 @@ + + + + + + Default: '${instance.getMessageDefault("STARTED")}' + + + + + Default: '${instance.getMessageDefault("BACK_TO_NORMAL")}' + + + + + Default: '${instance.getMessageDefault("SUCCESS")}' + + + + + Default: '${instance.getMessageDefault("FAILURE")}' + + + + + Default: '${instance.getMessageDefault("ABORTED")}' + + + + + Default: '${instance.getMessageDefault("NOT_BUILT")}' + + + + + Default: '${instance.getMessageDefault("UNSTABLE")}' + + + diff --git a/src/main/resources/jenkins/plugins/hipchat/Messages.properties b/src/main/resources/jenkins/plugins/hipchat/Messages.properties index 5c181b9..6d8644f 100644 --- a/src/main/resources/jenkins/plugins/hipchat/Messages.properties +++ b/src/main/resources/jenkins/plugins/hipchat/Messages.properties @@ -2,13 +2,13 @@ DisplayName=HipChat Notifications TestNotification=Test Notification {0} TestNotificationSent=Test Notification Sent -MessageStart={0} - {1} -Starting=Starting... -BackToNormal=Back to normal after {0} -Success=Success after {0} -Failure=FAILURE after {0} -Aborted=ABORTED after {0} -NotBuilt=Not built -Unstable=UNSTABLE after {0} -Unknown=Unknown StartWithChanges=Started by changes from {0} ({1} file(s) changed) + +Started=$JOB_NAME #$BUILD_NUMBER Starting... ($CHANGES_OR_CAUSE) (Open) +BackToNormal=$JOB_NAME #$BUILD_NUMBER Back to normal after $DURATION (Open) +Success=$JOB_NAME #$BUILD_NUMBER Success after $DURATION (Open) +Failure=$JOB_NAME #$BUILD_NUMBER FAILURE after $DURATION (Open) +Aborted=$JOB_NAME #$BUILD_NUMBER ABORTED after $DURATION (Open) +NotBuilt=$JOB_NAME #$BUILD_NUMBER Not built after $DURATION (Open) +Unstable=$JOB_NAME #$BUILD_NUMBER UNSTABLE after $DURATION (Open) +Unknown=Unknown \ No newline at end of file diff --git a/src/main/webapp/help-projectConfig-hipChatMessages.html b/src/main/webapp/help-projectConfig-hipChatMessages.html new file mode 100644 index 0000000..82ea34f --- /dev/null +++ b/src/main/webapp/help-projectConfig-hipChatMessages.html @@ -0,0 +1,19 @@ +
+

+ Configures the message that will be displayed in the room. If left blank a default value will be used. + All normal build variables can be used with the notation $VAR_NAME. Additional the following + variables are provided: +

+
    +
  • $DURATION - duration of the build in human readable form. Example: 13 min
  • +
  • $URL - URL to the job page on jenkins. You can use normal HTML to create a link + i.e: <a href="$URL">More Info<a>
  • +
  • $CAUSE - cause of this change. i.e. 'starte by user foo'
  • +
  • $CHANGES - a short overview of the changes that caused that job, if available
  • +
  • $CHANGES_OR_CAUSE - either $CHANGES or $CAUSE, see above
  • +
  • $STATUS - the status of the build, i.e. 'Success'
  • +
  • $PRINT_FULL_ENV - dump of all variables, use this only for debugging purposes. + Be aware that it also contains the full host environment and which might contain sensitive information. +
  • +
+
diff --git a/src/test/java/jenkins/plugins/hipchat/HipChatNotifierBuilder.java b/src/test/java/jenkins/plugins/hipchat/HipChatNotifierBuilder.java new file mode 100644 index 0000000..23f8fa0 --- /dev/null +++ b/src/test/java/jenkins/plugins/hipchat/HipChatNotifierBuilder.java @@ -0,0 +1,110 @@ +package jenkins.plugins.hipchat; + +public class HipChatNotifierBuilder { + private String token = "token"; + private String room = "room"; + private boolean startNotification = false; + private boolean notifySuccess = false; + private boolean notifyAborted = true; + private boolean notifyNotBuilt = false; + private boolean notifyUnstable = true; + private boolean notifyFailure = true; + private boolean notifyBackToNormal = true; + private String messageStarting; + private String messageBackToNormal; + private String messageSuccess; + private String messageFailure; + private String messageAborted; + private String messageNotBuilt; + private String messageUnstable; + + public static HipChatNotifierBuilder notifier() { + return new HipChatNotifierBuilder(); + } + + public HipChatNotifierBuilder setToken(String token) { + this.token = token; + return this; + } + + public HipChatNotifierBuilder setRoom(String room) { + this.room = room; + return this; + } + + public HipChatNotifierBuilder setStartNotification(boolean startNotification) { + this.startNotification = startNotification; + return this; + } + + public HipChatNotifierBuilder setNotifySuccess(boolean notifySuccess) { + this.notifySuccess = notifySuccess; + return this; + } + + public HipChatNotifierBuilder setNotifyAborted(boolean notifyAborted) { + this.notifyAborted = notifyAborted; + return this; + } + + public HipChatNotifierBuilder setNotifyNotBuilt(boolean notifyNotBuilt) { + this.notifyNotBuilt = notifyNotBuilt; + return this; + } + + public HipChatNotifierBuilder setNotifyUnstable(boolean notifyUnstable) { + this.notifyUnstable = notifyUnstable; + return this; + } + + public HipChatNotifierBuilder setNotifyFailure(boolean notifyFailure) { + this.notifyFailure = notifyFailure; + return this; + } + + public HipChatNotifierBuilder setNotifyBackToNormal(boolean notifyBackToNormal) { + this.notifyBackToNormal = notifyBackToNormal; + return this; + } + + public HipChatNotifierBuilder setMessageStarting(String messageStarting) { + this.messageStarting = messageStarting; + return this; + } + + public HipChatNotifierBuilder setMessageBackToNormal(String messageBackToNormal) { + this.messageBackToNormal = messageBackToNormal; + return this; + } + + public HipChatNotifierBuilder setMessageSuccess(String messageSuccess) { + this.messageSuccess = messageSuccess; + return this; + } + + public HipChatNotifierBuilder setMessageFailure(String messageFailure) { + this.messageFailure = messageFailure; + return this; + } + + public HipChatNotifierBuilder setMessageAborted(String messageAborted) { + this.messageAborted = messageAborted; + return this; + } + + public HipChatNotifierBuilder setMessageNotBuilt(String messageNotBuilt) { + this.messageNotBuilt = messageNotBuilt; + return this; + } + + public HipChatNotifierBuilder setMessageUnstable(String messageUnstable) { + this.messageUnstable = messageUnstable; + return this; + } + + public HipChatNotifier createHipChatNotifier() { + return new HipChatNotifier(token, room, startNotification, notifySuccess, notifyAborted, notifyNotBuilt, + notifyUnstable, notifyFailure, notifyBackToNormal, messageStarting, messageBackToNormal, + messageSuccess, messageFailure, messageAborted, messageNotBuilt, messageUnstable); + } +} \ No newline at end of file diff --git a/src/test/java/jenkins/plugins/hipchat/NotificationTypeTest.java b/src/test/java/jenkins/plugins/hipchat/NotificationTypeTest.java new file mode 100644 index 0000000..8c59fa0 --- /dev/null +++ b/src/test/java/jenkins/plugins/hipchat/NotificationTypeTest.java @@ -0,0 +1,217 @@ +package jenkins.plugins.hipchat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import hudson.EnvVars; +import hudson.model.AbstractBuild; +import hudson.model.TaskListener; +import hudson.model.User; +import hudson.scm.ChangeLogSet; +import hudson.scm.EditType; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; + +import static jenkins.plugins.hipchat.HipChatNotifierBuilder.notifier; +import static jenkins.plugins.hipchat.NotificationType.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Mockito.*; + +@RunWith(PowerMockRunner.class) +public class NotificationTypeTest { + @Mock + Jenkins jenkins; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + @PrepareForTest(Jenkins.class) + public void testGetMessage() throws Exception { + mockJenkins(); + HipChatNotifier notifier = notifier().createHipChatNotifier(); + + testNormalConfiguration(build(), notifier); + } + + @Test + @PrepareForTest(Jenkins.class) + public void testGetMessageWithConfig() throws Exception { + mockJenkins(); + + HipChatNotifier notifier = notifier() + .setMessageFailure("you broke it $AUTHOR!") + .createHipChatNotifier(); + String url = "(Open"; + String prefix = "test-job #33"; + assertNotifierProduces(build(), notifier, SUCCESS, prefix + " Success after 42 sec " + url + ")"); + assertNotifierProduces(build(), notifier, FAILURE, "you broke it Mike!"); + assertNotifierProduces(build(), notifier, NOT_BUILT, prefix + " Not built after 42 sec " + url + ")"); + } + + @Test + @PrepareForTest(Jenkins.class) + public void testGetMessageBlankConfiguration() throws Exception { + mockJenkins(); + + HipChatNotifier notifier = notifier() + .setMessageFailure("") + .setMessageNotBuilt(" ") + .setMessageSuccess(null) + .createHipChatNotifier(); + testNormalConfiguration(build(), notifier); + } + + @Test + @PrepareForTest(Jenkins.class) + public void testGetMessageAllOverride() throws Exception { + mockJenkins(); + + HipChatNotifier notifier = notifier() + .setMessageStarting("MessageStarting") + .setMessageAborted("MessageAborted") + .setMessageSuccess("MessageSuccess") + .setMessageFailure("MessageFailure") + .setMessageNotBuilt("MessageNotBuilt") + .setMessageBackToNormal("MessageBackToNormal") + .setMessageUnstable("MessageUnstable") + .createHipChatNotifier(); + assertNotifierProduces(build(), notifier, STARTED, "MessageStarting"); + assertNotifierProduces(build(), notifier, ABORTED, "MessageAborted"); + assertNotifierProduces(build(), notifier, SUCCESS, "MessageSuccess"); + assertNotifierProduces(build(), notifier, FAILURE, "MessageFailure"); + assertNotifierProduces(build(), notifier, NOT_BUILT, "MessageNotBuilt"); + assertNotifierProduces(build(), notifier, BACK_TO_NORMAL, "MessageBackToNormal"); + assertNotifierProduces(build(), notifier, UNSTABLE, "MessageUnstable"); + } + + private void testNormalConfiguration(AbstractBuild build, HipChatNotifier notifier) { + String url = "(Open"; + String prefix = "test-job #33"; + assertNotifierProduces(build, notifier, STARTED, prefix + " Starting... (Started by changes from john.doe@example.com (1 file(s) changed)) " + url + ")"); + assertNotifierProduces(build, notifier, ABORTED, prefix + " ABORTED after 42 sec " + url + ")"); + assertNotifierProduces(build, notifier, SUCCESS, prefix + " Success after 42 sec " + url + ")"); + assertNotifierProduces(build, notifier, FAILURE, prefix + " FAILURE after 42 sec " + url + ")"); + assertNotifierProduces(build, notifier, NOT_BUILT, prefix + " Not built after 42 sec " + url + ")"); + assertNotifierProduces(build, notifier, BACK_TO_NORMAL, prefix + " Back to normal after 42 sec " + url + ")"); + assertNotifierProduces(build, notifier, UNSTABLE, prefix + " UNSTABLE after 42 sec " + url + ")"); + } + + private AbstractBuild build() throws java.io.IOException, InterruptedException { + AbstractBuild build = mock(AbstractBuild.class); + when(build.getUrl()).thenReturn("foo/123"); + when(build.getBuildVariables()).thenReturn(ImmutableMap.of( + "JOB_NAME", "test-job", + "BUILD_NUMBER", "33", + "AUTHOR", "Mike")); + when(build.getDurationString()).thenReturn("42 sec"); + mockChanges(build); + EnvVars envVar = mock(EnvVars.class); + when(build.getEnvironment(any(TaskListener.class))).thenReturn(envVar); + return build; + } + + private void assertNotifierProduces(AbstractBuild build, HipChatNotifier notifier, NotificationType type, String expected) { + String msg = type.getMessage(build, notifier); + assertThat(msg, equalTo(expected)); + } + + private void mockJenkins() { + PowerMockito.mockStatic(Jenkins.class); + PowerMockito.when(Jenkins.getInstance()).thenReturn(jenkins); + PowerMockito.when(jenkins.getRootUrl()).thenReturn("http://localhost:8080/jenkins/"); + } + + private void mockChanges(final AbstractBuild build) { + when(build.hasChangeSetComputed()).thenReturn(true); + when(build.getChangeSet()).thenAnswer(new Answer() { + @Override + public DummyChangeSet answer(InvocationOnMock invocationOnMock) throws Throwable { + return new DummyChangeSet(null); + } + }); + } + + public static class DummyChangeSet extends ChangeLogSet { + private final LinkedList entries; + + public DummyChangeSet(AbstractBuild build) { + super(build); + entries = Lists.newLinkedList(); + entries.add(new DummyChangeSetEntry()); + } + + @Override + public boolean isEmptySet() { + return false; + } + + @Override + public Iterator iterator() { + return entries.iterator(); + } + + public static class DummyChangeSetEntry extends ChangeLogSet.Entry { + private final User user; + + public DummyChangeSetEntry() { + this.user = mock(User.class); + when(this.user.getDisplayName()).thenReturn("john.doe@example.com"); + } + + @Override + public Collection getAffectedFiles() { + return ImmutableList.of(new DummyAffectedFile("dummy-path")); + } + + @Override + public String getMsg() { + return null; + } + + @Override + public User getAuthor() { + return user; + } + + @Override + public Collection getAffectedPaths() { + return null; + } + + private class DummyAffectedFile implements AffectedFile { + private final String path; + + public DummyAffectedFile(String path) { + this.path = path; + } + + @Override + public String getPath() { + return path; + } + + @Override + public EditType getEditType() { + return null; + } + } + } + } +}