|
1
|
|
|
#! /usr/bin/env python3 |
|
2
|
|
|
# -*- coding: utf-8 -*- |
|
3
|
|
|
|
|
4
|
|
|
""" |
|
5
|
|
|
Tests for the main launcher |
|
6
|
|
|
""" |
|
7
|
|
|
|
|
8
|
|
|
import argparse |
|
9
|
|
|
import contextlib |
|
10
|
|
|
import io |
|
11
|
|
|
import os |
|
12
|
|
|
import sys |
|
13
|
|
|
from unittest import TestCase |
|
14
|
|
|
|
|
15
|
|
|
from irc2phpbb.__main__ import mergeOptionsWithConfigFile, parseOptions, determineProtocol, MSG_VERSION, createBot |
|
16
|
|
|
from irc2phpbb.irc_bot import IrcBot |
|
17
|
|
|
from irc2phpbb.discord_bot import DiscordBot |
|
18
|
|
|
|
|
19
|
|
|
|
|
20
|
|
|
class ConfigMergeTest(TestCase): |
|
21
|
|
|
"""Test merging a config file with a dict""" |
|
22
|
|
|
def assertMergedConfig(self, config, fileName, expected): |
|
23
|
|
|
"""Merge dict with file and assert the result matches expected""" |
|
24
|
|
|
configFile = os.path.join(os.path.dirname(__file__), "resources", "config", f"{fileName}.json") |
|
25
|
|
|
actualConfig = mergeOptionsWithConfigFile(config, configFile) |
|
26
|
|
|
self.assertEqual(actualConfig, expected) |
|
27
|
|
|
|
|
28
|
|
|
|
|
29
|
|
|
def testEmpty(self): |
|
30
|
|
|
"""Empty into empty should equal empty""" |
|
31
|
|
|
self.assertMergedConfig({}, "empty", {}) |
|
32
|
|
|
|
|
33
|
|
|
def testAddSingleParameter(self): |
|
34
|
|
|
"""Add a single parameter to an empty config""" |
|
35
|
|
|
new = { |
|
36
|
|
|
"single": "test" |
|
37
|
|
|
} |
|
38
|
|
|
expected = { |
|
39
|
|
|
"single": "test" |
|
40
|
|
|
} |
|
41
|
|
|
self.assertMergedConfig(new, "empty", expected) |
|
42
|
|
|
|
|
43
|
|
|
def testAddSingleParameterOverwrites(self): |
|
44
|
|
|
"""Add a single parameter to a config that contains it already""" |
|
45
|
|
|
new = { |
|
46
|
|
|
"single": "test" |
|
47
|
|
|
} |
|
48
|
|
|
expected = { |
|
49
|
|
|
"single": "original" |
|
50
|
|
|
} |
|
51
|
|
|
self.assertMergedConfig(new, "single", expected) |
|
52
|
|
|
|
|
53
|
|
|
def testAddSingleParameterMerges(self): |
|
54
|
|
|
"""Add a single parameter to a config that contains a different one""" |
|
55
|
|
|
new = { |
|
56
|
|
|
"new": "test" |
|
57
|
|
|
} |
|
58
|
|
|
expected = { |
|
59
|
|
|
"new" : "test", |
|
60
|
|
|
"single" : "original" |
|
61
|
|
|
} |
|
62
|
|
|
self.assertMergedConfig(new, "single", expected) |
|
63
|
|
|
|
|
64
|
|
|
class ConfigParseTest(TestCase): |
|
65
|
|
|
"""Test parsing options into a config""" |
|
66
|
|
|
|
|
67
|
|
|
TEST_CONFIG_DIR = os.path.join(os.path.dirname(__file__), "resources", "config") |
|
68
|
|
|
|
|
69
|
|
|
SAMPLE_CONFIG = { |
|
70
|
|
|
"server": "localhost", |
|
71
|
|
|
"port": 6667, |
|
72
|
|
|
"channel": "#dbwebb", |
|
73
|
|
|
"nick": "marvin", |
|
74
|
|
|
"realname": "Marvin The All Mighty dbwebb-bot", |
|
75
|
|
|
"ident": "password" |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
CHANGED_CONFIG = { |
|
79
|
|
|
"server": "remotehost", |
|
80
|
|
|
"port": 1234, |
|
81
|
|
|
"channel": "#db-o-webb", |
|
82
|
|
|
"nick": "imposter", |
|
83
|
|
|
"realname": "where is marvin?", |
|
84
|
|
|
"ident": "identify" |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
def testOverrideHardcodedParameters(self): |
|
88
|
|
|
"""Test that all the hard coded parameters can be overridden from commandline""" |
|
89
|
|
|
for parameter in ["server", "port", "channel", "nick", "realname", "ident"]: |
|
90
|
|
|
sys.argv = ["./main.py", f"--{parameter}", str(self.CHANGED_CONFIG.get(parameter))] |
|
91
|
|
|
actual = parseOptions(self.SAMPLE_CONFIG) |
|
92
|
|
|
self.assertEqual(actual.get(parameter), self.CHANGED_CONFIG.get(parameter)) |
|
93
|
|
|
|
|
94
|
|
|
def testOverrideMultipleParameters(self): |
|
95
|
|
|
"""Test that multiple parameters can be overridden from commandline""" |
|
96
|
|
|
sys.argv = ["./main.py", "--server", "dbwebb.se", "--port", "5432"] |
|
97
|
|
|
actual = parseOptions(self.SAMPLE_CONFIG) |
|
98
|
|
|
self.assertEqual(actual.get("server"), "dbwebb.se") |
|
99
|
|
|
self.assertEqual(actual.get("port"), 5432) |
|
100
|
|
|
|
|
101
|
|
|
def testOverrideWithFile(self): |
|
102
|
|
|
"""Test that parameters can be overridden with the --config option""" |
|
103
|
|
|
configFile = os.path.join(self.TEST_CONFIG_DIR, "server.json") |
|
104
|
|
|
sys.argv = ["./main.py", "--config", configFile] |
|
105
|
|
|
actual = parseOptions(self.SAMPLE_CONFIG) |
|
106
|
|
|
self.assertEqual(actual.get("server"), "irc.dbwebb.se") |
|
107
|
|
|
|
|
108
|
|
|
def testOverridePrecedenceConfigFirst(self): |
|
109
|
|
|
"""Test that proper precedence is considered. From most to least significant it should be: |
|
110
|
|
|
explicit parameter -> parameter in --config file -> default """ |
|
111
|
|
|
|
|
112
|
|
|
configFile = os.path.join(self.TEST_CONFIG_DIR, "server.json") |
|
113
|
|
|
sys.argv = ["./main.py", "--config", configFile, "--server", "important.com"] |
|
114
|
|
|
actual = parseOptions(self.SAMPLE_CONFIG) |
|
115
|
|
|
self.assertEqual(actual.get("server"), "important.com") |
|
116
|
|
|
|
|
117
|
|
|
def testOverridePrecedenceParameterFirst(self): |
|
118
|
|
|
"""Test that proper precedence is considered. From most to least significant it should be: |
|
119
|
|
|
explicit parameter -> parameter in --config file -> default """ |
|
120
|
|
|
|
|
121
|
|
|
configFile = os.path.join(self.TEST_CONFIG_DIR, "server.json") |
|
122
|
|
|
sys.argv = ["./main.py", "--server", "important.com", "--config", configFile] |
|
123
|
|
|
actual = parseOptions(self.SAMPLE_CONFIG) |
|
124
|
|
|
self.assertEqual(actual.get("server"), "important.com") |
|
125
|
|
|
|
|
126
|
|
|
def testBannedParameters(self): |
|
127
|
|
|
"""Don't allow config, help and version as parameters, as those options are special""" |
|
128
|
|
|
for bannedParameter in ["config", "help", "version"]: |
|
129
|
|
|
with self.assertRaises(argparse.ArgumentError): |
|
130
|
|
|
parseOptions({bannedParameter: "test"}) |
|
131
|
|
|
|
|
132
|
|
|
|
|
133
|
|
|
class FormattingTest(TestCase): |
|
134
|
|
|
"""Test the parameters that cause printouts""" |
|
135
|
|
|
|
|
136
|
|
|
USAGE = ("usage: main.py [-h] [-v] [--config CONFIG] [--server SERVER] [--port PORT] " |
|
137
|
|
|
"[--channel CHANNEL] [--nick NICK] [--realname REALNAME] [--ident IDENT]\n" |
|
138
|
|
|
" [{irc,discord}]\n") |
|
139
|
|
|
|
|
140
|
|
|
OPTIONS = ("positional arguments:\n {irc,discord}\n\n" |
|
141
|
|
|
"options:\n" |
|
142
|
|
|
" -h, --help show this help message and exit\n" |
|
143
|
|
|
" -v, --version\n" |
|
144
|
|
|
" --config CONFIG\n" |
|
145
|
|
|
" --server SERVER\n" |
|
146
|
|
|
" --port PORT\n" |
|
147
|
|
|
" --channel CHANNEL\n" |
|
148
|
|
|
" --nick NICK\n" |
|
149
|
|
|
" --realname REALNAME\n" |
|
150
|
|
|
" --ident IDENT") |
|
151
|
|
|
|
|
152
|
|
|
|
|
153
|
|
|
@classmethod |
|
154
|
|
|
def setUpClass(cls): |
|
155
|
|
|
"""Set the terminal width to 160 to prevent the tests from failing on small terminals""" |
|
156
|
|
|
os.environ["COLUMNS"] = "160" |
|
157
|
|
|
|
|
158
|
|
|
|
|
159
|
|
|
def assertPrintOption(self, options, returnCode, output): |
|
160
|
|
|
"""Assert that parseOptions returns a certain code and prints a certain output""" |
|
161
|
|
|
with self.assertRaises(SystemExit) as e: |
|
162
|
|
|
s = io.StringIO() |
|
163
|
|
|
with contextlib.redirect_stdout(s): |
|
164
|
|
|
sys.argv = ["./main.py"] + [options] |
|
165
|
|
|
parseOptions(ConfigParseTest.SAMPLE_CONFIG) |
|
166
|
|
|
self.assertEqual(e.exception.code, returnCode) |
|
167
|
|
|
self.assertEqual(s.getvalue(), output+"\n") # extra newline added by print() |
|
168
|
|
|
|
|
169
|
|
|
|
|
170
|
|
|
def testHelpPrintout(self): |
|
171
|
|
|
"""Test that a help is printed when providing the --help flag""" |
|
172
|
|
|
self.assertPrintOption("--help", 0, f"{self.USAGE}\n{self.OPTIONS}") |
|
173
|
|
|
|
|
174
|
|
|
def testHelpPrintoutShort(self): |
|
175
|
|
|
"""Test that a help is printed when providing the -h flag""" |
|
176
|
|
|
self.assertPrintOption("-h", 0, f"{self.USAGE}\n{self.OPTIONS}") |
|
177
|
|
|
|
|
178
|
|
|
def testVersionPrintout(self): |
|
179
|
|
|
"""Test that the version is printed when provided the --version flag""" |
|
180
|
|
|
self.assertPrintOption("--version", 0, MSG_VERSION) |
|
181
|
|
|
|
|
182
|
|
|
def testVersionPrintoutShort(self): |
|
183
|
|
|
"""Test that the version is printed when provided the -v flag""" |
|
184
|
|
|
self.assertPrintOption("-v", 0, MSG_VERSION) |
|
185
|
|
|
|
|
186
|
|
|
def testUnhandledOption(self): |
|
187
|
|
|
"""Test that unknown options gives an error""" |
|
188
|
|
|
with self.assertRaises(SystemExit) as e: |
|
189
|
|
|
s = io.StringIO() |
|
190
|
|
|
expectedError = f"{self.USAGE}main.py: error: unrecognized arguments: -g\n" |
|
191
|
|
|
with contextlib.redirect_stderr(s): |
|
192
|
|
|
sys.argv = ["./main.py", "-g"] |
|
193
|
|
|
parseOptions(ConfigParseTest.SAMPLE_CONFIG) |
|
194
|
|
|
self.assertEqual(e.exception.code, 2) |
|
195
|
|
|
self.assertEqual(s.getvalue(), expectedError) |
|
196
|
|
|
|
|
197
|
|
|
def testUnhandledArgument(self): |
|
198
|
|
|
"""Test that any argument gives an error""" |
|
199
|
|
|
with self.assertRaises(SystemExit) as e: |
|
200
|
|
|
s = io.StringIO() |
|
201
|
|
|
expectedError = (f"{self.USAGE}main.py: error: argument protocol: " |
|
202
|
|
|
"invalid choice: 'arg' (choose from 'irc', 'discord')\n") |
|
203
|
|
|
with contextlib.redirect_stderr(s): |
|
204
|
|
|
sys.argv = ["./main.py", "arg"] |
|
205
|
|
|
parseOptions(ConfigParseTest.SAMPLE_CONFIG) |
|
206
|
|
|
self.assertEqual(e.exception.code, 2) |
|
207
|
|
|
self.assertEqual(s.getvalue(), expectedError) |
|
208
|
|
|
|
|
209
|
|
|
class TestArgumentParsing(TestCase): |
|
210
|
|
|
"""Test parsing argument to determine whether to launch as irc or discord bot """ |
|
211
|
|
|
def testDetermineDiscordProtocol(self): |
|
212
|
|
|
"""Test that the it's possible to give argument to start the bot as a discord bot""" |
|
213
|
|
|
sys.argv = ["main.py", "discord"] |
|
214
|
|
|
protocol = determineProtocol() |
|
215
|
|
|
self.assertEqual(protocol, "discord") |
|
216
|
|
|
|
|
217
|
|
|
def testDetermineIRCProtocol(self): |
|
218
|
|
|
"""Test that the it's possible to give argument to start the bot as an irc bot""" |
|
219
|
|
|
sys.argv = ["main.py", "irc"] |
|
220
|
|
|
protocol = determineProtocol() |
|
221
|
|
|
self.assertEqual(protocol, "irc") |
|
222
|
|
|
|
|
223
|
|
|
def testDetermineIRCProtocolisDefault(self): |
|
224
|
|
|
"""Test that if no argument is given, irc is the default""" |
|
225
|
|
|
sys.argv = ["main.py"] |
|
226
|
|
|
protocol = determineProtocol() |
|
227
|
|
|
self.assertEqual(protocol, "irc") |
|
228
|
|
|
|
|
229
|
|
|
def testDetermineConfigThrowsOnInvalidProto(self): |
|
230
|
|
|
"""Test that determineProtocol throws error on unsupported protocols""" |
|
231
|
|
|
sys.argv = ["main.py", "gopher"] |
|
232
|
|
|
with self.assertRaises(SystemExit) as e: |
|
233
|
|
|
determineProtocol() |
|
234
|
|
|
self.assertEqual(e.exception.code, 2) |
|
235
|
|
|
|
|
236
|
|
|
class TestBotFactoryMethod(TestCase): |
|
237
|
|
|
"""Test that createBot returns expected instances of Bots""" |
|
238
|
|
|
def testCreateIRCBot(self): |
|
239
|
|
|
"""Test that an irc bot can be created""" |
|
240
|
|
|
bot = createBot("irc") |
|
241
|
|
|
self.assertIsInstance(bot, IrcBot) |
|
242
|
|
|
|
|
243
|
|
|
def testCreateDiscordBot(self): |
|
244
|
|
|
"""Test that a discord bot can be created""" |
|
245
|
|
|
bot = createBot("discord") |
|
246
|
|
|
self.assertIsInstance(bot, DiscordBot) |
|
247
|
|
|
|
|
248
|
|
|
def testCreateUnsupportedProtocolThrows(self): |
|
249
|
|
|
"""Test that trying to create a bot with an unsupported protocol will throw exception""" |
|
250
|
|
|
with self.assertRaises(ValueError) as e: |
|
251
|
|
|
createBot("gopher") |
|
252
|
|
|
self.assertEqual(str(e.exception), "Unsupported protocol: gopher") |
|
253
|
|
|
|