Merge "Harmonize action names with names used in hooks-its"
diff --git a/pom.xml b/pom.xml
index 0741b55..8b8ff4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,9 @@
     <Gerrit-ReloadMode>reload</Gerrit-ReloadMode>
     <Gerrit-InitStep>com.googlesource.gerrit.plugins.hooks.bz.InitBugzilla</Gerrit-InitStep>
     <Gerrit-Module>com.googlesource.gerrit.plugins.hooks.bz.BugzillaModule</Gerrit-Module>
+    <easymockVersion>3.0</easymockVersion>
+    <powermockVersion>1.5</powermockVersion>
+    <slf4jVersion>1.6.2</slf4jVersion>
   </properties>
 
   <build>
@@ -100,6 +103,30 @@
       <artifactId>j2bugzilla</artifactId>
       <version>2.2-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>${slf4jVersion}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <version>${easymockVersion}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <version>${powermockVersion}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-easymock</artifactId>
+      <version>${powermockVersion}</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <repositories>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java
index 4a79142..0a473a8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacade.java
@@ -217,7 +217,11 @@
 
   @Override
   public String createLinkForWebui(String url, String text) {
-    return url + " ("+text+")";
+    String ret = url;
+    if (text != null && ! text.equals(url)) {
+        ret += " (" + text + ")";
+    }
+    return ret;
   }
 
   private String healthCheckAccess() throws BugzillaException, ConnectionException {
diff --git a/src/main/resources/Documentation/.gitignore b/src/main/resources/Documentation/.gitignore
new file mode 100644
index 0000000..2d19fc7
--- /dev/null
+++ b/src/main/resources/Documentation/.gitignore
@@ -0,0 +1 @@
+*.html
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacadeTest.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacadeTest.java
new file mode 100644
index 0000000..46f7c79
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/bz/BugzillaItsFacadeTest.java
@@ -0,0 +1,109 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.hooks.bz;
+
+import static org.easymock.EasyMock.expect;
+
+import org.eclipse.jgit.lib.Config;
+
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
+
+public class BugzillaItsFacadeTest extends LoggingMockingTestCase {
+  private Injector injector;
+  private Config serverConfig;
+
+  public void testCreateLinkForWebUiPlain() {
+    mockUnconnectableBugzilla();
+
+    replayMocks();
+
+    BugzillaItsFacade itsFacade = createBugzillaItsFacade();
+    String actual = itsFacade.createLinkForWebui("Test-Url", "Test-Text");
+
+    assertNotNull("Created link is null", actual);
+    assertTrue("Created link does not contain url",
+        actual.contains("Test-Url"));
+    assertTrue("Created link does not contain text",
+        actual.contains("Test-Text"));
+
+    assertUnconnectableBugzilla();
+  }
+
+  public void testCreateLinkForWebUiUrlEqualsText() {
+    mockUnconnectableBugzilla();
+
+    replayMocks();
+
+    BugzillaItsFacade itsFacade = createBugzillaItsFacade();
+    String actual = itsFacade.createLinkForWebui("Test-Url", "Test-Url");
+
+    assertNotNull("Created link is null", actual);
+    assertEquals("Created link does not match", "Test-Url", actual);
+
+    assertUnconnectableBugzilla();
+  }
+
+  public void testCreateLinkForWebUiUrlEqualsNull() {
+    mockUnconnectableBugzilla();
+
+    replayMocks();
+
+    BugzillaItsFacade itsFacade = createBugzillaItsFacade();
+    String actual = itsFacade.createLinkForWebui("Test-Url", null);
+
+    assertNotNull("Created link is null", actual);
+    assertEquals("Created link does not match", "Test-Url", actual);
+
+    assertUnconnectableBugzilla();
+  }
+
+  private BugzillaItsFacade createBugzillaItsFacade() {
+    return injector.getInstance(BugzillaItsFacade.class);
+  }
+
+  private void mockUnconnectableBugzilla() {
+    expect(serverConfig.getString("bugzilla",  null, "url"))
+    .andReturn("<no-url>").anyTimes();
+    expect(serverConfig.getString("bugzilla",  null, "username"))
+    .andReturn("none").anyTimes();
+    expect(serverConfig.getString("bugzilla",  null, "password"))
+    .andReturn("none").anyTimes();
+  }
+
+  private void assertUnconnectableBugzilla() {
+    assertLogMessageContains("Connecting to bugzilla");
+    assertLogMessageContains("Unable to connect");
+    assertLogMessageContains("Bugzilla is currently not available");
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+
+    injector = Guice.createInjector(new TestModule());
+  }
+
+  private class TestModule extends FactoryModule {
+    @Override
+    protected void configure() {
+      serverConfig = createMock(Config.class);
+      bind(Config.class).annotatedWith(GerritServerConfig.class)
+          .toInstance(serverConfig);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
new file mode 100644
index 0000000..4bc25b8
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.hooks.testutil;
+
+import com.google.common.collect.Lists;
+import com.googlesource.gerrit.plugins.hooks.testutil.MockingTestCase;
+import com.googlesource.gerrit.plugins.hooks.testutil.log.LogUtil;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.junit.After;
+
+import java.util.Iterator;
+import org.apache.log4j.Level;
+
+public abstract class LoggingMockingTestCase extends MockingTestCase {
+
+  private java.util.Collection<LoggingEvent> loggedEvents;
+
+  protected final void assertLogMessageContains(String needle, Level level) {
+    LoggingEvent hit = null;
+    Iterator<LoggingEvent> iter = loggedEvents.iterator();
+    while (hit == null && iter.hasNext()) {
+      LoggingEvent event = iter.next();
+      if (event.getRenderedMessage().contains(needle)) {
+        if (level == null || level.equals(event.getLevel())) {
+          hit = event;
+        }
+      }
+    }
+    assertNotNull("Could not find log message containing '" + needle + "'",
+        hit);
+    assertTrue("Could not remove log message containing '" + needle + "'",
+        loggedEvents.remove(hit));
+  }
+
+  protected final void assertLogMessageContains(String needle) {
+    assertLogMessageContains(needle, null);
+  }
+
+  protected final void assertLogThrowableMessageContains(String needle) {
+    LoggingEvent hit = null;
+    Iterator<LoggingEvent> iter = loggedEvents.iterator();
+    while (hit == null && iter.hasNext()) {
+      LoggingEvent event = iter.next();
+
+      if (event.getThrowableInformation().getThrowable().toString()
+          .contains(needle)) {
+        hit = event;
+      }
+    }
+    assertNotNull("Could not find log message with a Throwable containing '"
+        + needle + "'", hit);
+    assertTrue("Could not remove log message with a Throwable containing '"
+        + needle + "'", loggedEvents.remove(hit));
+  }
+
+  // As the PowerMock runner does not pass through runTest, we inject log
+  // verification through @After
+  @After
+  public final void assertNoUnassertedLogEvents() {
+    if (loggedEvents.size() > 0) {
+      LoggingEvent event = loggedEvents.iterator().next();
+      String msg = "Found untreated logged events. First one is:\n";
+      msg += event.getRenderedMessage();
+      if (event.getThrowableInformation() != null) {
+        msg += "\n" + event.getThrowableInformation().getThrowable();
+      }
+      fail(msg);
+    }
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    loggedEvents = Lists.newArrayList();
+
+    // The logger we're interested is class name without the trailing "Test".
+    // While this is not the most general approach it is sufficient for now,
+    // and we can improve later to allow tests to specify which loggers are
+    // to check.
+    String logName = this.getClass().getCanonicalName();
+    logName = logName.substring(0, logName.length()-4);
+    LogUtil.logToCollection(logName, loggedEvents);
+  }
+
+  @Override
+  protected void runTest() throws Throwable {
+    super.runTest();
+    // Plain JUnit runner does not pick up @After, so we add it here
+    // explicitly. Note, that we cannot put this into tearDown, as failure
+    // to verify mocks would bail out and might leave open resources from
+    // subclasses open.
+    assertNoUnassertedLogEvents();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
new file mode 100644
index 0000000..819b138
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
@@ -0,0 +1,153 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.hooks.testutil;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Test case with some support for automatically verifying mocks.
+ */
+public abstract class MockingTestCase extends TestCase {
+  private Collection<Object> mocks;
+  private Collection<IMocksControl> mockControls;
+  private boolean mocksReplayed;
+  private boolean usePowerMock;
+
+  /**
+   * Create and register a mock control.
+   *
+   * @return The mock control instance.
+   */
+  protected final IMocksControl createMockControl() {
+    IMocksControl mockControl = EasyMock.createControl();
+    assertTrue("Adding mock control failed", mockControls.add(mockControl));
+    return mockControl;
+  }
+
+  /**
+   * Create and register a mock.
+   *
+   * Creates a mock and registers it in the list of created mocks, so it gets
+   * treated automatically upon {@code replay} and {@code verify};
+   * @param toMock The class to create a mock for.
+   * @return The mock instance.
+   */
+  protected final <T> T createMock(Class<T> toMock) {
+    return createMock(toMock, null);
+  }
+
+  /**
+   * Create a mock for a mock control and register a mock.
+   *
+   * Creates a mock and registers it in the list of created mocks, so it gets
+   * treated automatically upon {@code replay} and {@code verify};
+   * @param toMock The class to create a mock for.
+   * @param control The mock control to create the mock on. If null, do not use
+   *    a specific control.
+   * @return The mock instance.
+   */
+  protected final <T> T createMock(Class<T> toMock, IMocksControl control) {
+    assertFalse("Mocks have already been set to replay", mocksReplayed);
+    final T mock;
+    if (control == null) {
+      if (usePowerMock) {
+        mock = PowerMock.createMock(toMock);
+      } else {
+        mock = EasyMock.createMock(toMock);
+      }
+      assertTrue("Adding " + toMock.getName() + " mock failed",
+          mocks.add(mock));
+    } else {
+      mock = control.createMock(toMock);
+    }
+    return mock;
+  }
+
+  /**
+   * Set all registered mocks to replay
+   */
+  protected final void replayMocks() {
+    assertFalse("Mocks have already been set to replay", mocksReplayed);
+    if (usePowerMock) {
+      PowerMock.replayAll();
+    } else {
+      EasyMock.replay(mocks.toArray());
+    }
+    for (IMocksControl mockControl : mockControls) {
+      mockControl.replay();
+    }
+    mocksReplayed = true;
+  }
+
+  /**
+   * Verify all registered mocks
+   *
+   * This method is called automatically at the end of a test. Nevertheless,
+   * it is safe to also call it beforehand, if this better meets the
+   * verification part of a test.
+   */
+  // As the PowerMock runner does not pass through runTest, we inject mock
+  // verification through @After
+  @After
+  public final void verifyMocks() {
+    if (!mocks.isEmpty() || !mockControls.isEmpty()) {
+      assertTrue("Created mocks have not been set to replay. Call replayMocks "
+          + "within the test", mocksReplayed);
+      if (usePowerMock) {
+        PowerMock.verifyAll();
+      } else {
+        EasyMock.verify(mocks.toArray());
+      }
+      for (IMocksControl mockControl : mockControls) {
+        mockControl.verify();
+      }
+    }
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+
+    usePowerMock = false;
+    RunWith runWith = this.getClass().getAnnotation(RunWith.class);
+    if (runWith != null) {
+      usePowerMock = PowerMockRunner.class.isAssignableFrom(runWith.value());
+    }
+
+    mocks = new ArrayList<Object>();
+    mockControls = new ArrayList<IMocksControl>();
+    mocksReplayed = false;
+  }
+
+  @Override
+  protected void runTest() throws Throwable {
+    super.runTest();
+    // Plain JUnit runner does not pick up @After, so we add it here
+    // explicitly. Note, that we cannot put this into tearDown, as failure
+    // to verify mocks would bail out and might leave open resources from
+    // subclasses open.
+    verifyMocks();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
new file mode 100644
index 0000000..f63c7c3
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.hooks.testutil.log;
+
+import com.google.common.collect.Lists;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * Log4j appender that logs into a list
+ */
+public class CollectionAppender extends AppenderSkeleton {
+  private Collection<LoggingEvent> events;
+
+  public CollectionAppender() {
+    events = new LinkedList<LoggingEvent>();
+  }
+
+  public CollectionAppender(Collection<LoggingEvent> events) {
+    this.events = events;
+  }
+
+  @Override
+  public boolean requiresLayout() {
+    return false;
+  }
+
+  @Override
+  protected void append(LoggingEvent event) {
+    if (! events.add(event)) {
+      throw new RuntimeException("Could not append event " + event);
+    }
+  }
+
+  @Override
+  public void close() {
+  }
+
+  public Collection<LoggingEvent> getLoggedEvents() {
+    return Lists.newLinkedList(events);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
new file mode 100644
index 0000000..8ea559c
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.hooks.testutil.log;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+import com.googlesource.gerrit.plugins.hooks.testutil.log.CollectionAppender;
+
+
+import java.util.Collection;
+
+public class LogUtil {
+  public static CollectionAppender logToCollection(String logName,
+      Collection<LoggingEvent> collection) {
+    Logger log = LogManager.getLogger(logName);
+    CollectionAppender listAppender = new CollectionAppender(collection);
+    log.removeAllAppenders();
+    log.addAppender(listAppender);
+    return listAppender;
+  }
+}