Implement help command, add more tests, improve messages
authorJan Dittberner <jandd@cacert.org>
Thu, 5 Apr 2018 17:01:45 +0000 (19:01 +0200)
committerJan Dittberner <jandd@cacert.org>
Thu, 5 Apr 2018 17:01:45 +0000 (19:01 +0200)
14 files changed:
src/main/java/org/cacert/votebot/shared/CAcertVoteMechanics.java
src/main/java/org/cacert/votebot/shared/IRCClient.java
src/main/java/org/cacert/votebot/shared/exceptions/InvalidChannelName.java
src/main/java/org/cacert/votebot/shared/exceptions/InvalidNickName.java
src/main/java/org/cacert/votebot/vote/CAcertVoteBot.java
src/main/java/org/cacert/votebot/vote/VoteBotCommand.java [new file with mode: 0644]
src/main/resources/application.properties
src/main/resources/messages.properties
src/test/java/org/cacert/votebot/shared/CAcertVoteMechanicsTest.java
src/test/java/org/cacert/votebot/shared/IRCClientTest.java [new file with mode: 0644]
src/test/java/org/cacert/votebot/shared/exceptions/InvalidChannelNameTest.java
src/test/java/org/cacert/votebot/shared/exceptions/InvalidNickNameTest.java
src/test/java/org/cacert/votebot/vote/CAcertVoteBotTest.java [new file with mode: 0644]
src/test/java/org/cacert/votebot/vote/TestConfiguration.java [new file with mode: 0644]

index 7f91b6d..e17a304 100644 (file)
@@ -1,45 +1,47 @@
 /*
  * Copyright (c) 2015  Felix Doerre
  * Copyright (c) 2015  Benny Baumann
- * Copyright (c) 2016  Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package org.cacert.votebot.shared;
 
 import org.springframework.stereotype.Component;
 
+import java.text.MessageFormat;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.ResourceBundle;
+import java.util.regex.Pattern;
 
 /**
  * Represents the voting-automate for voting in IRC channels.
  */
 @Component
-public final class CAcertVoteMechanics {
-    private static final String PROXY_RE = "^\\s*proxy\\s.*";
+public class CAcertVoteMechanics {
+    private static final Pattern PROXY_RE = Pattern.compile("^\\s*proxy\\s.*");
     private static final int VOTE_MESSAGE_PART_COUNT = 3;
 
     private State state = State.IDLE;
     private String topic;
     private final Map<String, VoteType> votes = new HashMap<>();
-    private final ResourceBundle resourceBundle = ResourceBundle.getBundle("messages");
+    private final ResourceBundle messages = ResourceBundle.getBundle("messages");
 
     /**
      * Voting state indicating whether a vote is currently running or not.
@@ -59,18 +61,18 @@ public final class CAcertVoteMechanics {
         votes.put(voter, type);
 
         if (voter.equals(actor)) {
-            return String.format(resourceBundle.getString("count_vote"), actor, type);
+            return MessageFormat.format(messages.getString("count_vote"), actor, type);
         } else {
-            return String.format(resourceBundle.getString("count_proxy_vote"), actor, voter, type);
+            return MessageFormat.format(messages.getString("count_proxy_vote"), actor, voter, type);
         }
     }
 
     private String voteError(final String actor) {
-        return String.format(resourceBundle.getString("vote_not_understood"), actor);
+        return MessageFormat.format(messages.getString("vote_not_understood"), actor);
     }
 
     private String proxyVoteError(final String actor) {
-        return String.format(resourceBundle.getString("invalid_proxy_vote"), actor);
+        return MessageFormat.format(messages.getString("invalid_proxy_vote"), actor);
     }
 
     /**
@@ -82,13 +84,13 @@ public final class CAcertVoteMechanics {
      */
     public synchronized String evaluateVote(final String actor, final String txt) {
         if (state != State.RUNNING) {
-            return String.format(resourceBundle.getString("no_vote_running"), actor);
+            return MessageFormat.format(messages.getString("no_vote_running"), actor);
         }
 
         final String voter;
         final String value;
 
-        if (txt.toLowerCase().matches(PROXY_RE)) {
+        if (PROXY_RE.matcher(txt.toLowerCase()).matches()) {
             String[] parts = txt.split("\\s+");
             if (parts.length == VOTE_MESSAGE_PART_COUNT) {
                 voter = parts[1];
@@ -116,7 +118,7 @@ public final class CAcertVoteMechanics {
      */
     public synchronized String callVote(final String topic) {
         if (state != State.IDLE) {
-            return resourceBundle.getString("vote_running");
+            return messages.getString("vote_running");
         }
 
         this.topic = topic;
@@ -124,7 +126,7 @@ public final class CAcertVoteMechanics {
 
         state = State.RUNNING;
 
-        return resourceBundle.getString("vote_started");
+        return messages.getString("vote_started");
     }
 
     /**
@@ -142,7 +144,7 @@ public final class CAcertVoteMechanics {
         final String[] results = new String[VoteType.values().length];
 
         for (int i = 0; i < results.length; i++) {
-            results[i] = String.format("%s: %d", VoteType.values()[i], resultCounts[i]);
+            results[i] = MessageFormat.format("{0}: {1}", VoteType.values()[i], resultCounts[i]);
         }
 
         votes.clear();
index d82068c..3f17366 100644 (file)
@@ -1,32 +1,26 @@
 /*
  * Copyright (c) 2015  Felix Doerre
  * Copyright (c) 2015  Benny Baumann
- * Copyright (c) 2016  Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 package org.cacert.votebot.shared;
 
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.*;
 import org.cacert.votebot.shared.exceptions.IRCClientException;
 import org.cacert.votebot.shared.exceptions.InvalidChannelName;
 import org.cacert.votebot.shared.exceptions.InvalidNickName;
@@ -50,13 +44,9 @@ import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.FileAttribute;
 import java.nio.file.attribute.PosixFilePermission;
 import java.nio.file.attribute.PosixFilePermissions;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.Semaphore;
+import java.util.regex.Pattern;
 
 /**
  * This class encapsulates the communication with the IRC server.
@@ -66,7 +56,7 @@ import java.util.concurrent.Semaphore;
  */
 @SuppressWarnings({"unused", "WeakerAccess"})
 @Component
-public final class IRCClient {
+public class IRCClient {
     /**
      * Logger.
      */
@@ -74,13 +64,13 @@ public final class IRCClient {
     /**
      * Regular expression to validate IRC nick names.
      */
-    private static final String NICK_RE = "[a-zA-Z0-9_-]+";
+    private static final Pattern NICK_RE = Pattern.compile("[a-zA-Z0-9_-]+");
     /**
      * Regular expression to validate IRC channel names.
      */
-    private static final String CHANNEL_RE = "[a-zA-Z0-9_-]+";
+    private static final Pattern CHANNEL_RE = Pattern.compile("[a-zA-Z0-9_-]+");
 
-    private final Semaphore loggedin = new Semaphore(0);
+    private final Semaphore loggedin = new Semaphore(1);
     private PrintWriter out;
     private final Set<String> joinedChannels = new HashSet<>();
     private IRCBot targetBot;
@@ -130,7 +120,7 @@ public final class IRCClient {
     private void initialize(final String nick, final String server, final int port, final boolean ssl)
     throws IOException,
             InterruptedException, IRCClientException {
-        if (!nick.matches(NICK_RE)) {
+        if (!NICK_RE.matcher(nick).matches()) {
             throw new IRCClientException(String.format("malformed nickname %s", nick));
         }
 
@@ -146,8 +136,8 @@ public final class IRCClient {
 
         new ServerReader(in);
 
-        out.println("USER " + nick + " 0 * :unknown");
         out.println("NICK " + nick);
+        out.println("USER " + nick + " 0 * :CAcert Votebot");
 
         loggedin.acquire();
     }
@@ -164,7 +154,7 @@ public final class IRCClient {
             throw new NoBotAssigned();
         }
 
-        if (!channel.matches(CHANNEL_RE)) {
+        if (!CHANNEL_RE.matcher(channel).matches()) {
             throw new InvalidChannelName(channel);
         }
     }
@@ -181,7 +171,7 @@ public final class IRCClient {
             throw new NoBotAssigned();
         }
 
-        if (!nick.matches(NICK_RE)) {
+        if (!NICK_RE.matcher(nick).matches()) {
             throw new InvalidNickName(nick);
         }
     }
@@ -236,7 +226,9 @@ public final class IRCClient {
     public void send(final String msg, final String channel) throws IRCClientException {
         checkChannelPreconditions(channel);
 
-        out.println(String.format("PRIVMSG #%s :%s", channel, msg));
+        for (String line : msg.split("\n")) {
+            out.println(String.format("PRIVMSG #%s :%s", channel, line));
+        }
     }
 
     /**
@@ -249,7 +241,9 @@ public final class IRCClient {
     public void sendPrivate(final String msg, final String to) throws IRCClientException {
         checkPrivateMessagePreconditions(to);
 
-        out.println(String.format("PRIVMSG %s :%s", to, msg));
+        for (String line : msg.split("\n")) {
+            out.println(String.format("PRIVMSG %s :%s", to, line));
+        }
     }
 
     /**
@@ -261,6 +255,14 @@ public final class IRCClient {
         targetBot = bot;
     }
 
+
+    /**
+     * Quit the IRC session.
+     */
+    public void quit() {
+        out.println("QUIT");
+    }
+
     /**
      * Reader thread for handling the IRC connection.
      */
@@ -271,7 +273,9 @@ public final class IRCClient {
         ServerReader(final BufferedReader bufferedReader) {
             this.bufferedReader = bufferedReader;
 
-            new Thread(this).start();
+            Thread serverReader = new Thread(this);
+            serverReader.setName("irc-client-thread");
+            serverReader.start();
         }
 
         @Override
index 50aca81..e540c9f 100644 (file)
@@ -1,26 +1,27 @@
 /*
  * Copyright (c) 2015  Felix Doerre
  * Copyright (c) 2015  Benny Baumann
- * Copyright (c) 2016  Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package org.cacert.votebot.shared.exceptions;
 
+import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
 /**
@@ -33,6 +34,6 @@ public class InvalidChannelName extends IRCClientException {
      * @param channel channel name
      */
     public InvalidChannelName(final String channel) {
-        super(String.format(ResourceBundle.getBundle("messages").getString("invalid_channel_name"), channel));
+        super(MessageFormat.format(ResourceBundle.getBundle("messages").getString("invalid_channel_name"), channel));
     }
 }
index 5dbe434..f995c1b 100644 (file)
@@ -1,26 +1,27 @@
 /*
  * Copyright (c) 2015  Felix Doerre
  * Copyright (c) 2015  Benny Baumann
- * Copyright (c) 2016  Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package org.cacert.votebot.shared.exceptions;
 
+import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
 /**
@@ -33,6 +34,6 @@ public class InvalidNickName extends IRCClientException {
      * @param nickname IRC nick name
      */
     public InvalidNickName(final String nickname) {
-        super(String.format(ResourceBundle.getBundle("messages").getString("invalid_nick_name"), nickname));
+        super(MessageFormat.format(ResourceBundle.getBundle("messages").getString("invalid_nick_name"), nickname));
     }
 }
index 2aa1d07..a3d9bd5 100644 (file)
@@ -1,22 +1,22 @@
 /*
  * Copyright (c) 2015  Felix Doerre
  * Copyright (c) 2015  Benny Baumann
- * Copyright (c) 2016  Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 package org.cacert.votebot.vote;
 
@@ -36,10 +36,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.stereotype.Component;
 
 import java.io.IOException;
+import java.text.MessageFormat;
+import java.time.Duration;
+import java.util.Locale;
+import java.util.ResourceBundle;
 
 
 /**
- * Vote bot.
+ * VoteBot main class.
  *
  * @author Felix Doerre
  * @author Jan Dittberner
@@ -48,37 +52,42 @@ import java.io.IOException;
 @Component
 public class CAcertVoteBot extends IRCBot implements Runnable, CommandLineRunner {
     private static final Logger LOGGER = LoggerFactory.getLogger(CAcertVoteBot.class);
-    private static final int MILLIS_ONE_SECOND = 1000;
+    private static final long MILLIS_ONE_SECOND = Duration.ofSeconds(1).toMillis();
+    private final ResourceBundle messages = ResourceBundle.getBundle("messages");
 
     /**
      * Meeting channel where votes and results are published.
      */
-    @Value("${voteBot.meetingChn}")
+    @Value("${voteBot.meetingChn:meeting}")
     private String meetingChannel;
 
     /**
      * Channel name where voting is performed.
      */
-    @Value("${voteBot.voteChn}")
+    @Value("${voteBot.voteChn:vote}")
     private String voteChannel;
 
     /**
      * Seconds to warn before a vote ends.
      */
-    @Value("${voteBot.warnSecs}")
+    @Value("${voteBot.warnSecs:90}")
     private long warn;
 
     /**
      * Seconds before a vote times out.
      */
-    @Value("${voteBot.timeoutSecs}")
+    @Value("${voteBot.timeoutSecs:120}")
     private long timeout;
 
-    @Autowired
-    private CAcertVoteMechanics voteMechanics;
+    private final CAcertVoteMechanics voteMechanics;
+
+    private final IRCClient ircClient;
 
     @Autowired
-    private IRCClient ircClient;
+    public CAcertVoteBot(CAcertVoteMechanics voteMechanics, IRCClient ircClient) {
+        this.voteMechanics = voteMechanics;
+        this.ircClient = ircClient;
+    }
 
     /**
      * {@inheritDoc}
@@ -114,18 +123,46 @@ public class CAcertVoteBot extends IRCBot implements Runnable, CommandLineRunner
 
     @Override
     public final synchronized void privateMessage(final String from, final String message) throws IRCClientException {
-        if (message.startsWith("vote ")) {
-            final String response = voteMechanics.callVote(message.substring(5));
-            sendPrivateMessage(from, response);
-
-            if (response.startsWith("Sorry,")) {
-                return;
+        if (message != null && message.length() > 0) {
+            String[] parts = message.split("\\s+", 2);
+            try {
+                VoteBotCommand command = VoteBotCommand.valueOf(parts[0].toUpperCase(Locale.ENGLISH));
+                switch (command) {
+                    case VOTE:
+                        startVote(from, parts[1]);
+                        break;
+                    case HELP:
+                        giveHelp(from);
+                        break;
+                }
+            } catch (IllegalArgumentException e) {
+                sendUnknownCommand(from, parts[0]);
             }
+        }
+    }
 
-            announce("New Vote: " + from + " has started a vote on \"" + voteMechanics.getTopic() + "\"");
-            sendPublicMessage(meetingChannel, "Please cast your vote in #vote");
-            sendPublicMessage(voteChannel, "Please cast your vote in the next " + timeout + " seconds.");
+    private void sendUnknownCommand(String from, String command) throws IRCClientException {
+        sendPrivateMessage(from, String.format(messages.getString("unknown_command"), command));
+    }
+
+    private void giveHelp(String from) throws IRCClientException {
+        sendPrivateMessage(from, messages.getString("help_message"));
+    }
+
+    private void startVote(final String from, final String message) throws IRCClientException {
+        final String response = voteMechanics.callVote(message);
+        sendPrivateMessage(from, response);
+
+        if (response.startsWith("Sorry,")) {
+            return;
         }
+
+        announce(MessageFormat.format(messages.getString("new_vote"), from, voteMechanics.getTopic()));
+        sendPublicMessage(
+                meetingChannel,
+                MessageFormat.format(messages.getString("cast_vote_in_vote_channel"), voteChannel));
+        sendPublicMessage(
+                voteChannel, MessageFormat.format(messages.getString("cast_vote_in_next_seconds"), timeout));
     }
 
     private synchronized void announce(final String msg) throws IRCClientException {
diff --git a/src/main/java/org/cacert/votebot/vote/VoteBotCommand.java b/src/main/java/org/cacert/votebot/vote/VoteBotCommand.java
new file mode 100644 (file)
index 0000000..ad9b3ef
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015  Felix Doerre
+ * Copyright (c) 2015  Benny Baumann
+ * Copyright (c) 2016, 2018  Jan Dittberner
+ *
+ * This file is part of CAcert VoteBot.
+ *
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.cacert.votebot.vote;
+
+/**
+ * Enumeration of supported VoteBot commands.
+ *
+ * @since 0.2.0
+ * @author Jan Dittberner
+ */
+public enum VoteBotCommand {
+    VOTE,
+    HELP
+}
index f417a70..c8f1247 100644 (file)
@@ -1,26 +1,30 @@
 #
-# Copyright (c) 2016. Jan Dittberner
+# Copyright (c) 2016, 2018  Jan Dittberner
 #
-# This file is part of CAcert votebot.
+# This file is part of CAcert VoteBot.
 #
-# CAcert votebot is free software: you can redistribute it and/or modify it
+# CAcert VoteBot is free software: you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by the Free
 # Software Foundation, either version 3 of the License, or (at your option)
 # any later version.
 #
-# CAcert votebot is distributed in the hope that it will be useful, but
+# CAcert VoteBot is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 # more details.
 #
 # You should have received a copy of the GNU General Public License along with
-# CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+# CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
 #
-
+# Properties for vote bot
 voteBot.meetingChn=${meetingChn:agm}
 voteBot.voteChn=${voteChn:vote}
 voteBot.warnSecs=${warnSecs:90}
 voteBot.timeoutSecs=${timeoutSecs:120}
-
+# Properties for audit bot
 auditor.target.voteChn=${voteChn:vote}
-auditor.target.nick=${auditor.nick:}
\ No newline at end of file
+auditor.target.nick=${auditor.nick}
+# global properties
+debug=false
+logging.level.root=INFO
+logging.level.org.cacert=DEBUG
\ No newline at end of file
index ff24c5b..8921d4c 100644 (file)
@@ -1,28 +1,39 @@
 #
-# Copyright (c) 2016. Jan Dittberner
+# Copyright (c) 2016, 2018  Jan Dittberner
 #
-# This file is part of CAcert votebot.
+# This file is part of CAcert VoteBot.
 #
-# CAcert votebot is free software: you can redistribute it and/or modify it
+# CAcert VoteBot is free software: you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by the Free
 # Software Foundation, either version 3 of the License, or (at your option)
 # any later version.
 #
-# CAcert votebot is distributed in the hope that it will be useful, but
+# CAcert VoteBot is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 # more details.
 #
 # You should have received a copy of the GNU General Public License along with
-# CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+# CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
 #
 assign_bot_not_called=assignBot() has not been called.
-count_proxy_vote=Thanks %s I count your vote for %s as %s
-count_vote=Thanks %s I count your vote as %s
-invalid_channel_name=%s is not a valid channel name
-invalid_nick_name=%s is not a valid nick name.
-invalid_proxy_vote=Sorry %s, you tried an invalid proxy vote. Please use 'proxy <voter> <vote>'
-no_vote_running=Sorry %s, but currently no vote is running.
-vote_not_understood=Sorry %s, I did not understand your vote, your current vote state remains unchanged!
+count_proxy_vote=Thanks {0} I count your vote for {1} as {2}
+count_vote=Thanks {0} I count your vote as {1}
+invalid_channel_name={0} is not a valid channel name
+invalid_nick_name={0} is not a valid nick name.
+invalid_proxy_vote=Sorry {0}, you tried an invalid proxy vote. Please use 'proxy <voter> <vote>'
+no_vote_running=Sorry {0}, but currently no vote is running.
+vote_not_understood=Sorry {0}, I did not understand your vote, your current vote state remains unchanged!
 vote_running=Sorry, a vote is already running
-vote_started=Vote started.
\ No newline at end of file
+vote_started=Vote started.
+new_vote=New Vote: {0} has started a vote on "{1}"
+cast_vote_in_vote_channel=Please cast your vote in #{0}
+cast_vote_in_next_seconds=Please cast your vote in the next {0} seconds.
+
+help_message=Help for VoteBot\
+  \
+  VoteBot understands the following commands:\
+  \
+  HELP         - this help\
+  VOTE <topic> - start a vote on <topic> if no other vote is running
+unknown_command=I do not understand what you mean with %s
index aee9d84..b41b6d9 100644 (file)
@@ -1,20 +1,20 @@
 /*
- * Copyright (c) 2016. Jan Dittberner
+ * Copyright (c) 2016, 2018. Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package org.cacert.votebot.shared;
@@ -24,6 +24,7 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.text.MessageFormat;
 import java.util.Arrays;
 import java.util.List;
 import java.util.ResourceBundle;
@@ -36,7 +37,7 @@ import static org.cacert.votebot.shared.CAcertVoteMechanics.State.RUNNING;
  */
 public class CAcertVoteMechanicsTest {
     private CAcertVoteMechanics subject;
-    private ResourceBundle resourceBundle;
+    private ResourceBundle messages;
 
     @Test
     public void testStateEnum() {
@@ -49,20 +50,20 @@ public class CAcertVoteMechanicsTest {
 
     @Before
     public void setup() {
-        resourceBundle = ResourceBundle.getBundle("messages");
+        messages = ResourceBundle.getBundle("messages");
         subject = new CAcertVoteMechanics();
     }
 
     @Test
     public void testNoVote() {
         String response = subject.evaluateVote("alice", "test");
-        Assert.assertEquals(String.format(resourceBundle.getString("no_vote_running"), "alice"), response);
+        Assert.assertEquals(MessageFormat.format(messages.getString("no_vote_running"), "alice"), response);
     }
 
     @Test
     public void testCallVote() {
         String response = subject.callVote("test vote");
-        Assert.assertEquals(resourceBundle.getString("vote_started"), response);
+        Assert.assertEquals(messages.getString("vote_started"), response);
         Assert.assertEquals("test vote", subject.getTopic());
         Assert.assertEquals(RUNNING, subject.getState());
     }
@@ -71,7 +72,7 @@ public class CAcertVoteMechanicsTest {
     public void testRefuseParallelCallVote() {
         subject.callVote("first");
         String response = subject.callVote("second");
-        Assert.assertEquals(resourceBundle.getString("vote_running"), response);
+        Assert.assertEquals(messages.getString("vote_running"), response);
         Assert.assertEquals("first", subject.getTopic());
         Assert.assertEquals(RUNNING, subject.getState());
     }
@@ -86,7 +87,8 @@ public class CAcertVoteMechanicsTest {
     public void testVote() {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "aye");
-        Assert.assertEquals(String.format(resourceBundle.getString("count_vote"), "alice", "AYE"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("count_vote"), "alice", "AYE"), response);
         Assert.assertEquals("{alice=AYE}", subject.getCurrentResult());
     }
 
@@ -95,7 +97,8 @@ public class CAcertVoteMechanicsTest {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "proxy bob aye");
         Assert.assertEquals(
-                String.format(resourceBundle.getString("count_proxy_vote"), "alice", "bob", "AYE"), response);
+                MessageFormat.format(messages.getString("count_proxy_vote"), "alice", "bob", "AYE"),
+                response);
         Assert.assertEquals("{bob=AYE}", subject.getCurrentResult());
     }
 
@@ -103,7 +106,8 @@ public class CAcertVoteMechanicsTest {
     public void testInvalidVote() {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "moo");
-        Assert.assertEquals(String.format(resourceBundle.getString("vote_not_understood"), "alice"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("vote_not_understood"), "alice"), response);
         Assert.assertEquals("{}", subject.getCurrentResult());
     }
 
@@ -111,10 +115,12 @@ public class CAcertVoteMechanicsTest {
     public void testChangeVote() {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "aye");
-        Assert.assertEquals(String.format(resourceBundle.getString("count_vote"), "alice", "AYE"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("count_vote"), "alice", "AYE"), response);
         Assert.assertEquals("{alice=AYE}", subject.getCurrentResult());
         response = subject.evaluateVote("alice", "naye");
-        Assert.assertEquals(String.format(resourceBundle.getString("count_vote"), "alice", "NAYE"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("count_vote"), "alice", "NAYE"), response);
         Assert.assertEquals("{alice=NAYE}", subject.getCurrentResult());
     }
 
@@ -122,10 +128,12 @@ public class CAcertVoteMechanicsTest {
     public void testNoChangeForInvalidVote() {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "aye");
-        Assert.assertEquals(String.format(resourceBundle.getString("count_vote"), "alice", "AYE"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("count_vote"), "alice", "AYE"), response);
         Assert.assertEquals("{alice=AYE}", subject.getCurrentResult());
         response = subject.evaluateVote("alice", "moo");
-        Assert.assertEquals(String.format(resourceBundle.getString("vote_not_understood"), "alice"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("vote_not_understood"), "alice"), response);
         Assert.assertEquals("{alice=AYE}", subject.getCurrentResult());
     }
 
@@ -133,7 +141,8 @@ public class CAcertVoteMechanicsTest {
     public void testInvalidProxyVote() {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "proxy bob moo");
-        Assert.assertEquals(String.format(resourceBundle.getString("vote_not_understood"), "alice"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("vote_not_understood"), "alice"), response);
         Assert.assertEquals("{}", subject.getCurrentResult());
     }
 
@@ -141,7 +150,8 @@ public class CAcertVoteMechanicsTest {
     public void testInvalidProxyVoteTokenCount() {
         subject.callVote("test");
         String response = subject.evaluateVote("alice", "proxy ");
-        Assert.assertEquals(String.format(resourceBundle.getString("invalid_proxy_vote"), "alice"), response);
+        Assert.assertEquals(
+                MessageFormat.format(messages.getString("invalid_proxy_vote"), "alice"), response);
         Assert.assertEquals("{}", subject.getCurrentResult());
     }
 
diff --git a/src/test/java/org/cacert/votebot/shared/IRCClientTest.java b/src/test/java/org/cacert/votebot/shared/IRCClientTest.java
new file mode 100644 (file)
index 0000000..1a55068
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2016, 2018  Jan Dittberner
+ *
+ * This file is part of CAcert VoteBot.
+ *
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.cacert.votebot.shared;
+
+import org.cacert.votebot.shared.exceptions.IRCClientException;
+import org.junit.*;
+import org.junit.runners.MethodSorters;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.SocketUtils;
+
+import javax.net.ServerSocketFactory;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class IRCClientTest {
+    private static IRCRequestHandler handler;
+    private static IRCClient client;
+    private static Thread serverThread;
+    private static PrintWriter mockMe;
+
+    interface IRCRequestHandler {
+        void handle();
+
+        void sendCommand(String command);
+
+        void setSocket(Socket socket) throws IOException;
+    }
+
+    static class MockIRCRequestHandler implements IRCRequestHandler {
+        private BufferedReader reader;
+        private PrintWriter writer;
+        private Socket socket;
+        private final PrintWriter testWriter;
+
+        MockIRCRequestHandler(PrintWriter testWriter) {
+            this.testWriter = testWriter;
+        }
+
+
+        @Override
+        public void handle() {
+            try {
+
+                String nick = null;
+                Map<String, Set<String>> channels = new HashMap<>();
+
+                for (; ; ) {
+                    String inputLine = reader.readLine();
+                    this.testWriter.println(inputLine);
+                    String[] parts = inputLine.split(" ");
+                    switch (parts[0].toUpperCase()) {
+                        case "NICK":
+                            nick = parts[1];
+                            break;
+                        case "USER":
+                            if (nick != null) {
+                                writer.println("Hello " + nick);
+                            }
+                            break;
+                        case "QUIT":
+                            socket.close();
+                            return;
+                        case "JOIN":
+                            String channel = parts[1];
+                            if (!channels.containsKey(channel)) {
+                                channels.put(channel, new HashSet<>());
+                            }
+                            channels.get(channel).add(nick);
+                            break;
+                        default:
+                            writer.println("001 Unknown command");
+                    }
+                }
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void sendCommand(String command) {
+            this.writer.println(command);
+        }
+
+        @Override
+        public void setSocket(Socket socket) throws IOException {
+            this.socket = socket;
+            this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+            this.writer = new PrintWriter(socket.getOutputStream(), true);
+        }
+    }
+
+    static class MockIrcServer implements Runnable {
+        private final IRCRequestHandler handler;
+        private ServerSocket serverSocket;
+        private final Logger log = LoggerFactory.getLogger(MockIrcServer.class);
+
+        MockIrcServer(IRCRequestHandler handler) throws IOException {
+            this.serverSocket = ServerSocketFactory.getDefault().createServerSocket(SocketUtils.findAvailableTcpPort());
+            this.handler = handler;
+        }
+
+        int getServerPort() {
+            return serverSocket.getLocalPort();
+        }
+
+        @Override
+        public void run() {
+            new Thread(() -> {
+                if (serverSocket != null) {
+                    try {
+                        Socket socket = serverSocket.accept();
+                        handler.setSocket(socket);
+                        handler.handle();
+                    } catch (Exception e) {
+                        log.error("Exception while handling socket I/O", e);
+                    }
+                }
+            }).start();
+        }
+    }
+
+    @BeforeClass
+    public static void initializeClient() throws Exception {
+        mockMe = Mockito.mock(PrintWriter.class);
+        handler = new MockIRCRequestHandler(mockMe);
+        MockIrcServer server = new MockIrcServer(handler);
+        serverThread = new Thread(server);
+        serverThread.setName("mock-server");
+        serverThread.run();
+        client = new IRCClient();
+        String testPort = Integer.toString(server.getServerPort());
+        client.initializeFromArgs("-h", "localhost", "-p", testPort, "-n", "testbot", "--no-ssl");
+        client.assignBot(Mockito.mock(IRCBot.class));
+        verify(mockMe, after(100)).println("NICK testbot");
+        verify(mockMe, after(100)).println(ArgumentMatchers.startsWith("USER testbot"));
+    }
+
+    @AfterClass
+    public static void shutdownClient() {
+        client.quit();
+        serverThread.interrupt();
+    }
+
+    @Before
+    public void clearInvocations() {
+        Mockito.clearInvocations(mockMe);
+    }
+
+    @Test
+    public void testPingHandler() {
+        handler.sendCommand("PING me");
+        verify(mockMe, after(100)).println("PONG me");
+    }
+
+    @Test
+    public void testPrivMessageHandler() {
+        handler.sendCommand("PRIVMSG #meeting Hello");
+    }
+
+    @Test
+    public void testJoinLeave() throws IRCClientException {
+        client.join("test");
+        verify(mockMe, after(100)).println("JOIN #test");
+        client.leave("test");
+        verify(mockMe, after(100)).println("PART #test");
+    }
+
+    @Test
+    public void testJoinLeaveAll() throws IRCClientException {
+        client.join("vote");
+        client.join("meeting");
+        client.leaveAll();
+
+        verify(mockMe, after(100)).println("JOIN #vote");
+        verify(mockMe).println("JOIN #meeting");
+        verify(mockMe).println("PART #vote");
+        verify(mockMe).println("PART #meeting");
+        verifyNoMoreInteractions(mockMe);
+    }
+
+    @Test
+    public void testSend() throws Exception {
+        client.join("meeting");
+        client.send("Test message", "meeting");
+        client.leaveAll();
+
+        verify(mockMe, after(100)).println("JOIN #meeting");
+        verify(mockMe).println("PRIVMSG #meeting :Test message");
+        verify(mockMe).println("PART #meeting");
+        verifyNoMoreInteractions(mockMe);
+    }
+
+    @Test
+    public void testSendMultiline() throws Exception {
+        client.join("meeting");
+        client.send("Test message\nMeeting in 10 minutes", "meeting");
+        client.leaveAll();
+
+        verify(mockMe, after(100)).println("JOIN #meeting");
+        verify(mockMe).println("PRIVMSG #meeting :Test message");
+        verify(mockMe).println("PRIVMSG #meeting :Meeting in 10 minutes");
+        verify(mockMe).println("PART #meeting");
+        verifyNoMoreInteractions(mockMe);
+    }
+
+    @Test
+    public void testSendPrivateMessage() throws Exception {
+        client.sendPrivate("Test message", "otherguy");
+        verify(mockMe, after(100)).println("PRIVMSG otherguy :Test message");
+        verifyNoMoreInteractions(mockMe);
+    }
+
+    @Test
+    public void testSendPrivateMessageMultiline() throws Exception {
+        client.sendPrivate("Test message\nMeeting in 10 minutes", "otherguy");
+        verify(mockMe, after(100)).println("PRIVMSG otherguy :Test message");
+        verify(mockMe, after(100)).println("PRIVMSG otherguy :Meeting in 10 minutes");
+        verifyNoMoreInteractions(mockMe);
+    }
+
+    @Test
+    public void testFailPrivateMessageToSelf() throws Exception {
+        try {
+            client.sendPrivate("Test message", "test/nick");
+            fail("Expected IRC client exception for private message to invalid nick not thrown.");
+        } catch (IRCClientException e) {
+            assertThat(e.getMessage(), containsString("test/nick"));
+        }
+        verifyZeroInteractions(mockMe);
+    }
+}
index ca91f8f..9aabf0e 100644 (file)
@@ -1,29 +1,31 @@
 /*
- * Copyright (c) 2016. Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package org.cacert.votebot.shared.exceptions;
 
-import org.junit.Assert;
 import org.junit.Test;
 
+import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
+import static org.junit.Assert.assertEquals;
+
 /**
  * @author Jan Dittberner
  */
@@ -31,8 +33,10 @@ public class InvalidChannelNameTest {
     @Test
     public void testConstructor() {
         final InvalidChannelName e = new InvalidChannelName("test");
-        Assert.assertEquals(
-                String.format(ResourceBundle.getBundle("messages").getString("invalid_channel_name"), "test"),
-                e.getMessage());
+        assertEquals(
+                e.getMessage(),
+                MessageFormat.format(
+                        ResourceBundle.getBundle("messages").getString("invalid_channel_name"),
+                        "test"));
     }
 }
\ No newline at end of file
index c23485e..d1b8e1e 100644 (file)
@@ -1,29 +1,31 @@
 /*
- * Copyright (c) 2016. Jan Dittberner
+ * Copyright (c) 2016, 2018  Jan Dittberner
  *
- * This file is part of CAcert votebot.
+ * This file is part of CAcert VoteBot.
  *
- * CAcert votebot is free software: you can redistribute it and/or modify it
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the Free
  * Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
  *
- * CAcert votebot is distributed in the hope that it will be useful, but
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
  * You should have received a copy of the GNU General Public License along with
- * CAcert votebot.  If not, see <http://www.gnu.org/licenses/>.
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package org.cacert.votebot.shared.exceptions;
 
-import org.junit.Assert;
 import org.junit.Test;
 
+import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
+import static org.junit.Assert.assertEquals;
+
 /**
  * @author Jan Dittberner
  */
@@ -31,10 +33,10 @@ public class InvalidNickNameTest {
     @Test
     public void testConstructor() {
         final InvalidNickName e = new InvalidNickName("test");
-        Assert.assertEquals(
-                String.format(ResourceBundle.getBundle("messages").getString("invalid_nick_name"), "test"),
-                e.getMessage()
-        );
+        assertEquals(
+                e.getMessage(),
+                MessageFormat.format(
+                        ResourceBundle.getBundle("messages").getString("invalid_nick_name"),
+                        "test"));
     }
-
 }
\ No newline at end of file
diff --git a/src/test/java/org/cacert/votebot/vote/CAcertVoteBotTest.java b/src/test/java/org/cacert/votebot/vote/CAcertVoteBotTest.java
new file mode 100644 (file)
index 0000000..1690c0f
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2016, 2018  Jan Dittberner
+ *
+ * This file is part of CAcert VoteBot.
+ *
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.cacert.votebot.vote;
+
+import org.cacert.votebot.shared.CAcertVoteMechanics;
+import org.cacert.votebot.shared.IRCClient;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+import static org.mockito.Mockito.*;
+
+@SuppressWarnings("SpringJavaAutowiredMembersInspection")
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {TestConfiguration.class})
+public class CAcertVoteBotTest {
+
+    private static final String TEST_VOTE_TOPIC = "Is it really a vote or just a fake election?";
+
+    @Autowired
+    private IRCClient ircClient;
+    @Autowired
+    private CAcertVoteMechanics mechanics;
+
+    private ResourceBundle messages;
+
+    private CAcertVoteBot bot;
+
+    @Before
+    public void setupTest() {
+        messages = ResourceBundle.getBundle("messages");
+        bot = new CAcertVoteBot(mechanics, ircClient);
+        ReflectionTestUtils.setField(bot, "meetingChannel", "meeting");
+        ReflectionTestUtils.setField(bot, "voteChannel", "vote");
+        ReflectionTestUtils.setField(bot, "timeout", 120);
+    }
+
+    @Test
+    public void testStartVoteBot() throws Exception {
+        when(mechanics.callVote(TEST_VOTE_TOPIC)).thenReturn(messages.getString("vote_started"));
+        when(mechanics.getTopic()).thenReturn(TEST_VOTE_TOPIC);
+        bot.privateMessage("test", String.format("vote %s", TEST_VOTE_TOPIC));
+        verify(ircClient).send(
+                MessageFormat.format(messages.getString("new_vote"), "test", TEST_VOTE_TOPIC),
+                "meeting");
+        verify(ircClient).send(
+                MessageFormat.format(messages.getString("new_vote"), "test", TEST_VOTE_TOPIC),
+                "vote");
+        verify(ircClient).send(
+                MessageFormat.format(messages.getString("cast_vote_in_vote_channel"), "vote"),
+                "meeting");
+        verify(ircClient).send(
+                MessageFormat.format(messages.getString("cast_vote_in_next_seconds"), 120),
+                "vote");
+    }
+
+    @Test
+    public void testHelp() throws Exception {
+        bot.privateMessage("test", "help");
+        verify(ircClient).sendPrivate(messages.getString("help_message"), "test");
+        verifyNoMoreInteractions(ircClient);
+    }
+}
diff --git a/src/test/java/org/cacert/votebot/vote/TestConfiguration.java b/src/test/java/org/cacert/votebot/vote/TestConfiguration.java
new file mode 100644 (file)
index 0000000..71aad97
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2016, 2018  Jan Dittberner
+ *
+ * This file is part of CAcert VoteBot.
+ *
+ * CAcert VoteBot is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * CAcert VoteBot is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * CAcert VoteBot.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.cacert.votebot.vote;
+
+import org.cacert.votebot.shared.CAcertVoteMechanics;
+import org.cacert.votebot.shared.IRCClient;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+@Configuration
+public class TestConfiguration {
+    @Bean
+    @Primary
+    public CAcertVoteMechanics getTestVoteMechanics() {
+        return Mockito.mock(CAcertVoteMechanics.class);
+    }
+
+    @Bean
+    @Primary
+    public IRCClient getTestIrcClient() {
+        return Mockito.mock(IRCClient.class);
+    }
+}