test_marvin_actions

Tests for all Marvin actions

  1#! /usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3
  4"""
  5Tests for all Marvin actions
  6"""
  7
  8import json
  9import os
 10
 11from datetime import date, timedelta
 12from unittest import mock, TestCase
 13
 14import requests
 15
 16from bot import Bot
 17import marvin_actions
 18import marvin_general_actions
 19
 20class ActionTest(TestCase):
 21    """Test Marvin actions"""
 22    strings = {}
 23
 24    @classmethod
 25    def setUpClass(cls):
 26        with open("marvin_strings.json", encoding="utf-8") as f:
 27            cls.strings = json.load(f)
 28
 29
 30    def executeAction(self, action, message):
 31        """Execute an action for a message and return the response"""
 32        return action(Bot.tokenize(message))
 33
 34
 35    def assertActionOutput(self, action, message, expectedOutput):
 36        """Call an action on message and assert expected output"""
 37        actualOutput = self.executeAction(action, message)
 38
 39        self.assertEqual(actualOutput, expectedOutput)
 40
 41
 42    def assertActionSilent(self, action, message):
 43        """Call an action with provided message and assert no output"""
 44        self.assertActionOutput(action, message, None)
 45
 46
 47    def assertStringsOutput(self, action, message, expectedoutputKey, subkey=None):
 48        """Call an action with provided message and assert the output is equal to DB"""
 49        expectedOutput = self.strings.get(expectedoutputKey)
 50        if subkey is not None:
 51            if isinstance(expectedOutput, list):
 52                expectedOutput = expectedOutput[subkey]
 53            else:
 54                expectedOutput = expectedOutput.get(subkey)
 55        self.assertActionOutput(action, message, expectedOutput)
 56
 57
 58    def assertBBQResponse(self, todaysDate, bbqDate, expectedMessageKey):
 59        """Assert that the proper bbq message is returned, given a date"""
 60        url = self.strings.get("barbecue").get("url")
 61        message = self.strings.get("barbecue").get(expectedMessageKey)
 62        if isinstance(message, list):
 63            message = message[1]
 64        if expectedMessageKey in ["base", "week", "eternity"]:
 65            message = message % bbqDate
 66
 67        with mock.patch("marvin_actions.datetime") as d, mock.patch("marvin_actions.random") as r:
 68            d.date.today.return_value = todaysDate
 69            r.randint.return_value = 1
 70            expected = f"{url}. {message}"
 71            self.assertActionOutput(marvin_actions.marvinTimeToBBQ, "dags att grilla", expected)
 72
 73
 74    def createResponseFrom(self, directory, filename):
 75        """Create a response object with contect as contained in the specified file"""
 76        with open(os.path.join(directory, f"{filename}.json"), "r", encoding="UTF-8") as f:
 77            response = requests.models.Response()
 78            response._content = str.encode(json.dumps(json.load(f)))
 79            return response
 80
 81
 82    def assertNameDayOutput(self, exampleFile, expectedOutput):
 83        """Assert that the proper nameday message is returned, given an inputfile"""
 84        response = self.createResponseFrom("namedayFiles", exampleFile)
 85        with mock.patch("marvin_actions.requests") as r:
 86            r.get.return_value = response
 87            self.assertActionOutput(marvin_actions.marvinNameday, "nameday", expectedOutput)
 88
 89
 90    def assertJokeOutput(self, exampleFile, expectedOutput):
 91        """Assert that a joke is returned, given an input file"""
 92        response = self.createResponseFrom("jokeFiles", exampleFile)
 93        with mock.patch("marvin_actions.requests") as r:
 94            r.get.return_value = response
 95            self.assertActionOutput(marvin_actions.marvinJoke, "joke", expectedOutput)
 96
 97
 98    def assertSunOutput(self, expectedOutput):
 99        """Test that marvin knows when the sun comes up, given an input file"""
100        response = self.createResponseFrom("sunFiles", "sun")
101        with mock.patch("marvin_actions.requests") as r:
102            r.get.return_value = response
103            self.assertActionOutput(marvin_actions.marvinSun, "sol", expectedOutput)
104
105
106    def testSmile(self):
107        """Test that marvin can smile"""
108        with mock.patch("marvin_actions.random") as r:
109            r.randint.return_value = 1
110            self.assertStringsOutput(marvin_actions.marvinSmile, "le lite?", "smile", 1)
111        self.assertActionSilent(marvin_actions.marvinSmile, "sur idag?")
112
113    def testWhois(self):
114        """Test that marvin responds to whois"""
115        self.assertStringsOutput(marvin_actions.marvinWhoIs, "vem är marvin?", "whois")
116        self.assertActionSilent(marvin_actions.marvinWhoIs, "vemär")
117
118    def testGoogle(self):
119        """Test that marvin can help google stuff"""
120        with mock.patch("marvin_actions.random") as r:
121            r.randint.return_value = 1
122            self.assertActionOutput(
123                marvin_actions.marvinGoogle,
124                "kan du googla mos",
125                "LMGTFY https://www.google.se/search?q=mos")
126            self.assertActionOutput(
127                marvin_actions.marvinGoogle,
128                "kan du googla google mos",
129                "LMGTFY https://www.google.se/search?q=google+mos")
130        self.assertActionSilent(marvin_actions.marvinGoogle, "du kan googla")
131        self.assertActionSilent(marvin_actions.marvinGoogle, "gogool")
132
133    def testExplainShell(self):
134        """Test that marvin can explain shell commands"""
135        url = "https://explainshell.com/explain?cmd=pwd"
136        self.assertActionOutput(marvin_actions.marvinExplainShell, "explain pwd", url)
137        self.assertActionOutput(marvin_actions.marvinExplainShell, "can you explain pwd", url)
138        self.assertActionOutput(
139            marvin_actions.marvinExplainShell,
140            "förklara pwd|grep -o $user",
141            f"{url}%7Cgrep+-o+%24user")
142
143        self.assertActionSilent(marvin_actions.marvinExplainShell, "explains")
144
145    def testSource(self):
146        """Test that marvin responds to questions about source code"""
147        self.assertStringsOutput(marvin_actions.marvinSource, "source", "source")
148        self.assertStringsOutput(marvin_actions.marvinSource, "källkod", "source")
149        self.assertActionSilent(marvin_actions.marvinSource, "opensource")
150
151    def testBudord(self):
152        """Test that marvin knows all the commandments"""
153        for n, _ in enumerate(self.strings.get("budord")):
154            self.assertStringsOutput(marvin_actions.marvinBudord, f"budord #{n}", "budord", f"#{n}")
155
156        self.assertStringsOutput(marvin_actions.marvinBudord,"visa stentavla 1", "budord", "#1")
157        self.assertActionSilent(marvin_actions.marvinBudord, "var är stentavlan?")
158
159    def testQuote(self):
160        """Test that marvin can quote The Hitchhikers Guide to the Galaxy"""
161        with mock.patch("marvin_actions.random") as r:
162            r.randint.return_value = 1
163            self.assertStringsOutput(marvin_actions.marvinQuote, "ge os ett citat", "hitchhiker", 1)
164            self.assertStringsOutput(marvin_actions.marvinQuote, "filosofi", "hitchhiker", 1)
165            self.assertStringsOutput(marvin_actions.marvinQuote, "filosofera", "hitchhiker", 1)
166            self.assertActionSilent(marvin_actions.marvinQuote, "noquote")
167
168            for i,_ in enumerate(self.strings.get("hitchhiker")):
169                r.randint.return_value = i
170                self.assertStringsOutput(marvin_actions.marvinQuote, "quote", "hitchhiker", i)
171
172    def testVideoOfToday(self):
173        """Test that marvin can link to a different video each day of the week"""
174        with mock.patch("marvin_actions.datetime") as dt:
175            for d in range(1, 8):
176                day = date(2024, 11, 25) + timedelta(days=d)
177                dt.date.today.return_value = day
178                weekday = day.strftime("%A")
179                weekdayPhrase = self.strings.get("video-of-today").get(weekday).get("message")
180                videoPhrase = self.strings.get("video-of-today").get(weekday).get("url")
181                response = f"{weekdayPhrase} En passande video är {videoPhrase}"
182                self.assertActionOutput(marvin_actions.marvinVideoOfToday, "dagens video", response)
183        self.assertActionSilent(marvin_actions.marvinVideoOfToday, "videoidag")
184
185    def testHelp(self):
186        """Test that marvin can provide a help menu"""
187        self.assertStringsOutput(marvin_actions.marvinHelp, "help", "menu")
188        self.assertActionSilent(marvin_actions.marvinHelp, "halp")
189
190    def testSayHi(self):
191        """Test that marvin responds to greetings"""
192        with mock.patch("marvin_actions.random") as r:
193            for skey, s in enumerate(self.strings.get("smile")):
194                for hkey, h in enumerate(self.strings.get("hello")):
195                    for fkey, f in enumerate(self.strings.get("friendly")):
196                        r.randint.side_effect = [skey, hkey, fkey]
197                        self.assertActionOutput(marvin_actions.marvinSayHi, "hej", f"{s} {h} {f}")
198        self.assertActionSilent(marvin_actions.marvinSayHi, "korsning")
199
200    def testLunchLocations(self):
201        """Test that marvin can provide lunch suggestions for certain places"""
202        locations = ["karlskrona", "goteborg", "angelholm", "hassleholm", "malmo"]
203        with mock.patch("marvin_actions.random") as r:
204            for location in locations:
205                for i, place in enumerate(self.strings.get("lunch").get("location").get(location)):
206                    r.randint.side_effect = [0, i]
207                    self.assertActionOutput(
208                        marvin_actions.marvinLunch, f"mat {location}", f"Ska vi ta {place}?")
209            r.randint.side_effect = [1, 2]
210            self.assertActionOutput(
211                marvin_actions.marvinLunch, "dags att luncha", "Jag är lite sugen på Indiska?")
212        self.assertActionSilent(marvin_actions.marvinLunch, "matdags")
213
214    def testStrip(self):
215        """Test that marvin can recommend comics"""
216        messageFormat = self.strings.get("commitstrip").get("message")
217        expected = messageFormat.format(url=self.strings.get("commitstrip").get("url"))
218        self.assertActionOutput(marvin_actions.marvinStrip, "lite strip kanske?", expected)
219        self.assertActionSilent(marvin_actions.marvinStrip, "nostrip")
220
221    def testRandomStrip(self):
222        """Test that marvin can recommend random comics"""
223        messageFormat = self.strings.get("commitstrip").get("message")
224        expected = messageFormat.format(url=self.strings.get("commitstrip").get("urlPage") + "123")
225        with mock.patch("marvin_actions.random") as r:
226            r.randint.return_value = 123
227            self.assertActionOutput(marvin_actions.marvinStrip, "random strip kanske?", expected)
228
229    def testTimeToBBQ(self):
230        """Test that marvin knows when the next BBQ is"""
231        self.assertBBQResponse(date(2024, 5, 17), date(2024, 5, 17), "today")
232        self.assertBBQResponse(date(2024, 5, 16), date(2024, 5, 17), "tomorrow")
233        self.assertBBQResponse(date(2024, 5, 10), date(2024, 5, 17), "week")
234        self.assertBBQResponse(date(2024, 5, 1), date(2024, 5, 17), "base")
235        self.assertBBQResponse(date(2023, 10, 17), date(2024, 5, 17), "eternity")
236
237        self.assertBBQResponse(date(2024, 9, 20), date(2024, 9, 20), "today")
238        self.assertBBQResponse(date(2024, 9, 19), date(2024, 9, 20), "tomorrow")
239        self.assertBBQResponse(date(2024, 9, 13), date(2024, 9, 20), "week")
240        self.assertBBQResponse(date(2024, 9, 4), date(2024, 9, 20), "base")
241
242    def testNameDayReaction(self):
243        """Test that marvin only responds to nameday when asked"""
244        self.assertActionSilent(marvin_actions.marvinNameday, "anything")
245
246    def testNameDayRequest(self):
247        """Test that marvin sends a proper request for nameday info"""
248        with mock.patch("marvin_actions.requests") as r, mock.patch("marvin_actions.datetime") as d:
249            d.datetime.now.return_value = date(2024, 1, 2)
250            self.executeAction(marvin_actions.marvinNameday, "namnsdag")
251            self.assertEqual(r.get.call_args.args[0], "https://api.dryg.net/dagar/v2.1/2024/1/2")
252
253    def testNameDayResponse(self):
254        """Test that marvin properly parses nameday responses"""
255        self.assertNameDayOutput("single", "Idag har Svea namnsdag")
256        self.assertNameDayOutput("double", "Idag har Alfred och Alfrida namnsdag")
257        self.assertNameDayOutput("triple", "Idag har Kasper, Melker och Baltsar namnsdag")
258        self.assertNameDayOutput("nobody", "Ingen har namnsdag idag")
259
260    def testNameDayError(self):
261        """Tests that marvin returns the proper error message when nameday API is down"""
262        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
263            self.assertStringsOutput(
264                marvin_actions.marvinNameday,
265                "har någon namnsdag idag?",
266                "nameday",
267                "error")
268
269    def testJokeRequest(self):
270        """Test that marvin sends a proper request for a joke"""
271        with mock.patch("marvin_actions.requests") as r:
272            self.executeAction(marvin_actions.marvinJoke, "joke")
273            self.assertEqual(
274                r.get.call_args.args[0],
275                "https://api.chucknorris.io/jokes/random?category=dev")
276
277    def testJoke(self):
278        """Test that marvin sends a joke when requested"""
279        self.assertJokeOutput("joke", "There is no Esc key on Chuck Norris' keyboard, because no one escapes Chuck Norris.")
280
281    def testJokeError(self):
282        """Tests that marvin returns the proper error message when joke API is down"""
283        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
284            self.assertStringsOutput(marvin_actions.marvinJoke, "kör ett skämt", "joke", "error")
285
286    def testSun(self):
287        """Test that marvin sends the sunrise and sunset times """
288        self.assertSunOutput(
289            "Idag går solen upp 7:12 och ner 18:21. Iallafall i trakterna kring BTH.")
290
291    def testSunError(self):
292        """Tests that marvin returns the proper error message when joke API is down"""
293        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
294            self.assertStringsOutput(marvin_actions.marvinSun, "när går solen ner?", "sun", "error")
295
296    def testUptime(self):
297        """Test that marvin can provide the link to the uptime tournament"""
298        self.assertStringsOutput(marvin_actions.marvinUptime, "visa lite uptime", "uptime", "info")
299        self.assertActionSilent(marvin_actions.marvinUptime, "uptimetävling")
300
301    def testStream(self):
302        """Test that marvin can provide the link to the stream"""
303        self.assertStringsOutput(marvin_actions.marvinStream, "ska mos streama?", "stream", "info")
304        self.assertActionSilent(marvin_actions.marvinStream, "är mos en streamer?")
305
306    def testPrinciple(self):
307        """Test that marvin can recite some software principles"""
308        principles = self.strings.get("principle")
309        for key, value in principles.items():
310            self.assertActionOutput(marvin_actions.marvinPrinciple, f"princip {key}", value)
311        with mock.patch("marvin_actions.random") as r:
312            r.choice.return_value = "dry"
313            self.assertStringsOutput(marvin_actions.marvinPrinciple, "princip", "principle", "dry")
314        self.assertActionSilent(marvin_actions.marvinPrinciple, "principlös")
315
316    def testCommitRequest(self):
317        """Test that marvin sends proper requests when generating commit messages"""
318        with mock.patch("marvin_actions.requests") as r:
319            self.executeAction(marvin_actions.marvinCommit, "vad skriver man efter commit -m?")
320            self.assertEqual(r.get.call_args.args[0], "https://whatthecommit.com/index.txt")
321
322    def testCommitResponse(self):
323        """Test that marvin properly handles responses when generating commit messages"""
324        message = "Secret sauce #9"
325        response = requests.models.Response()
326        response._content = str.encode(message)
327        with mock.patch("marvin_actions.requests") as r:
328            r.get.return_value = response
329            expected = f"Använd detta meddelandet: '{message}'"
330            self.assertActionOutput(marvin_actions.marvinCommit, "commit", expected)
331
332    def testWeatherRequest(self):
333        """Test that marvin sends the expected requests for weather info"""
334        with mock.patch("marvin_actions.requests") as r:
335            self.executeAction(marvin_actions.marvinWeather, "väder")
336            for url in ["https://opendata-download-metobs.smhi.se/api/version/1.0/parameter/13/station/65090/period/latest-hour/data.json",
337                        "https://opendata-download-metobs.smhi.se/api/version/1.0/parameter/13/codes.json",
338                        "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/15.5890/lat/56.1500/data.json"]:
339                self.assertTrue(mock.call(url, timeout=5) in r.get.call_args_list)
340
341    def testWeatherResponse(self):
342        """Test that marvin properly parses weather responses"""
343        responses = []
344        for responseFile in ["station.json", "codes.json", "weather.json"]:
345            with open(os.path.join("weatherFiles", responseFile), "r", encoding="UTF-8") as f:
346                response = requests.models.Response()
347                response._content = str.encode(json.dumps(json.load(f)))
348                responses.append(response)
349
350        with mock.patch("marvin_actions.requests") as r:
351            r.get.side_effect = responses
352            expected = "Karlskrona just nu: 11.7 °C. Inget signifikant väder observerat."
353            self.assertActionOutput(marvin_actions.marvinWeather, "väder", expected)
354
355    def testCommitReaction(self):
356        """Test that marvin only generates commit messages when asked"""
357        self.assertActionSilent(marvin_actions.marvinCommit, "nocommit")
358
359
360    def testCommitError(self):
361        """Tests that marvin sends the proper message when get commit fails"""
362        with mock.patch("marvin_actions.requests.get", side_effect=Exception('API Down!')):
363            self.assertStringsOutput(
364                marvin_actions.marvinCommit,
365                "vad skriver man efter commit -m?",
366                "commit",
367                "error")
368
369    def testMorning(self):
370        """Test that marvin wishes good morning, at most once per day"""
371        marvin_general_actions.lastDateGreeted = None
372        with mock.patch("marvin_general_actions.datetime") as d:
373            d.date.today.return_value = date(2024, 5, 17)
374            with mock.patch("marvin_general_actions.random") as r:
375                r.choice.return_value = "Morgon"
376                self.assertActionOutput(marvin_general_actions.marvinMorning, "morrn", "Morgon")
377                # Should only greet once per day
378                self.assertActionSilent(marvin_general_actions.marvinMorning, "morgon")
379                # Should greet again tomorrow
380                d.date.today.return_value = date(2024, 5, 18)
381                self.assertActionOutput(marvin_general_actions.marvinMorning, "godmorgon", "Morgon")
class ActionTest(unittest.case.TestCase):
 21class ActionTest(TestCase):
 22    """Test Marvin actions"""
 23    strings = {}
 24
 25    @classmethod
 26    def setUpClass(cls):
 27        with open("marvin_strings.json", encoding="utf-8") as f:
 28            cls.strings = json.load(f)
 29
 30
 31    def executeAction(self, action, message):
 32        """Execute an action for a message and return the response"""
 33        return action(Bot.tokenize(message))
 34
 35
 36    def assertActionOutput(self, action, message, expectedOutput):
 37        """Call an action on message and assert expected output"""
 38        actualOutput = self.executeAction(action, message)
 39
 40        self.assertEqual(actualOutput, expectedOutput)
 41
 42
 43    def assertActionSilent(self, action, message):
 44        """Call an action with provided message and assert no output"""
 45        self.assertActionOutput(action, message, None)
 46
 47
 48    def assertStringsOutput(self, action, message, expectedoutputKey, subkey=None):
 49        """Call an action with provided message and assert the output is equal to DB"""
 50        expectedOutput = self.strings.get(expectedoutputKey)
 51        if subkey is not None:
 52            if isinstance(expectedOutput, list):
 53                expectedOutput = expectedOutput[subkey]
 54            else:
 55                expectedOutput = expectedOutput.get(subkey)
 56        self.assertActionOutput(action, message, expectedOutput)
 57
 58
 59    def assertBBQResponse(self, todaysDate, bbqDate, expectedMessageKey):
 60        """Assert that the proper bbq message is returned, given a date"""
 61        url = self.strings.get("barbecue").get("url")
 62        message = self.strings.get("barbecue").get(expectedMessageKey)
 63        if isinstance(message, list):
 64            message = message[1]
 65        if expectedMessageKey in ["base", "week", "eternity"]:
 66            message = message % bbqDate
 67
 68        with mock.patch("marvin_actions.datetime") as d, mock.patch("marvin_actions.random") as r:
 69            d.date.today.return_value = todaysDate
 70            r.randint.return_value = 1
 71            expected = f"{url}. {message}"
 72            self.assertActionOutput(marvin_actions.marvinTimeToBBQ, "dags att grilla", expected)
 73
 74
 75    def createResponseFrom(self, directory, filename):
 76        """Create a response object with contect as contained in the specified file"""
 77        with open(os.path.join(directory, f"{filename}.json"), "r", encoding="UTF-8") as f:
 78            response = requests.models.Response()
 79            response._content = str.encode(json.dumps(json.load(f)))
 80            return response
 81
 82
 83    def assertNameDayOutput(self, exampleFile, expectedOutput):
 84        """Assert that the proper nameday message is returned, given an inputfile"""
 85        response = self.createResponseFrom("namedayFiles", exampleFile)
 86        with mock.patch("marvin_actions.requests") as r:
 87            r.get.return_value = response
 88            self.assertActionOutput(marvin_actions.marvinNameday, "nameday", expectedOutput)
 89
 90
 91    def assertJokeOutput(self, exampleFile, expectedOutput):
 92        """Assert that a joke is returned, given an input file"""
 93        response = self.createResponseFrom("jokeFiles", exampleFile)
 94        with mock.patch("marvin_actions.requests") as r:
 95            r.get.return_value = response
 96            self.assertActionOutput(marvin_actions.marvinJoke, "joke", expectedOutput)
 97
 98
 99    def assertSunOutput(self, expectedOutput):
100        """Test that marvin knows when the sun comes up, given an input file"""
101        response = self.createResponseFrom("sunFiles", "sun")
102        with mock.patch("marvin_actions.requests") as r:
103            r.get.return_value = response
104            self.assertActionOutput(marvin_actions.marvinSun, "sol", expectedOutput)
105
106
107    def testSmile(self):
108        """Test that marvin can smile"""
109        with mock.patch("marvin_actions.random") as r:
110            r.randint.return_value = 1
111            self.assertStringsOutput(marvin_actions.marvinSmile, "le lite?", "smile", 1)
112        self.assertActionSilent(marvin_actions.marvinSmile, "sur idag?")
113
114    def testWhois(self):
115        """Test that marvin responds to whois"""
116        self.assertStringsOutput(marvin_actions.marvinWhoIs, "vem är marvin?", "whois")
117        self.assertActionSilent(marvin_actions.marvinWhoIs, "vemär")
118
119    def testGoogle(self):
120        """Test that marvin can help google stuff"""
121        with mock.patch("marvin_actions.random") as r:
122            r.randint.return_value = 1
123            self.assertActionOutput(
124                marvin_actions.marvinGoogle,
125                "kan du googla mos",
126                "LMGTFY https://www.google.se/search?q=mos")
127            self.assertActionOutput(
128                marvin_actions.marvinGoogle,
129                "kan du googla google mos",
130                "LMGTFY https://www.google.se/search?q=google+mos")
131        self.assertActionSilent(marvin_actions.marvinGoogle, "du kan googla")
132        self.assertActionSilent(marvin_actions.marvinGoogle, "gogool")
133
134    def testExplainShell(self):
135        """Test that marvin can explain shell commands"""
136        url = "https://explainshell.com/explain?cmd=pwd"
137        self.assertActionOutput(marvin_actions.marvinExplainShell, "explain pwd", url)
138        self.assertActionOutput(marvin_actions.marvinExplainShell, "can you explain pwd", url)
139        self.assertActionOutput(
140            marvin_actions.marvinExplainShell,
141            "förklara pwd|grep -o $user",
142            f"{url}%7Cgrep+-o+%24user")
143
144        self.assertActionSilent(marvin_actions.marvinExplainShell, "explains")
145
146    def testSource(self):
147        """Test that marvin responds to questions about source code"""
148        self.assertStringsOutput(marvin_actions.marvinSource, "source", "source")
149        self.assertStringsOutput(marvin_actions.marvinSource, "källkod", "source")
150        self.assertActionSilent(marvin_actions.marvinSource, "opensource")
151
152    def testBudord(self):
153        """Test that marvin knows all the commandments"""
154        for n, _ in enumerate(self.strings.get("budord")):
155            self.assertStringsOutput(marvin_actions.marvinBudord, f"budord #{n}", "budord", f"#{n}")
156
157        self.assertStringsOutput(marvin_actions.marvinBudord,"visa stentavla 1", "budord", "#1")
158        self.assertActionSilent(marvin_actions.marvinBudord, "var är stentavlan?")
159
160    def testQuote(self):
161        """Test that marvin can quote The Hitchhikers Guide to the Galaxy"""
162        with mock.patch("marvin_actions.random") as r:
163            r.randint.return_value = 1
164            self.assertStringsOutput(marvin_actions.marvinQuote, "ge os ett citat", "hitchhiker", 1)
165            self.assertStringsOutput(marvin_actions.marvinQuote, "filosofi", "hitchhiker", 1)
166            self.assertStringsOutput(marvin_actions.marvinQuote, "filosofera", "hitchhiker", 1)
167            self.assertActionSilent(marvin_actions.marvinQuote, "noquote")
168
169            for i,_ in enumerate(self.strings.get("hitchhiker")):
170                r.randint.return_value = i
171                self.assertStringsOutput(marvin_actions.marvinQuote, "quote", "hitchhiker", i)
172
173    def testVideoOfToday(self):
174        """Test that marvin can link to a different video each day of the week"""
175        with mock.patch("marvin_actions.datetime") as dt:
176            for d in range(1, 8):
177                day = date(2024, 11, 25) + timedelta(days=d)
178                dt.date.today.return_value = day
179                weekday = day.strftime("%A")
180                weekdayPhrase = self.strings.get("video-of-today").get(weekday).get("message")
181                videoPhrase = self.strings.get("video-of-today").get(weekday).get("url")
182                response = f"{weekdayPhrase} En passande video är {videoPhrase}"
183                self.assertActionOutput(marvin_actions.marvinVideoOfToday, "dagens video", response)
184        self.assertActionSilent(marvin_actions.marvinVideoOfToday, "videoidag")
185
186    def testHelp(self):
187        """Test that marvin can provide a help menu"""
188        self.assertStringsOutput(marvin_actions.marvinHelp, "help", "menu")
189        self.assertActionSilent(marvin_actions.marvinHelp, "halp")
190
191    def testSayHi(self):
192        """Test that marvin responds to greetings"""
193        with mock.patch("marvin_actions.random") as r:
194            for skey, s in enumerate(self.strings.get("smile")):
195                for hkey, h in enumerate(self.strings.get("hello")):
196                    for fkey, f in enumerate(self.strings.get("friendly")):
197                        r.randint.side_effect = [skey, hkey, fkey]
198                        self.assertActionOutput(marvin_actions.marvinSayHi, "hej", f"{s} {h} {f}")
199        self.assertActionSilent(marvin_actions.marvinSayHi, "korsning")
200
201    def testLunchLocations(self):
202        """Test that marvin can provide lunch suggestions for certain places"""
203        locations = ["karlskrona", "goteborg", "angelholm", "hassleholm", "malmo"]
204        with mock.patch("marvin_actions.random") as r:
205            for location in locations:
206                for i, place in enumerate(self.strings.get("lunch").get("location").get(location)):
207                    r.randint.side_effect = [0, i]
208                    self.assertActionOutput(
209                        marvin_actions.marvinLunch, f"mat {location}", f"Ska vi ta {place}?")
210            r.randint.side_effect = [1, 2]
211            self.assertActionOutput(
212                marvin_actions.marvinLunch, "dags att luncha", "Jag är lite sugen på Indiska?")
213        self.assertActionSilent(marvin_actions.marvinLunch, "matdags")
214
215    def testStrip(self):
216        """Test that marvin can recommend comics"""
217        messageFormat = self.strings.get("commitstrip").get("message")
218        expected = messageFormat.format(url=self.strings.get("commitstrip").get("url"))
219        self.assertActionOutput(marvin_actions.marvinStrip, "lite strip kanske?", expected)
220        self.assertActionSilent(marvin_actions.marvinStrip, "nostrip")
221
222    def testRandomStrip(self):
223        """Test that marvin can recommend random comics"""
224        messageFormat = self.strings.get("commitstrip").get("message")
225        expected = messageFormat.format(url=self.strings.get("commitstrip").get("urlPage") + "123")
226        with mock.patch("marvin_actions.random") as r:
227            r.randint.return_value = 123
228            self.assertActionOutput(marvin_actions.marvinStrip, "random strip kanske?", expected)
229
230    def testTimeToBBQ(self):
231        """Test that marvin knows when the next BBQ is"""
232        self.assertBBQResponse(date(2024, 5, 17), date(2024, 5, 17), "today")
233        self.assertBBQResponse(date(2024, 5, 16), date(2024, 5, 17), "tomorrow")
234        self.assertBBQResponse(date(2024, 5, 10), date(2024, 5, 17), "week")
235        self.assertBBQResponse(date(2024, 5, 1), date(2024, 5, 17), "base")
236        self.assertBBQResponse(date(2023, 10, 17), date(2024, 5, 17), "eternity")
237
238        self.assertBBQResponse(date(2024, 9, 20), date(2024, 9, 20), "today")
239        self.assertBBQResponse(date(2024, 9, 19), date(2024, 9, 20), "tomorrow")
240        self.assertBBQResponse(date(2024, 9, 13), date(2024, 9, 20), "week")
241        self.assertBBQResponse(date(2024, 9, 4), date(2024, 9, 20), "base")
242
243    def testNameDayReaction(self):
244        """Test that marvin only responds to nameday when asked"""
245        self.assertActionSilent(marvin_actions.marvinNameday, "anything")
246
247    def testNameDayRequest(self):
248        """Test that marvin sends a proper request for nameday info"""
249        with mock.patch("marvin_actions.requests") as r, mock.patch("marvin_actions.datetime") as d:
250            d.datetime.now.return_value = date(2024, 1, 2)
251            self.executeAction(marvin_actions.marvinNameday, "namnsdag")
252            self.assertEqual(r.get.call_args.args[0], "https://api.dryg.net/dagar/v2.1/2024/1/2")
253
254    def testNameDayResponse(self):
255        """Test that marvin properly parses nameday responses"""
256        self.assertNameDayOutput("single", "Idag har Svea namnsdag")
257        self.assertNameDayOutput("double", "Idag har Alfred och Alfrida namnsdag")
258        self.assertNameDayOutput("triple", "Idag har Kasper, Melker och Baltsar namnsdag")
259        self.assertNameDayOutput("nobody", "Ingen har namnsdag idag")
260
261    def testNameDayError(self):
262        """Tests that marvin returns the proper error message when nameday API is down"""
263        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
264            self.assertStringsOutput(
265                marvin_actions.marvinNameday,
266                "har någon namnsdag idag?",
267                "nameday",
268                "error")
269
270    def testJokeRequest(self):
271        """Test that marvin sends a proper request for a joke"""
272        with mock.patch("marvin_actions.requests") as r:
273            self.executeAction(marvin_actions.marvinJoke, "joke")
274            self.assertEqual(
275                r.get.call_args.args[0],
276                "https://api.chucknorris.io/jokes/random?category=dev")
277
278    def testJoke(self):
279        """Test that marvin sends a joke when requested"""
280        self.assertJokeOutput("joke", "There is no Esc key on Chuck Norris' keyboard, because no one escapes Chuck Norris.")
281
282    def testJokeError(self):
283        """Tests that marvin returns the proper error message when joke API is down"""
284        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
285            self.assertStringsOutput(marvin_actions.marvinJoke, "kör ett skämt", "joke", "error")
286
287    def testSun(self):
288        """Test that marvin sends the sunrise and sunset times """
289        self.assertSunOutput(
290            "Idag går solen upp 7:12 och ner 18:21. Iallafall i trakterna kring BTH.")
291
292    def testSunError(self):
293        """Tests that marvin returns the proper error message when joke API is down"""
294        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
295            self.assertStringsOutput(marvin_actions.marvinSun, "när går solen ner?", "sun", "error")
296
297    def testUptime(self):
298        """Test that marvin can provide the link to the uptime tournament"""
299        self.assertStringsOutput(marvin_actions.marvinUptime, "visa lite uptime", "uptime", "info")
300        self.assertActionSilent(marvin_actions.marvinUptime, "uptimetävling")
301
302    def testStream(self):
303        """Test that marvin can provide the link to the stream"""
304        self.assertStringsOutput(marvin_actions.marvinStream, "ska mos streama?", "stream", "info")
305        self.assertActionSilent(marvin_actions.marvinStream, "är mos en streamer?")
306
307    def testPrinciple(self):
308        """Test that marvin can recite some software principles"""
309        principles = self.strings.get("principle")
310        for key, value in principles.items():
311            self.assertActionOutput(marvin_actions.marvinPrinciple, f"princip {key}", value)
312        with mock.patch("marvin_actions.random") as r:
313            r.choice.return_value = "dry"
314            self.assertStringsOutput(marvin_actions.marvinPrinciple, "princip", "principle", "dry")
315        self.assertActionSilent(marvin_actions.marvinPrinciple, "principlös")
316
317    def testCommitRequest(self):
318        """Test that marvin sends proper requests when generating commit messages"""
319        with mock.patch("marvin_actions.requests") as r:
320            self.executeAction(marvin_actions.marvinCommit, "vad skriver man efter commit -m?")
321            self.assertEqual(r.get.call_args.args[0], "https://whatthecommit.com/index.txt")
322
323    def testCommitResponse(self):
324        """Test that marvin properly handles responses when generating commit messages"""
325        message = "Secret sauce #9"
326        response = requests.models.Response()
327        response._content = str.encode(message)
328        with mock.patch("marvin_actions.requests") as r:
329            r.get.return_value = response
330            expected = f"Använd detta meddelandet: '{message}'"
331            self.assertActionOutput(marvin_actions.marvinCommit, "commit", expected)
332
333    def testWeatherRequest(self):
334        """Test that marvin sends the expected requests for weather info"""
335        with mock.patch("marvin_actions.requests") as r:
336            self.executeAction(marvin_actions.marvinWeather, "väder")
337            for url in ["https://opendata-download-metobs.smhi.se/api/version/1.0/parameter/13/station/65090/period/latest-hour/data.json",
338                        "https://opendata-download-metobs.smhi.se/api/version/1.0/parameter/13/codes.json",
339                        "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/15.5890/lat/56.1500/data.json"]:
340                self.assertTrue(mock.call(url, timeout=5) in r.get.call_args_list)
341
342    def testWeatherResponse(self):
343        """Test that marvin properly parses weather responses"""
344        responses = []
345        for responseFile in ["station.json", "codes.json", "weather.json"]:
346            with open(os.path.join("weatherFiles", responseFile), "r", encoding="UTF-8") as f:
347                response = requests.models.Response()
348                response._content = str.encode(json.dumps(json.load(f)))
349                responses.append(response)
350
351        with mock.patch("marvin_actions.requests") as r:
352            r.get.side_effect = responses
353            expected = "Karlskrona just nu: 11.7 °C. Inget signifikant väder observerat."
354            self.assertActionOutput(marvin_actions.marvinWeather, "väder", expected)
355
356    def testCommitReaction(self):
357        """Test that marvin only generates commit messages when asked"""
358        self.assertActionSilent(marvin_actions.marvinCommit, "nocommit")
359
360
361    def testCommitError(self):
362        """Tests that marvin sends the proper message when get commit fails"""
363        with mock.patch("marvin_actions.requests.get", side_effect=Exception('API Down!')):
364            self.assertStringsOutput(
365                marvin_actions.marvinCommit,
366                "vad skriver man efter commit -m?",
367                "commit",
368                "error")
369
370    def testMorning(self):
371        """Test that marvin wishes good morning, at most once per day"""
372        marvin_general_actions.lastDateGreeted = None
373        with mock.patch("marvin_general_actions.datetime") as d:
374            d.date.today.return_value = date(2024, 5, 17)
375            with mock.patch("marvin_general_actions.random") as r:
376                r.choice.return_value = "Morgon"
377                self.assertActionOutput(marvin_general_actions.marvinMorning, "morrn", "Morgon")
378                # Should only greet once per day
379                self.assertActionSilent(marvin_general_actions.marvinMorning, "morgon")
380                # Should greet again tomorrow
381                d.date.today.return_value = date(2024, 5, 18)
382                self.assertActionOutput(marvin_general_actions.marvinMorning, "godmorgon", "Morgon")

Test Marvin actions

strings = {}
@classmethod
def setUpClass(cls):
25    @classmethod
26    def setUpClass(cls):
27        with open("marvin_strings.json", encoding="utf-8") as f:
28            cls.strings = json.load(f)

Hook method for setting up class fixture before running tests in the class.

def executeAction(self, action, message):
31    def executeAction(self, action, message):
32        """Execute an action for a message and return the response"""
33        return action(Bot.tokenize(message))

Execute an action for a message and return the response

def assertActionOutput(self, action, message, expectedOutput):
36    def assertActionOutput(self, action, message, expectedOutput):
37        """Call an action on message and assert expected output"""
38        actualOutput = self.executeAction(action, message)
39
40        self.assertEqual(actualOutput, expectedOutput)

Call an action on message and assert expected output

def assertActionSilent(self, action, message):
43    def assertActionSilent(self, action, message):
44        """Call an action with provided message and assert no output"""
45        self.assertActionOutput(action, message, None)

Call an action with provided message and assert no output

def assertStringsOutput(self, action, message, expectedoutputKey, subkey=None):
48    def assertStringsOutput(self, action, message, expectedoutputKey, subkey=None):
49        """Call an action with provided message and assert the output is equal to DB"""
50        expectedOutput = self.strings.get(expectedoutputKey)
51        if subkey is not None:
52            if isinstance(expectedOutput, list):
53                expectedOutput = expectedOutput[subkey]
54            else:
55                expectedOutput = expectedOutput.get(subkey)
56        self.assertActionOutput(action, message, expectedOutput)

Call an action with provided message and assert the output is equal to DB

def assertBBQResponse(self, todaysDate, bbqDate, expectedMessageKey):
59    def assertBBQResponse(self, todaysDate, bbqDate, expectedMessageKey):
60        """Assert that the proper bbq message is returned, given a date"""
61        url = self.strings.get("barbecue").get("url")
62        message = self.strings.get("barbecue").get(expectedMessageKey)
63        if isinstance(message, list):
64            message = message[1]
65        if expectedMessageKey in ["base", "week", "eternity"]:
66            message = message % bbqDate
67
68        with mock.patch("marvin_actions.datetime") as d, mock.patch("marvin_actions.random") as r:
69            d.date.today.return_value = todaysDate
70            r.randint.return_value = 1
71            expected = f"{url}. {message}"
72            self.assertActionOutput(marvin_actions.marvinTimeToBBQ, "dags att grilla", expected)

Assert that the proper bbq message is returned, given a date

def createResponseFrom(self, directory, filename):
75    def createResponseFrom(self, directory, filename):
76        """Create a response object with contect as contained in the specified file"""
77        with open(os.path.join(directory, f"{filename}.json"), "r", encoding="UTF-8") as f:
78            response = requests.models.Response()
79            response._content = str.encode(json.dumps(json.load(f)))
80            return response

Create a response object with contect as contained in the specified file

def assertNameDayOutput(self, exampleFile, expectedOutput):
83    def assertNameDayOutput(self, exampleFile, expectedOutput):
84        """Assert that the proper nameday message is returned, given an inputfile"""
85        response = self.createResponseFrom("namedayFiles", exampleFile)
86        with mock.patch("marvin_actions.requests") as r:
87            r.get.return_value = response
88            self.assertActionOutput(marvin_actions.marvinNameday, "nameday", expectedOutput)

Assert that the proper nameday message is returned, given an inputfile

def assertJokeOutput(self, exampleFile, expectedOutput):
91    def assertJokeOutput(self, exampleFile, expectedOutput):
92        """Assert that a joke is returned, given an input file"""
93        response = self.createResponseFrom("jokeFiles", exampleFile)
94        with mock.patch("marvin_actions.requests") as r:
95            r.get.return_value = response
96            self.assertActionOutput(marvin_actions.marvinJoke, "joke", expectedOutput)

Assert that a joke is returned, given an input file

def assertSunOutput(self, expectedOutput):
 99    def assertSunOutput(self, expectedOutput):
100        """Test that marvin knows when the sun comes up, given an input file"""
101        response = self.createResponseFrom("sunFiles", "sun")
102        with mock.patch("marvin_actions.requests") as r:
103            r.get.return_value = response
104            self.assertActionOutput(marvin_actions.marvinSun, "sol", expectedOutput)

Test that marvin knows when the sun comes up, given an input file

def testSmile(self):
107    def testSmile(self):
108        """Test that marvin can smile"""
109        with mock.patch("marvin_actions.random") as r:
110            r.randint.return_value = 1
111            self.assertStringsOutput(marvin_actions.marvinSmile, "le lite?", "smile", 1)
112        self.assertActionSilent(marvin_actions.marvinSmile, "sur idag?")

Test that marvin can smile

def testWhois(self):
114    def testWhois(self):
115        """Test that marvin responds to whois"""
116        self.assertStringsOutput(marvin_actions.marvinWhoIs, "vem är marvin?", "whois")
117        self.assertActionSilent(marvin_actions.marvinWhoIs, "vemär")

Test that marvin responds to whois

def testGoogle(self):
119    def testGoogle(self):
120        """Test that marvin can help google stuff"""
121        with mock.patch("marvin_actions.random") as r:
122            r.randint.return_value = 1
123            self.assertActionOutput(
124                marvin_actions.marvinGoogle,
125                "kan du googla mos",
126                "LMGTFY https://www.google.se/search?q=mos")
127            self.assertActionOutput(
128                marvin_actions.marvinGoogle,
129                "kan du googla google mos",
130                "LMGTFY https://www.google.se/search?q=google+mos")
131        self.assertActionSilent(marvin_actions.marvinGoogle, "du kan googla")
132        self.assertActionSilent(marvin_actions.marvinGoogle, "gogool")

Test that marvin can help google stuff

def testExplainShell(self):
134    def testExplainShell(self):
135        """Test that marvin can explain shell commands"""
136        url = "https://explainshell.com/explain?cmd=pwd"
137        self.assertActionOutput(marvin_actions.marvinExplainShell, "explain pwd", url)
138        self.assertActionOutput(marvin_actions.marvinExplainShell, "can you explain pwd", url)
139        self.assertActionOutput(
140            marvin_actions.marvinExplainShell,
141            "förklara pwd|grep -o $user",
142            f"{url}%7Cgrep+-o+%24user")
143
144        self.assertActionSilent(marvin_actions.marvinExplainShell, "explains")

Test that marvin can explain shell commands

def testSource(self):
146    def testSource(self):
147        """Test that marvin responds to questions about source code"""
148        self.assertStringsOutput(marvin_actions.marvinSource, "source", "source")
149        self.assertStringsOutput(marvin_actions.marvinSource, "källkod", "source")
150        self.assertActionSilent(marvin_actions.marvinSource, "opensource")

Test that marvin responds to questions about source code

def testBudord(self):
152    def testBudord(self):
153        """Test that marvin knows all the commandments"""
154        for n, _ in enumerate(self.strings.get("budord")):
155            self.assertStringsOutput(marvin_actions.marvinBudord, f"budord #{n}", "budord", f"#{n}")
156
157        self.assertStringsOutput(marvin_actions.marvinBudord,"visa stentavla 1", "budord", "#1")
158        self.assertActionSilent(marvin_actions.marvinBudord, "var är stentavlan?")

Test that marvin knows all the commandments

def testQuote(self):
160    def testQuote(self):
161        """Test that marvin can quote The Hitchhikers Guide to the Galaxy"""
162        with mock.patch("marvin_actions.random") as r:
163            r.randint.return_value = 1
164            self.assertStringsOutput(marvin_actions.marvinQuote, "ge os ett citat", "hitchhiker", 1)
165            self.assertStringsOutput(marvin_actions.marvinQuote, "filosofi", "hitchhiker", 1)
166            self.assertStringsOutput(marvin_actions.marvinQuote, "filosofera", "hitchhiker", 1)
167            self.assertActionSilent(marvin_actions.marvinQuote, "noquote")
168
169            for i,_ in enumerate(self.strings.get("hitchhiker")):
170                r.randint.return_value = i
171                self.assertStringsOutput(marvin_actions.marvinQuote, "quote", "hitchhiker", i)

Test that marvin can quote The Hitchhikers Guide to the Galaxy

def testVideoOfToday(self):
173    def testVideoOfToday(self):
174        """Test that marvin can link to a different video each day of the week"""
175        with mock.patch("marvin_actions.datetime") as dt:
176            for d in range(1, 8):
177                day = date(2024, 11, 25) + timedelta(days=d)
178                dt.date.today.return_value = day
179                weekday = day.strftime("%A")
180                weekdayPhrase = self.strings.get("video-of-today").get(weekday).get("message")
181                videoPhrase = self.strings.get("video-of-today").get(weekday).get("url")
182                response = f"{weekdayPhrase} En passande video är {videoPhrase}"
183                self.assertActionOutput(marvin_actions.marvinVideoOfToday, "dagens video", response)
184        self.assertActionSilent(marvin_actions.marvinVideoOfToday, "videoidag")

Test that marvin can link to a different video each day of the week

def testHelp(self):
186    def testHelp(self):
187        """Test that marvin can provide a help menu"""
188        self.assertStringsOutput(marvin_actions.marvinHelp, "help", "menu")
189        self.assertActionSilent(marvin_actions.marvinHelp, "halp")

Test that marvin can provide a help menu

def testSayHi(self):
191    def testSayHi(self):
192        """Test that marvin responds to greetings"""
193        with mock.patch("marvin_actions.random") as r:
194            for skey, s in enumerate(self.strings.get("smile")):
195                for hkey, h in enumerate(self.strings.get("hello")):
196                    for fkey, f in enumerate(self.strings.get("friendly")):
197                        r.randint.side_effect = [skey, hkey, fkey]
198                        self.assertActionOutput(marvin_actions.marvinSayHi, "hej", f"{s} {h} {f}")
199        self.assertActionSilent(marvin_actions.marvinSayHi, "korsning")

Test that marvin responds to greetings

def testLunchLocations(self):
201    def testLunchLocations(self):
202        """Test that marvin can provide lunch suggestions for certain places"""
203        locations = ["karlskrona", "goteborg", "angelholm", "hassleholm", "malmo"]
204        with mock.patch("marvin_actions.random") as r:
205            for location in locations:
206                for i, place in enumerate(self.strings.get("lunch").get("location").get(location)):
207                    r.randint.side_effect = [0, i]
208                    self.assertActionOutput(
209                        marvin_actions.marvinLunch, f"mat {location}", f"Ska vi ta {place}?")
210            r.randint.side_effect = [1, 2]
211            self.assertActionOutput(
212                marvin_actions.marvinLunch, "dags att luncha", "Jag är lite sugen på Indiska?")
213        self.assertActionSilent(marvin_actions.marvinLunch, "matdags")

Test that marvin can provide lunch suggestions for certain places

def testStrip(self):
215    def testStrip(self):
216        """Test that marvin can recommend comics"""
217        messageFormat = self.strings.get("commitstrip").get("message")
218        expected = messageFormat.format(url=self.strings.get("commitstrip").get("url"))
219        self.assertActionOutput(marvin_actions.marvinStrip, "lite strip kanske?", expected)
220        self.assertActionSilent(marvin_actions.marvinStrip, "nostrip")

Test that marvin can recommend comics

def testRandomStrip(self):
222    def testRandomStrip(self):
223        """Test that marvin can recommend random comics"""
224        messageFormat = self.strings.get("commitstrip").get("message")
225        expected = messageFormat.format(url=self.strings.get("commitstrip").get("urlPage") + "123")
226        with mock.patch("marvin_actions.random") as r:
227            r.randint.return_value = 123
228            self.assertActionOutput(marvin_actions.marvinStrip, "random strip kanske?", expected)

Test that marvin can recommend random comics

def testTimeToBBQ(self):
230    def testTimeToBBQ(self):
231        """Test that marvin knows when the next BBQ is"""
232        self.assertBBQResponse(date(2024, 5, 17), date(2024, 5, 17), "today")
233        self.assertBBQResponse(date(2024, 5, 16), date(2024, 5, 17), "tomorrow")
234        self.assertBBQResponse(date(2024, 5, 10), date(2024, 5, 17), "week")
235        self.assertBBQResponse(date(2024, 5, 1), date(2024, 5, 17), "base")
236        self.assertBBQResponse(date(2023, 10, 17), date(2024, 5, 17), "eternity")
237
238        self.assertBBQResponse(date(2024, 9, 20), date(2024, 9, 20), "today")
239        self.assertBBQResponse(date(2024, 9, 19), date(2024, 9, 20), "tomorrow")
240        self.assertBBQResponse(date(2024, 9, 13), date(2024, 9, 20), "week")
241        self.assertBBQResponse(date(2024, 9, 4), date(2024, 9, 20), "base")

Test that marvin knows when the next BBQ is

def testNameDayReaction(self):
243    def testNameDayReaction(self):
244        """Test that marvin only responds to nameday when asked"""
245        self.assertActionSilent(marvin_actions.marvinNameday, "anything")

Test that marvin only responds to nameday when asked

def testNameDayRequest(self):
247    def testNameDayRequest(self):
248        """Test that marvin sends a proper request for nameday info"""
249        with mock.patch("marvin_actions.requests") as r, mock.patch("marvin_actions.datetime") as d:
250            d.datetime.now.return_value = date(2024, 1, 2)
251            self.executeAction(marvin_actions.marvinNameday, "namnsdag")
252            self.assertEqual(r.get.call_args.args[0], "https://api.dryg.net/dagar/v2.1/2024/1/2")

Test that marvin sends a proper request for nameday info

def testNameDayResponse(self):
254    def testNameDayResponse(self):
255        """Test that marvin properly parses nameday responses"""
256        self.assertNameDayOutput("single", "Idag har Svea namnsdag")
257        self.assertNameDayOutput("double", "Idag har Alfred och Alfrida namnsdag")
258        self.assertNameDayOutput("triple", "Idag har Kasper, Melker och Baltsar namnsdag")
259        self.assertNameDayOutput("nobody", "Ingen har namnsdag idag")

Test that marvin properly parses nameday responses

def testNameDayError(self):
261    def testNameDayError(self):
262        """Tests that marvin returns the proper error message when nameday API is down"""
263        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
264            self.assertStringsOutput(
265                marvin_actions.marvinNameday,
266                "har någon namnsdag idag?",
267                "nameday",
268                "error")

Tests that marvin returns the proper error message when nameday API is down

def testJokeRequest(self):
270    def testJokeRequest(self):
271        """Test that marvin sends a proper request for a joke"""
272        with mock.patch("marvin_actions.requests") as r:
273            self.executeAction(marvin_actions.marvinJoke, "joke")
274            self.assertEqual(
275                r.get.call_args.args[0],
276                "https://api.chucknorris.io/jokes/random?category=dev")

Test that marvin sends a proper request for a joke

def testJoke(self):
278    def testJoke(self):
279        """Test that marvin sends a joke when requested"""
280        self.assertJokeOutput("joke", "There is no Esc key on Chuck Norris' keyboard, because no one escapes Chuck Norris.")

Test that marvin sends a joke when requested

def testJokeError(self):
282    def testJokeError(self):
283        """Tests that marvin returns the proper error message when joke API is down"""
284        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
285            self.assertStringsOutput(marvin_actions.marvinJoke, "kör ett skämt", "joke", "error")

Tests that marvin returns the proper error message when joke API is down

def testSun(self):
287    def testSun(self):
288        """Test that marvin sends the sunrise and sunset times """
289        self.assertSunOutput(
290            "Idag går solen upp 7:12 och ner 18:21. Iallafall i trakterna kring BTH.")

Test that marvin sends the sunrise and sunset times

def testSunError(self):
292    def testSunError(self):
293        """Tests that marvin returns the proper error message when joke API is down"""
294        with mock.patch("marvin_actions.requests.get", side_effect=Exception("API Down!")):
295            self.assertStringsOutput(marvin_actions.marvinSun, "när går solen ner?", "sun", "error")

Tests that marvin returns the proper error message when joke API is down

def testUptime(self):
297    def testUptime(self):
298        """Test that marvin can provide the link to the uptime tournament"""
299        self.assertStringsOutput(marvin_actions.marvinUptime, "visa lite uptime", "uptime", "info")
300        self.assertActionSilent(marvin_actions.marvinUptime, "uptimetävling")

Test that marvin can provide the link to the uptime tournament

def testStream(self):
302    def testStream(self):
303        """Test that marvin can provide the link to the stream"""
304        self.assertStringsOutput(marvin_actions.marvinStream, "ska mos streama?", "stream", "info")
305        self.assertActionSilent(marvin_actions.marvinStream, "är mos en streamer?")

Test that marvin can provide the link to the stream

def testPrinciple(self):
307    def testPrinciple(self):
308        """Test that marvin can recite some software principles"""
309        principles = self.strings.get("principle")
310        for key, value in principles.items():
311            self.assertActionOutput(marvin_actions.marvinPrinciple, f"princip {key}", value)
312        with mock.patch("marvin_actions.random") as r:
313            r.choice.return_value = "dry"
314            self.assertStringsOutput(marvin_actions.marvinPrinciple, "princip", "principle", "dry")
315        self.assertActionSilent(marvin_actions.marvinPrinciple, "principlös")

Test that marvin can recite some software principles

def testCommitRequest(self):
317    def testCommitRequest(self):
318        """Test that marvin sends proper requests when generating commit messages"""
319        with mock.patch("marvin_actions.requests") as r:
320            self.executeAction(marvin_actions.marvinCommit, "vad skriver man efter commit -m?")
321            self.assertEqual(r.get.call_args.args[0], "https://whatthecommit.com/index.txt")

Test that marvin sends proper requests when generating commit messages

def testCommitResponse(self):
323    def testCommitResponse(self):
324        """Test that marvin properly handles responses when generating commit messages"""
325        message = "Secret sauce #9"
326        response = requests.models.Response()
327        response._content = str.encode(message)
328        with mock.patch("marvin_actions.requests") as r:
329            r.get.return_value = response
330            expected = f"Använd detta meddelandet: '{message}'"
331            self.assertActionOutput(marvin_actions.marvinCommit, "commit", expected)

Test that marvin properly handles responses when generating commit messages

def testWeatherRequest(self):
333    def testWeatherRequest(self):
334        """Test that marvin sends the expected requests for weather info"""
335        with mock.patch("marvin_actions.requests") as r:
336            self.executeAction(marvin_actions.marvinWeather, "väder")
337            for url in ["https://opendata-download-metobs.smhi.se/api/version/1.0/parameter/13/station/65090/period/latest-hour/data.json",
338                        "https://opendata-download-metobs.smhi.se/api/version/1.0/parameter/13/codes.json",
339                        "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/15.5890/lat/56.1500/data.json"]:
340                self.assertTrue(mock.call(url, timeout=5) in r.get.call_args_list)

Test that marvin sends the expected requests for weather info

def testWeatherResponse(self):
342    def testWeatherResponse(self):
343        """Test that marvin properly parses weather responses"""
344        responses = []
345        for responseFile in ["station.json", "codes.json", "weather.json"]:
346            with open(os.path.join("weatherFiles", responseFile), "r", encoding="UTF-8") as f:
347                response = requests.models.Response()
348                response._content = str.encode(json.dumps(json.load(f)))
349                responses.append(response)
350
351        with mock.patch("marvin_actions.requests") as r:
352            r.get.side_effect = responses
353            expected = "Karlskrona just nu: 11.7 °C. Inget signifikant väder observerat."
354            self.assertActionOutput(marvin_actions.marvinWeather, "väder", expected)

Test that marvin properly parses weather responses

def testCommitReaction(self):
356    def testCommitReaction(self):
357        """Test that marvin only generates commit messages when asked"""
358        self.assertActionSilent(marvin_actions.marvinCommit, "nocommit")

Test that marvin only generates commit messages when asked

def testCommitError(self):
361    def testCommitError(self):
362        """Tests that marvin sends the proper message when get commit fails"""
363        with mock.patch("marvin_actions.requests.get", side_effect=Exception('API Down!')):
364            self.assertStringsOutput(
365                marvin_actions.marvinCommit,
366                "vad skriver man efter commit -m?",
367                "commit",
368                "error")

Tests that marvin sends the proper message when get commit fails

def testMorning(self):
370    def testMorning(self):
371        """Test that marvin wishes good morning, at most once per day"""
372        marvin_general_actions.lastDateGreeted = None
373        with mock.patch("marvin_general_actions.datetime") as d:
374            d.date.today.return_value = date(2024, 5, 17)
375            with mock.patch("marvin_general_actions.random") as r:
376                r.choice.return_value = "Morgon"
377                self.assertActionOutput(marvin_general_actions.marvinMorning, "morrn", "Morgon")
378                # Should only greet once per day
379                self.assertActionSilent(marvin_general_actions.marvinMorning, "morgon")
380                # Should greet again tomorrow
381                d.date.today.return_value = date(2024, 5, 18)
382                self.assertActionOutput(marvin_general_actions.marvinMorning, "godmorgon", "Morgon")

Test that marvin wishes good morning, at most once per day