Implement help command, add more tests, improve messages
[cacert-votebot.git] / src / test / java / org / cacert / votebot / shared / IRCClientTest.java
1 /*
2 * Copyright (c) 2016, 2018 Jan Dittberner
3 *
4 * This file is part of CAcert VoteBot.
5 *
6 * CAcert VoteBot is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation, either version 3 of the License, or (at your option)
9 * any later version.
10 *
11 * CAcert VoteBot is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * CAcert VoteBot. If not, see <http://www.gnu.org/licenses/>.
18 */
19 package org.cacert.votebot.shared;
20
21 import org.cacert.votebot.shared.exceptions.IRCClientException;
22 import org.junit.*;
23 import org.junit.runners.MethodSorters;
24 import org.mockito.ArgumentMatchers;
25 import org.mockito.Mockito;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.springframework.util.SocketUtils;
29
30 import javax.net.ServerSocketFactory;
31 import java.io.BufferedReader;
32 import java.io.IOException;
33 import java.io.InputStreamReader;
34 import java.io.PrintWriter;
35 import java.net.ServerSocket;
36 import java.net.Socket;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.Map;
40 import java.util.Set;
41
42 import static org.hamcrest.Matchers.containsString;
43 import static org.junit.Assert.*;
44 import static org.mockito.Mockito.*;
45
46
47 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
48 public class IRCClientTest {
49 private static IRCRequestHandler handler;
50 private static IRCClient client;
51 private static Thread serverThread;
52 private static PrintWriter mockMe;
53
54 interface IRCRequestHandler {
55 void handle();
56
57 void sendCommand(String command);
58
59 void setSocket(Socket socket) throws IOException;
60 }
61
62 static class MockIRCRequestHandler implements IRCRequestHandler {
63 private BufferedReader reader;
64 private PrintWriter writer;
65 private Socket socket;
66 private final PrintWriter testWriter;
67
68 MockIRCRequestHandler(PrintWriter testWriter) {
69 this.testWriter = testWriter;
70 }
71
72
73 @Override
74 public void handle() {
75 try {
76
77 String nick = null;
78 Map<String, Set<String>> channels = new HashMap<>();
79
80 for (; ; ) {
81 String inputLine = reader.readLine();
82 this.testWriter.println(inputLine);
83 String[] parts = inputLine.split(" ");
84 switch (parts[0].toUpperCase()) {
85 case "NICK":
86 nick = parts[1];
87 break;
88 case "USER":
89 if (nick != null) {
90 writer.println("Hello " + nick);
91 }
92 break;
93 case "QUIT":
94 socket.close();
95 return;
96 case "JOIN":
97 String channel = parts[1];
98 if (!channels.containsKey(channel)) {
99 channels.put(channel, new HashSet<>());
100 }
101 channels.get(channel).add(nick);
102 break;
103 default:
104 writer.println("001 Unknown command");
105 }
106 }
107 } catch (Exception e) {
108 throw new RuntimeException(e);
109 }
110 }
111
112 @Override
113 public void sendCommand(String command) {
114 this.writer.println(command);
115 }
116
117 @Override
118 public void setSocket(Socket socket) throws IOException {
119 this.socket = socket;
120 this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
121 this.writer = new PrintWriter(socket.getOutputStream(), true);
122 }
123 }
124
125 static class MockIrcServer implements Runnable {
126 private final IRCRequestHandler handler;
127 private ServerSocket serverSocket;
128 private final Logger log = LoggerFactory.getLogger(MockIrcServer.class);
129
130 MockIrcServer(IRCRequestHandler handler) throws IOException {
131 this.serverSocket = ServerSocketFactory.getDefault().createServerSocket(SocketUtils.findAvailableTcpPort());
132 this.handler = handler;
133 }
134
135 int getServerPort() {
136 return serverSocket.getLocalPort();
137 }
138
139 @Override
140 public void run() {
141 new Thread(() -> {
142 if (serverSocket != null) {
143 try {
144 Socket socket = serverSocket.accept();
145 handler.setSocket(socket);
146 handler.handle();
147 } catch (Exception e) {
148 log.error("Exception while handling socket I/O", e);
149 }
150 }
151 }).start();
152 }
153 }
154
155 @BeforeClass
156 public static void initializeClient() throws Exception {
157 mockMe = Mockito.mock(PrintWriter.class);
158 handler = new MockIRCRequestHandler(mockMe);
159 MockIrcServer server = new MockIrcServer(handler);
160 serverThread = new Thread(server);
161 serverThread.setName("mock-server");
162 serverThread.run();
163 client = new IRCClient();
164 String testPort = Integer.toString(server.getServerPort());
165 client.initializeFromArgs("-h", "localhost", "-p", testPort, "-n", "testbot", "--no-ssl");
166 client.assignBot(Mockito.mock(IRCBot.class));
167 verify(mockMe, after(100)).println("NICK testbot");
168 verify(mockMe, after(100)).println(ArgumentMatchers.startsWith("USER testbot"));
169 }
170
171 @AfterClass
172 public static void shutdownClient() {
173 client.quit();
174 serverThread.interrupt();
175 }
176
177 @Before
178 public void clearInvocations() {
179 Mockito.clearInvocations(mockMe);
180 }
181
182 @Test
183 public void testPingHandler() {
184 handler.sendCommand("PING me");
185 verify(mockMe, after(100)).println("PONG me");
186 }
187
188 @Test
189 public void testPrivMessageHandler() {
190 handler.sendCommand("PRIVMSG #meeting Hello");
191 }
192
193 @Test
194 public void testJoinLeave() throws IRCClientException {
195 client.join("test");
196 verify(mockMe, after(100)).println("JOIN #test");
197 client.leave("test");
198 verify(mockMe, after(100)).println("PART #test");
199 }
200
201 @Test
202 public void testJoinLeaveAll() throws IRCClientException {
203 client.join("vote");
204 client.join("meeting");
205 client.leaveAll();
206
207 verify(mockMe, after(100)).println("JOIN #vote");
208 verify(mockMe).println("JOIN #meeting");
209 verify(mockMe).println("PART #vote");
210 verify(mockMe).println("PART #meeting");
211 verifyNoMoreInteractions(mockMe);
212 }
213
214 @Test
215 public void testSend() throws Exception {
216 client.join("meeting");
217 client.send("Test message", "meeting");
218 client.leaveAll();
219
220 verify(mockMe, after(100)).println("JOIN #meeting");
221 verify(mockMe).println("PRIVMSG #meeting :Test message");
222 verify(mockMe).println("PART #meeting");
223 verifyNoMoreInteractions(mockMe);
224 }
225
226 @Test
227 public void testSendMultiline() throws Exception {
228 client.join("meeting");
229 client.send("Test message\nMeeting in 10 minutes", "meeting");
230 client.leaveAll();
231
232 verify(mockMe, after(100)).println("JOIN #meeting");
233 verify(mockMe).println("PRIVMSG #meeting :Test message");
234 verify(mockMe).println("PRIVMSG #meeting :Meeting in 10 minutes");
235 verify(mockMe).println("PART #meeting");
236 verifyNoMoreInteractions(mockMe);
237 }
238
239 @Test
240 public void testSendPrivateMessage() throws Exception {
241 client.sendPrivate("Test message", "otherguy");
242 verify(mockMe, after(100)).println("PRIVMSG otherguy :Test message");
243 verifyNoMoreInteractions(mockMe);
244 }
245
246 @Test
247 public void testSendPrivateMessageMultiline() throws Exception {
248 client.sendPrivate("Test message\nMeeting in 10 minutes", "otherguy");
249 verify(mockMe, after(100)).println("PRIVMSG otherguy :Test message");
250 verify(mockMe, after(100)).println("PRIVMSG otherguy :Meeting in 10 minutes");
251 verifyNoMoreInteractions(mockMe);
252 }
253
254 @Test
255 public void testFailPrivateMessageToSelf() throws Exception {
256 try {
257 client.sendPrivate("Test message", "test/nick");
258 fail("Expected IRC client exception for private message to invalid nick not thrown.");
259 } catch (IRCClientException e) {
260 assertThat(e.getMessage(), containsString("test/nick"));
261 }
262 verifyZeroInteractions(mockMe);
263 }
264 }