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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