Initial commit supporting Gerrit stable 2.9
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..080c4e8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,105 @@
+Slack Integration Plugin
+========================
+
+A simple Gerrit plugin that allows the publishing of certain Gerrit events
+to a configured Slack Webhook URL. The plugin uses Gerrit's inherited project
+configuration support so common config options can be set at a higher level
+and shared by many projects along with project specific config options.
+
+
+Development
+-----------
+
+To build the plugin,
+[JDK 1.8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html),
+[Maven 3.0.x](http://maven.apache.org/download.cgi) and
+[Ant 1.9.x](https://ant.apache.org/bindownload.cgi) are required.
+Once installed use _mvn_ to build.
+
+ cd ./slack-integration
+ mvn install
+
+This command will compile/test and package the resulting artifact.
+
+ cd ./slack-integration
+ mvn package
+
+Once packaged, you can install the _./target/slack-integration.jar_ file into
+Gerrit.
+
+
+Installation
+------------
+
+Installing the Slack Integration Plugin is as simple as copying the resulting
+JAR file into the Gerrit plugins directory. Assuming you installation of Gerrit
+is located at _/usr/local/gerrit_ you simply execute the following.
+
+ cp ./slack-integration.jar /usr/local/gerrit/plugins
+
+Simple substitute the path to your Gerrit plugins directory as needed. Gerrit
+automatically loads new plugins and unloads old plugins, no restart is
+required.
+
+
+Configuration
+-------------
+
+The first thing you need to do is setup an incoming webhook integration in
+Slack. This is done via my.slack.com - Configure Integrations.
+
+Configuration of the Slack Integration Plugin is done in Gerrit via a project
+specific config file. This configuration is stored in the project’s
+_project.config_ file on the _refs/meta/config_ branch of the project.
+
+Common configuration options that can be shared between multiple projects
+can be placed in the _All-Projects_ config branch, or another project that
+serves as an inherited base. Config options can then be overridden in the
+actual project's config branch. For example, you may want to specify a default
+webhook URL, username and channel then override the channel to be specific
+to each project.
+
+Editing a project's config
+
+ mkdir <project>-config
+ cd <project>-config
+ git init
+ git remote add origin ssh://<admin-user>@<gerrit-host>:29418/<project>
+ git fetch origin refs/meta/config:refs/remotes/origin/meta/config
+ git checkout meta/config
+
+Create the following config block
+
+ vi project.config
+
+ [plugin "slack-integration"]
+ enabled = true
+ webhookurl = https://<web-hook-url>
+ channel = general
+ username = gerrit
+ ignore = "^WIP.*"
+
+Commit and push changes
+
+ git commit -a
+ git push origin meta/config:meta/config
+
+
+Configuration Options
+---------------------
+
+The following configuration options are available
+
+ enabled – boolean (true/false)
+ When true, enables Slack integration (defaults to false).
+ webhookurl - String
+ The Slack webhook URL to publish to (defaults to an
+ empty string).
+ channel - String
+ The Slack channel to publish to (defaults to "general").
+ username - String
+ The Slack username to publish as (defaults to "gerrit").
+ ignore - Pattern
+ A "dotall" enabled regular expression pattern that, when matches
+ against a commit message, will prevent the publishing of patchset
+ created event messages (defaults to an empty string).
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1aac62d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ ~ Copyright 2016 Cisco Systems, 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.
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.cisco.gerrit.plugins</groupId>
+ <artifactId>slack-integration</artifactId>
+ <packaging>jar</packaging>
+ <version>2.9</version>
+ <name>Slack Integration Plugin</name>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+ <gerrit-api-type>plugin</gerrit-api-type>
+ <gerrit-api-version>2.9</gerrit-api-version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Implementation-Vendor>
+ Cisco Systems, Inc.
+ </Implementation-Vendor>
+ <Implementation-URL>
+ https://gerrit-review.googlesource.com/#/admin/projects/plugins/slack-integration
+ </Implementation-URL>
+ <Implementation-Title>
+ Slack Integration
+ </Implementation-Title>
+ <Implementation-Version>
+ ${project.version}
+ </Implementation-Version>
+ <Gerrit-ApiType>
+ ${gerrit-api-type}
+ </Gerrit-ApiType>
+ <Gerrit-ApiVersion>
+ ${gerrit-api-version}
+ </Gerrit-ApiVersion>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ <!--<plugin>-->
+ <!--<groupId>org.apache.maven.plugins</groupId>-->
+ <!--<artifactId>maven-surefire-plugin</artifactId>-->
+ <!--<version>2.18.1</version>-->
+ <!--<configuration>-->
+ <!--<excludes>-->
+ <!--<excludes>**/*IntegrationTest.java</excludes>-->
+ <!--</excludes>-->
+ <!--</configuration>-->
+ <!--</plugin>-->
+
+ <!--<plugin>-->
+ <!--<groupId>org.apache.maven.plugins</groupId>-->
+ <!--<artifactId>maven-failsafe-plugin</artifactId>-->
+ <!--<version>2.18.1</version>-->
+ <!--<configuration>-->
+ <!--<includes>-->
+ <!--<include>**/*IntegrationTest.java</include>-->
+ <!--</includes>-->
+ <!--</configuration>-->
+ <!--</plugin>-->
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.7</source>
+ <target>1.7</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <repositories>
+ <repository>
+ <id>gerrit-api-repository</id>
+ <url>https://gerrit-api.commondatastorage.googleapis.com/release/
+ </url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-${gerrit-api-type}-api</artifactId>
+ <version>${gerrit-api-version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.7.9</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.10.19</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>1.6.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <version>1.6.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/EventListener.java b/src/main/java/com/cisco/gerrit/plugins/slack/EventListener.java
new file mode 100644
index 0000000..b4c4ca5
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/EventListener.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack;
+
+import com.cisco.gerrit.plugins.slack.client.WebhookClient;
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.cisco.gerrit.plugins.slack.message.MessageGenerator;
+import com.cisco.gerrit.plugins.slack.message.MessageGeneratorFactory;
+import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Listens for Gerrit change events and publishes messages to Slack.
+ */
+@Listen
+@Singleton
+public class EventListener implements ChangeListener
+{
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(EventListener.class);
+
+ private static final String ALL_PROJECTS = "All-Projects";
+
+ @Inject
+ private PluginConfigFactory configFactory;
+
+ @Override
+ public void onChangeEvent(ChangeEvent event)
+ {
+ try
+ {
+ ProjectConfig config;
+ MessageGenerator messageGenerator;
+
+ if (event instanceof PatchSetCreatedEvent)
+ {
+ PatchSetCreatedEvent patchSetCreatedEvent;
+ patchSetCreatedEvent = (PatchSetCreatedEvent) event;
+
+ config = new ProjectConfig(configFactory,
+ patchSetCreatedEvent.change.project);
+
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ patchSetCreatedEvent, config);
+ }
+ else if (event instanceof ChangeMergedEvent)
+ {
+ ChangeMergedEvent changeMergedEvent;
+ changeMergedEvent = (ChangeMergedEvent) event;
+
+ config = new ProjectConfig(configFactory,
+ changeMergedEvent.change.project);
+
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ changeMergedEvent, config);
+ }
+ else
+ {
+ LOGGER.debug("Event " + event + " not currently supported");
+
+ config = new ProjectConfig(configFactory, ALL_PROJECTS);
+
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ event, config);
+ }
+
+ if (messageGenerator.shouldPublish())
+ {
+ WebhookClient client;
+ client = new WebhookClient();
+
+ client.publish(messageGenerator.generate(),
+ config.getWebhookUrl());
+ }
+ }
+ catch (Throwable e)
+ {
+ LOGGER.error("Event " + event + " processing failed", e);
+ }
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java b/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java
new file mode 100644
index 0000000..5ecb0fe
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.client;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Scanner;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A minimal Slack client for publishing messages to a pre-configured incoming
+ * webhook (https://api.slack.com/incoming-webhooks).
+ *
+ * @author Matthew Montgomery
+ */
+public class WebhookClient
+{
+ /**
+ * The class logger instance.
+ **/
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(WebhookClient.class);
+
+ /**
+ * Publish a message to the provided Slack webhook URL.
+ *
+ * @param message The message to publish.
+ * @param webhookUrl The web hook URL to publish to.
+ * @return true, if successful; otherwise false
+ */
+ public boolean publish(String message, String webhookUrl)
+ {
+ if (message == null || message.equals(""))
+ {
+ throw new IllegalArgumentException(
+ "message cannot be null or empty");
+ }
+
+ if (webhookUrl == null || webhookUrl.equals(""))
+ {
+ throw new IllegalArgumentException(
+ "webhookUrl cannot be null or empty");
+ }
+
+ boolean result;
+ result = false;
+
+ String response;
+ response = postRequest(message, webhookUrl);
+
+ if ("ok".equals(response))
+ {
+ result = true;
+ }
+ else
+ {
+ LOGGER.error("Unexpected response: [" + response + "].");
+ }
+
+ return result;
+ }
+
+ /**
+ * Initiates an HTTP POST to the provided Webhook URL.
+ *
+ * @param message The message payload.
+ * @param webhookUrl The URL to post to.
+ * @return The response payload from Slack.
+ */
+ private String postRequest(String message, String webhookUrl)
+ {
+ String response;
+
+ HttpURLConnection connection;
+ connection = null;
+ try
+ {
+ connection = openConnection(webhookUrl);
+ try
+ {
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type",
+ "application/json");
+ connection.setRequestProperty("charset", "utf-8");
+
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+
+ DataOutputStream request;
+ request = new DataOutputStream(connection.getOutputStream());
+
+ request.writeBytes(message);
+ request.flush();
+ request.close();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(
+ "Error posting message to Slack: [" + e.getMessage() +
+ "].", e);
+ }
+
+ response = getResponse(connection);
+ }
+ finally
+ {
+ if (connection != null)
+ {
+ connection.disconnect();
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Opens a connection to the provided Webhook URL.
+ *
+ * @param webhookUrl The Webhook URL to open a connection to.
+ * @return The open connection to the provided Webhook URL.
+ */
+ private HttpURLConnection openConnection(String webhookUrl)
+ {
+ try
+ {
+ return (HttpURLConnection) new URL(webhookUrl).openConnection();
+ }
+ catch (MalformedURLException e)
+ {
+ throw new RuntimeException("Unable to create webhook URL: " +
+ webhookUrl, e);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(
+ "Error opening connection to Slack URL: [" +
+ e.getMessage() + "].", e);
+ }
+ }
+
+ /**
+ * Gets the response payload.
+ *
+ * @param connection The connection.
+ * @return The string representation of the response.
+ */
+ private String getResponse(HttpURLConnection connection)
+ {
+ String response;
+
+ InputStream responseStream;
+ responseStream = null;
+ try
+ {
+ responseStream = connection.getInputStream();
+ response = readResponse(responseStream);
+ }
+ catch (IOException e)
+ {
+ responseStream = connection.getErrorStream();
+ response = readResponse(responseStream);
+ }
+ finally
+ {
+ if (responseStream != null)
+ {
+ try
+ {
+ responseStream.close();
+ }
+ catch (IOException e)
+ {
+ LOGGER.debug("Error closing response stream: " +
+ e.getMessage());
+ }
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Reads the response from the response InputStream.
+ *
+ * @param responseStream The response stream from the connection.
+ * @return The string representation of the response.
+ */
+ private String readResponse(InputStream responseStream)
+ {
+ try
+ {
+ Scanner scanner;
+ scanner = new Scanner(responseStream, "UTF-8");
+ scanner.useDelimiter("\\A");
+
+ return scanner.next();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(
+ "Error reading response: [" + e.getMessage() + "].", e);
+ }
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java b/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java
new file mode 100644
index 0000000..6f79f5b
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.config;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple configuration class to access plugin config values.
+ *
+ * @author Matthew Montgomery
+ */
+public class ProjectConfig
+{
+ /**
+ * The class logger instance.
+ */
+ private static final Logger LOGGER = LoggerFactory.getLogger(
+ ProjectConfig.class);
+
+ /**
+ * The name of the plugin config section to lookup within the gerrit.config
+ * file.
+ */
+ public static final String CONFIG_NAME = "slack-integration";
+
+ private boolean enabled;
+ private String webhookUrl;
+ private String channel;
+ private String username;
+ private String ignore;
+
+ /**
+ * Creates a new instance of the ProjectConfig class for the given project.
+ *
+ * @param configFactory The Gerrit PluginConfigFactory instance to use.
+ * @param project The project to use when looking up a configuration.
+ */
+ public ProjectConfig(PluginConfigFactory configFactory, String project)
+ {
+ enabled = false;
+
+ Project.NameKey projectNameKey;
+ projectNameKey = Project.NameKey.parse(project);
+
+ try
+ {
+ enabled = configFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, CONFIG_NAME).getBoolean(
+ "enabled", false);
+
+ webhookUrl = configFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, CONFIG_NAME).getString(
+ "webhookurl", "");
+
+ channel = configFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, CONFIG_NAME).getString(
+ "channel", "general");
+
+ username = configFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, CONFIG_NAME).getString(
+ "username", "gerrit");
+
+ ignore = configFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, CONFIG_NAME).getString(
+ "ignore", "");
+ }
+ catch (NoSuchProjectException e)
+ {
+ LOGGER.warn("The specified project could not be found: " +
+ project);
+ }
+ }
+
+ public boolean isEnabled()
+ {
+ return enabled;
+ }
+
+ public String getWebhookUrl()
+ {
+ return webhookUrl;
+ }
+
+ public String getChannel()
+ {
+ return channel;
+ }
+
+ public String getUsername()
+ {
+ return username;
+ }
+
+ public String getIgnore()
+ {
+ return ignore;
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGenerator.java b/src/main/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGenerator.java
new file mode 100644
index 0000000..4e0882f
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGenerator.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.cisco.gerrit.plugins.slack.util.ResourceHelper;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A specific MessageGenerator implementation that can generate a message for
+ * a change merged event.
+ *
+ * @author Matthew Montgomery
+ */
+public class ChangeMergedMessageGenerator extends MessageGenerator
+{
+ /**
+ * The class logger instance.
+ */
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(ChangeMergedMessageGenerator.class);
+
+ private ProjectConfig config;
+ private ChangeMergedEvent event;
+
+ /**
+ * Creates a new ChangeMergedMessageGenerator instance using the provided
+ * ChangeMergedEvent instance.
+ *
+ * @param event The ChangeMergedEvent instance to generate a message for.
+ */
+ protected ChangeMergedMessageGenerator(ChangeMergedEvent event,
+ ProjectConfig config)
+ {
+ if (event == null)
+ {
+ throw new NullPointerException("event cannot be null");
+ }
+
+ this.event = event;
+ this.config = config;
+ }
+
+ @Override
+ public boolean shouldPublish()
+ {
+ return true;
+ }
+
+ @Override
+ public String generate()
+ {
+ String message;
+ message = "";
+
+ try
+ {
+ String template;
+ template = ResourceHelper.loadNamedResourceAsString(
+ "basic-message-template.json");
+
+ StringBuilder text;
+ text = new StringBuilder();
+
+ text.append(escape(event.submitter.name));
+ text.append(" merged\\n>>>");
+ text.append(escape(event.change.project));
+ text.append(" (");
+ text.append(escape(event.change.branch));
+ text.append("): ");
+ text.append(escape(event.change.commitMessage.split("\n")[0]));
+ text.append(" (");
+ text.append(escape(event.change.url));
+ text.append(")");
+
+ message = String.format(template, text, config.getChannel(),
+ config.getUsername());
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Error generating message: " + e.getMessage());
+ }
+
+ return message;
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/message/MessageGenerator.java b/src/main/java/com/cisco/gerrit/plugins/slack/message/MessageGenerator.java
new file mode 100644
index 0000000..16b5b4f
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/message/MessageGenerator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+/**
+ * Defines a simple base class for a message generators.
+ *
+ * @author Matthew Montgomery
+ */
+public abstract class MessageGenerator
+{
+ /**
+ * Whether or not the generated message should be published.
+ *
+ * @return True if the message should be published, otherwise false
+ */
+ public abstract boolean shouldPublish();
+
+ /**
+ * Generates an event specific message suitable for publishing.
+ *
+ * @return The generated message.
+ */
+ public abstract String generate();
+
+ /**
+ * Escapes the double quote character.
+ *
+ * @param message The message in which to search escape double quote
+ * characters
+ *
+ * @return The message with all occurrences of the double quote character
+ * escaped.
+ */
+ protected String escape(String message)
+ {
+ if (message != null)
+ {
+ message = message.replace("\"", "\\\"");
+ }
+
+ return message;
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/message/MessageGeneratorFactory.java b/src/main/java/com/cisco/gerrit/plugins/slack/message/MessageGeneratorFactory.java
new file mode 100644
index 0000000..5e026db
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/message/MessageGeneratorFactory.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+
+/**
+ * Factory used to create event specific MessageGenerator instances.
+ *
+ * @author Matthew Montgomery
+ */
+public class MessageGeneratorFactory
+{
+ // Made private to prevent instantiation
+ private MessageGeneratorFactory() {}
+
+ /**
+ * Creates a new MessageGenerator for patchset created events.
+ *
+ * @param event A PatchSetCreatedEvent instance
+ * @param config A ProjectConfig instance for the given event
+ *
+ * @return A MessageGenerator instance capable of generating a message for
+ * a PatchSetCreatedEvent.
+ */
+ public static MessageGenerator newInstance(PatchSetCreatedEvent event,
+ ProjectConfig config)
+ {
+ PatchSetCreatedMessageGenerator messageGenerator;
+ messageGenerator = new PatchSetCreatedMessageGenerator(event, config);
+
+ return messageGenerator;
+ }
+
+ /**
+ * Creates a new MessageGenerator for change merged events.
+ *
+ * @param event A ChangeMergedEvent instance
+ * @param config A ProjectConfig instance for the given event
+ *
+ * @return A MessageGenerator instance capable of generating a message for
+ * a ChangeMergedEvent.
+ */
+ public static MessageGenerator newInstance(ChangeMergedEvent event,
+ ProjectConfig config)
+ {
+ ChangeMergedMessageGenerator messageGenerator;
+ messageGenerator = new ChangeMergedMessageGenerator(event, config);
+
+ return messageGenerator;
+ }
+
+ /**
+ * Creates a new MessageGenerator for unsupported events.
+ *
+ * @param event A ChangeEvent instance
+ * @param config A ProjectConfig instance for the given event
+ *
+ * @return A MessageGenerator instance capable of generating a message for
+ * an unsupported ChangeEvent.
+ */
+ public static MessageGenerator newInstance(ChangeEvent event,
+ ProjectConfig config)
+ {
+ UnsupportedMessageGenerator messageGenerator;
+ messageGenerator = new UnsupportedMessageGenerator(event, config);
+
+ return messageGenerator;
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGenerator.java b/src/main/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGenerator.java
new file mode 100644
index 0000000..f10b7f9
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGenerator.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.cisco.gerrit.plugins.slack.util.ResourceHelper;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A specific MessageGenerator implementation that can generate a message for a
+ * patchset created event.
+ *
+ * @author Matthew Montgomery
+ */
+public class PatchSetCreatedMessageGenerator extends MessageGenerator
+{
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(PatchSetCreatedMessageGenerator.class);
+
+ private PatchSetCreatedEvent event;
+ private ProjectConfig config;
+
+ /**
+ * Creates a new PatchSetCreatedMessageGenerator instance using the
+ * provided PatchSetCreatedEvent instance.
+ *
+ * @param event The PatchSetCreatedEvent instance to generate a
+ * message for.
+ */
+ protected PatchSetCreatedMessageGenerator(PatchSetCreatedEvent event,
+ ProjectConfig config)
+ {
+ if (event == null)
+ {
+ throw new NullPointerException("event cannot be null");
+ }
+
+ this.event = event;
+ this.config = config;
+ }
+
+ @Override
+ public boolean shouldPublish()
+ {
+ boolean result;
+ result = true;
+
+ try
+ {
+ Pattern pattern;
+ pattern = Pattern.compile(config.getIgnore(), Pattern.DOTALL);
+
+ Matcher matcher;
+ matcher = pattern.matcher(event.change.commitMessage);
+
+ // If the ignore pattern matches, publishing should not happen
+ result = !matcher.matches();
+ }
+ catch (Exception e)
+ {
+ LOGGER.warn("The specified ignore pattern was invalid", e);
+ }
+
+ return result;
+ }
+
+ @Override
+ public String generate()
+ {
+ String message;
+ message = "";
+
+ try
+ {
+ String template;
+ template = ResourceHelper.loadNamedResourceAsString(
+ "basic-message-template.json");
+
+ StringBuilder text;
+ text = new StringBuilder();
+
+ text.append(escape(event.uploader.name));
+ text.append(" proposed\\n>>>");
+ text.append(escape(event.change.project));
+ text.append(" (");
+ text.append(escape(event.change.branch));
+ text.append("): ");
+ text.append(escape(event.change.commitMessage.split("\n")[0]));
+ text.append(" (");
+ text.append(escape(event.change.url));
+ text.append(")");
+
+ message = String.format(template, text, config.getChannel(),
+ config.getUsername());
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Error generating message: " + e.getMessage());
+ }
+
+ return message;
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/message/UnsupportedMessageGenerator.java b/src/main/java/com/cisco/gerrit/plugins/slack/message/UnsupportedMessageGenerator.java
new file mode 100644
index 0000000..6dcd767
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/message/UnsupportedMessageGenerator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.google.gerrit.server.events.ChangeEvent;
+
+/**
+ * A specific MessageGenerator implementation that can generate a message for
+ * an unsupported ChangeEvent. The default behavior for this MessageGenerator
+ * is to flag that it should not be published.
+ *
+ * @author Matthew Montgomery
+ */
+public class UnsupportedMessageGenerator extends MessageGenerator
+{
+ private ProjectConfig config;
+ private ChangeEvent event;
+
+ protected UnsupportedMessageGenerator(ChangeEvent event,
+ ProjectConfig config)
+ {
+ if (event == null)
+ {
+ throw new NullPointerException("event cannot be null");
+ }
+
+ this.event = event;
+ this.config = config;
+ }
+
+ @Override
+ public boolean shouldPublish()
+ {
+ return false;
+ }
+
+ @Override
+ public String generate()
+ {
+ StringBuilder message;
+ message = new StringBuilder();
+
+ message.append("Unsupported change event: ");
+ message.append(this.event.toString());
+
+ return message.toString();
+ }
+}
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/util/ResourceHelper.java b/src/main/java/com/cisco/gerrit/plugins/slack/util/ResourceHelper.java
new file mode 100644
index 0000000..b7482dc
--- /dev/null
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/util/ResourceHelper.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple helper class to load resources via the current classloader.
+ */
+public final class ResourceHelper
+{
+ // Made private to prevent instantiation.
+ private ResourceHelper() { }
+
+ /**
+ * Loads the named resource as an InputStream from the current
+ * classloader.
+ *
+ * @param name The named resource.
+ *
+ * @return The named resource as an InputStream, null if not found.
+ *
+ * @throws IOException In the event of an IO error
+ */
+ public static InputStream loadNamedResourceAsStream(String name)
+ throws IOException
+ {
+ InputStream result;
+ result = null;
+
+ if (name != null)
+ {
+ ClassLoader classLoader;
+ classLoader = ResourceHelper.class.getClassLoader();
+
+ result = classLoader.getResourceAsStream(name);
+ }
+
+ return result;
+ }
+
+ /**
+ * Loads the named resource as a String from the current classloader.
+ *
+ * @param name The named resource.
+ *
+ * @return The named resource as a String, null if not found.
+ *
+ * @throws IOException In the event of an IO error
+ */
+ public static String loadNamedResourceAsString(String name)
+ throws IOException
+ {
+ String result;
+ result = null;
+
+ InputStream inputStream;
+ inputStream = ResourceHelper.loadNamedResourceAsStream(name);
+
+ if (inputStream != null)
+ {
+ StringBuffer buffer;
+ buffer = new StringBuffer();
+
+ byte[] b;
+ b = new byte[4096];
+
+ for (int n; (n = inputStream.read(b)) != -1; )
+ {
+ buffer.append(new String(b, 0, n));
+ }
+
+ result = buffer.toString();
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/resources/basic-message-template.json b/src/main/resources/basic-message-template.json
new file mode 100644
index 0000000..f61b2bf
--- /dev/null
+++ b/src/main/resources/basic-message-template.json
@@ -0,0 +1 @@
+{"text": "%s","channel": "#%s","username": "%s"}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/EventListenerTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/EventListenerTest.java
new file mode 100644
index 0000000..bed0523
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/EventListenerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack;
+
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class EventListenerTest
+{
+ private PatchSetCreatedEvent mockPatchSetCreatedEvent =
+ mock(PatchSetCreatedEvent.class);
+ private ChangeMergedEvent mocChangeMergedEvent =
+ mock(ChangeMergedEvent.class);
+
+ private EventListener eventListener;
+
+ @Before
+ public void setup() throws Exception
+ {
+ eventListener = new EventListener();
+ }
+
+ @Test
+ public void handlesPatchSetCreatedEvents() throws Exception
+ {
+ // Wat? Placeholder, as I need to think about out how to test...
+ assertTrue(true);
+ }
+}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java
new file mode 100644
index 0000000..c52651c
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.client;
+
+import com.cisco.gerrit.plugins.slack.util.ResourceHelper;
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import static org.junit.Assert.assertTrue;
+
+public class WebhookClientIntegrationTest
+{
+ @Test
+ public void canPublishMessage() throws Exception
+ {
+ WebhookClient client;
+ client = new WebhookClient();
+
+ InputStream testProperties;
+ testProperties = ResourceHelper.loadNamedResourceAsStream(
+ "test.properties");
+
+ Properties properties;
+ properties = new Properties();
+ properties.load(testProperties);
+
+ testProperties.close();
+
+ String message;
+ message = "{\"text\": \"Integration Test Message\"}";
+
+ String webhookUrl;
+ webhookUrl = properties.getProperty("webhook-url");
+
+ assertTrue(client.publish(message, webhookUrl));
+ }
+}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java
new file mode 100644
index 0000000..3a89b3b
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.config;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the PluginConfig class.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({Project.NameKey.class})
+public class ProjectConfigTest
+{
+ private static final String PROJECT_NAME = "test-project";
+
+ private Project.NameKey mockNameKey =
+ mock(Project.NameKey.class);
+
+ private PluginConfigFactory mockConfigFactory =
+ mock(PluginConfigFactory.class);
+
+ private PluginConfig mockPluginConfig =
+ mock(PluginConfig.class);
+
+ private ProjectConfig config;
+
+ @Before
+ public void setup() throws Exception
+ {
+ PowerMockito.mockStatic(Project.NameKey.class);
+ when(Project.NameKey.parse(PROJECT_NAME)).thenReturn(mockNameKey);
+
+ Project.NameKey projectNameKey;
+ projectNameKey = Project.NameKey.parse(PROJECT_NAME);
+
+ // Setup mocks
+ when(mockConfigFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, ProjectConfig.CONFIG_NAME))
+ .thenReturn(mockPluginConfig);
+
+ when(mockPluginConfig.getBoolean("enabled", false))
+ .thenReturn(true);
+ when(mockPluginConfig.getString("webhookurl", ""))
+ .thenReturn("https://webook/");
+ when(mockPluginConfig.getString("channel", "general"))
+ .thenReturn("test-channel");
+ when(mockPluginConfig.getString("username", "gerrit"))
+ .thenReturn("test-user");
+ when(mockPluginConfig.getString("ignore", ""))
+ .thenReturn("^WIP.*");
+
+ config = new ProjectConfig(mockConfigFactory, PROJECT_NAME);
+ }
+
+ @Test
+ public void testIsEnabled() throws Exception
+ {
+ assertTrue(config.isEnabled());
+ }
+
+ @Test
+ public void testGetWebhookUrl() throws Exception
+ {
+ assertThat(config.getWebhookUrl(), is(equalTo("https://webook/")));
+ }
+
+ @Test
+ public void testGetChannel() throws Exception
+ {
+ assertThat(config.getChannel(), is(equalTo("test-channel")));
+ }
+
+ @Test
+ public void testGetUsername() throws Exception
+ {
+ assertThat(config.getUsername(), is(equalTo("test-user")));
+ }
+}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java
new file mode 100644
index 0000000..49ee3db
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ChangeAttribute;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the ChangeMergedMessageGeneratorTest class. The expected behavior
+ * is that the ChangeMergedMessageGenerator should publish regardless of a
+ * configured ignore pattern.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({Project.NameKey.class})
+public class ChangeMergedMessageGeneratorTest
+{
+ private static final String PROJECT_NAME = "test-project";
+
+ private Project.NameKey mockNameKey =
+ mock(Project.NameKey.class);
+
+ private PluginConfigFactory mockConfigFactory =
+ mock(PluginConfigFactory.class);
+
+ private PluginConfig mockPluginConfig =
+ mock(PluginConfig.class);
+
+ private ChangeMergedEvent mockEvent = mock(ChangeMergedEvent.class);
+ private AccountAttribute mockAccount = mock(AccountAttribute.class);
+ private ChangeAttribute mockChange = mock(ChangeAttribute.class);
+
+ private ProjectConfig config;
+
+ @Before
+ public void setup() throws Exception
+ {
+ PowerMockito.mockStatic(Project.NameKey.class);
+ when(Project.NameKey.parse(PROJECT_NAME)).thenReturn(mockNameKey);
+
+ Project.NameKey projectNameKey;
+ projectNameKey = Project.NameKey.parse(PROJECT_NAME);
+
+ // Setup mocks
+ when(mockConfigFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, ProjectConfig.CONFIG_NAME))
+ .thenReturn(mockPluginConfig);
+
+ when(mockPluginConfig.getBoolean("enabled", false))
+ .thenReturn(true);
+ when(mockPluginConfig.getString("webhookurl", ""))
+ .thenReturn("https://webook/");
+ when(mockPluginConfig.getString("channel", "general"))
+ .thenReturn("testchannel");
+ when(mockPluginConfig.getString("username", "gerrit"))
+ .thenReturn("testuser");
+ when(mockPluginConfig.getString("ignore", ""))
+ .thenReturn("^WIP.*");
+
+ config = new ProjectConfig(mockConfigFactory, PROJECT_NAME);
+ }
+
+ @Test
+ public void factoryCreatesExpectedType() throws Exception
+ {
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator instanceof ChangeMergedMessageGenerator,
+ is(true));
+ }
+
+ @Test
+ public void publishesWhenExpected() throws Exception
+ {
+ // Setup mocks
+ mockEvent.change = mockChange;
+ mockChange.commitMessage = "This is a title\nAnd a the body.";
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator.shouldPublish(), is(true));
+ }
+
+ @Test
+ public void doesNotPublishWhenExpected() throws Exception
+ {
+ // Setup mocks
+ mockEvent.change = mockChange;
+ mockChange.commitMessage = "WIP:This is a title\nAnd a the body.";
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator.shouldPublish(), is(true));
+ }
+
+ @Test
+ public void handlesInvalidIgnorePatterns() throws Exception
+ {
+ when(mockPluginConfig.getString("ignore", ""))
+ .thenReturn(null);
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator.shouldPublish(), is(true));
+ }
+
+ @Test
+ public void generatesExpectedMessage() throws Exception
+ {
+ // Setup mocks
+ mockEvent.change = mockChange;
+ mockEvent.submitter = mockAccount;
+
+ mockChange.project = "testproject";
+ mockChange.branch = "master";
+ mockChange.url = "https://change/";
+ mockChange.commitMessage = "This is a title\nAnd a the body.";
+
+ mockAccount.name = "Unit Tester";
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ String expectedResult;
+ expectedResult = "{\"text\": \"Unit Tester merged\\n>>>" +
+ "testproject (master): This is a title (https://change/)\"," +
+ "\"channel\": \"#testchannel\",\"username\": \"testuser\"}\n";
+
+ String actualResult;
+ actualResult = messageGenerator.generate();
+
+ assertThat(actualResult, is(equalTo(expectedResult)));
+ }
+}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/MessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/MessageGeneratorTest.java
new file mode 100644
index 0000000..63be613
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/MessageGeneratorTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class MessageGeneratorTest
+{
+
+ @Test
+ public void testEscape() throws Exception
+ {
+ MessageGenerator messageGenerator;
+ messageGenerator = new MessageGenerator()
+ {
+ @Override
+ public boolean shouldPublish()
+ {
+ return false;
+ }
+
+ @Override
+ public String generate()
+ {
+ return null;
+ }
+ };
+
+ assertThat(messageGenerator.escape("\""), is(equalTo("\\\"")));
+ }
+}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java
new file mode 100644
index 0000000..e534600
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.message;
+
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ChangeAttribute;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the PatchSetCreatedMessageGeneratorTest class.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({Project.NameKey.class})
+public class PatchSetCreatedMessageGeneratorTest
+{
+ private static final String PROJECT_NAME = "test-project";
+
+ private Project.NameKey mockNameKey =
+ mock(Project.NameKey.class);
+
+ private PluginConfigFactory mockConfigFactory =
+ mock(PluginConfigFactory.class);
+
+ private PluginConfig mockPluginConfig =
+ mock(PluginConfig.class);
+
+ private PatchSetCreatedEvent mockEvent = mock(PatchSetCreatedEvent.class);
+ private AccountAttribute mockAccount = mock(AccountAttribute.class);
+ private ChangeAttribute mockChange = mock(ChangeAttribute.class);
+
+ private ProjectConfig config;
+
+ @Before
+ public void setup() throws Exception
+ {
+ PowerMockito.mockStatic(Project.NameKey.class);
+ when(Project.NameKey.parse(PROJECT_NAME)).thenReturn(mockNameKey);
+
+ Project.NameKey projectNameKey;
+ projectNameKey = Project.NameKey.parse(PROJECT_NAME);
+
+ // Setup mocks
+ when(mockConfigFactory.getFromProjectConfigWithInheritance(
+ projectNameKey, ProjectConfig.CONFIG_NAME))
+ .thenReturn(mockPluginConfig);
+
+ when(mockPluginConfig.getBoolean("enabled", false))
+ .thenReturn(true);
+ when(mockPluginConfig.getString("webhookurl", ""))
+ .thenReturn("https://webook/");
+ when(mockPluginConfig.getString("channel", "general"))
+ .thenReturn("testchannel");
+ when(mockPluginConfig.getString("username", "gerrit"))
+ .thenReturn("testuser");
+ when(mockPluginConfig.getString("ignore", ""))
+ .thenReturn("^WIP.*");
+
+ config = new ProjectConfig(mockConfigFactory, PROJECT_NAME);
+ }
+
+ @Test
+ public void factoryCreatesExpectedType() throws Exception
+ {
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator instanceof PatchSetCreatedMessageGenerator,
+ is(true));
+ }
+
+ @Test
+ public void publishesWhenExpected() throws Exception
+ {
+ // Setup mocks
+ mockEvent.change = mockChange;
+ mockChange.commitMessage = "This is a title\nAnd a the body.";
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator.shouldPublish(), is(true));
+ }
+
+ @Test
+ public void doesNotPublishWhenExpected() throws Exception
+ {
+ // Setup mocks
+ mockEvent.change = mockChange;
+ mockChange.commitMessage = "WIP-This is a title\nAnd a the body.";
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator.shouldPublish(), is(false));
+ }
+
+ @Test
+ public void handlesInvalidIgnorePatterns() throws Exception
+ {
+ when(mockPluginConfig.getString("ignore", ""))
+ .thenReturn(null);
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ assertThat(messageGenerator.shouldPublish(), is(true));
+ }
+
+ @Test
+ public void generatesExpectedMessage() throws Exception
+ {
+ // Setup mocks
+ mockEvent.change = mockChange;
+ mockEvent.uploader = mockAccount;
+
+ mockChange.project = "testproject";
+ mockChange.branch = "master";
+ mockChange.url = "https://change/";
+ mockChange.commitMessage = "This is a title\nAnd a the body.";
+
+ mockAccount.name = "Unit Tester";
+
+ // Test
+ MessageGenerator messageGenerator;
+ messageGenerator = MessageGeneratorFactory.newInstance(
+ mockEvent, config);
+
+ String expectedResult;
+ expectedResult = "{\"text\": \"Unit Tester proposed\\n>>>" +
+ "testproject (master): This is a title (https://change/)\"," +
+ "\"channel\": \"#testchannel\",\"username\": \"testuser\"}\n";
+
+ String actualResult;
+ actualResult = messageGenerator.generate();
+
+ assertThat(actualResult, is(equalTo(expectedResult)));
+ }
+}
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/util/ResourceHelperTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/util/ResourceHelperTest.java
new file mode 100644
index 0000000..3a9c046
--- /dev/null
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/util/ResourceHelperTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Cisco Systems, 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.cisco.gerrit.plugins.slack.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ResourceHelperTest
+{
+ private static final String RESOURCE_NAME = "test.properties";
+
+ @Test
+ public void testLoadNamedResourceAsStream() throws Exception
+ {
+ assertNotNull(ResourceHelper.loadNamedResourceAsStream(RESOURCE_NAME));
+ }
+
+ @Test
+ public void testLoadNamedResourceAsString() throws Exception
+ {
+ String resource;
+ resource = ResourceHelper.loadNamedResourceAsString(RESOURCE_NAME);
+
+ assertNotNull(resource);
+ assertTrue(resource.length() > 0);
+ }
+}
diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties
new file mode 100644
index 0000000..615036b
--- /dev/null
+++ b/src/test/resources/test.properties
@@ -0,0 +1,18 @@
+#
+# Copyright 2016 Cisco Systems, 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.
+#
+#
+
+webhook-url=https://hooks.slack.com/services/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