Test Failed
Pull Request — master (#86)
by Daniel
06:22 queued 03:08
created

test_main.FormattingTest.assertPrintOption()   A

Complexity

Conditions 3

Size

Total Lines 9
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nop 4
dl 0
loc 9
rs 10
c 0
b 0
f 0
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