# Copyright (C) 2017-2019 Alban Gruin # # celcatsanitizer is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # celcatsanitizer is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . from unittest import mock from django.test import TestCase from django.utils import timezone from .management.parsers.abstractparser import ParserError from .management.parsers.ups2018 import Parser as UPS2018Parser from .models import Course, Group, Module, Room, Source, Timetable, Year from .templatetags.rooms import format_rooms from .utils import tz_now import datetime import os def mock_requests_get(*args, **kwargs): class MockedResponse: def __init__(self, content=""): self.encoding = "utf-8" self.content = content self.status = 200 def raise_for_status(self): return def mocked_response_from_file(filename): module_dir = os.path.dirname(__file__) filepath = os.path.join(module_dir, filename) with open(filepath, "r") as response: return MockedResponse(response.read()) if args[0] == "https://example.org/2018": if "params" not in kwargs or not kwargs["params"]: return mocked_response_from_file("tests/data/2018/september.html") elif kwargs["params"].get("Date") == "20181001": return mocked_response_from_file("tests/data/2018/october.html") else: return mocked_response_from_file("tests/data/2018/empty.html") return MockedResponse("") class CourseTestCase(TestCase): def setUp(self): dt = tz_now() self.year = Year(name="L2", slug="l2") self.year.save() source = Source(url="http://example.org/") source.save() self.timetable = Timetable(year=self.year, name="Test timetable 2", source=source, slug="test-timetable2") self.timetable.save() cma = Group.objects.create(celcat_name="L1 info s2 CMA", source=source) tda2 = Group.objects.create(celcat_name="L1 info s2 TDA2", source=source) self.tpa21 = Group.objects.create(celcat_name="L1 info s2 TPA21", source=source) cmb = Group.objects.create(celcat_name="L1 info s2 CMB", source=source) tdb2 = Group.objects.create(celcat_name="L1 info s2 TDB2", source=source) self.tpb21 = Group.objects.create(celcat_name="L1 info s2 TPB21", source=source) for group in (cma, tda2, self.tpa21, cmb, tdb2, self.tpb21,): course = Course.objects.create( name="{0} course".format(group.name), type="cours", source=source, begin=dt, end=dt, celcat_id=0) course.groups.add(group) def test_get_courses_for_group(self): tpa21_courses = Course.objects.get_courses(self.tpa21) tpb21_courses = Course.objects.get_courses(self.tpb21) tpa21_course_names = ["L1 info s2 CMA course", "L1 info s2 TDA2 course", "L1 info s2 TPA21 course"] tpb21_course_names = ["L1 info s2 CMB course", "L1 info s2 TDB2 course", "L1 info s2 TPB21 course"] for courses, names in ((tpa21_courses, tpa21_course_names,), (tpb21_courses, tpb21_course_names,),): for course in courses: self.assertIn(course.name, names) names.remove(course.name) class GroupTestCase(TestCase): def setUp(self): self.year = Year(name="L1", slug="l1") self.year.save() self.source = Source(url="http://example.org/") self.source.save() self.timetable = Timetable(year=self.year, name="Test timetable", source=self.source, slug="test-timetable") self.timetable.save() Group.objects.create(celcat_name="L1 info s2 CMA", source=self.source) Group.objects.create(celcat_name="L1 info s2 TDA2", source=self.source) Group.objects.create(celcat_name="L1 info s2 TPA21", source=self.source) Group.objects.create(celcat_name="L1 info s2 CMB", source=self.source) Group.objects.create(celcat_name="L1 info s2 TDB2", source=self.source) Group.objects.create(celcat_name="L1 info s2 TPB21", source=self.source) Group.objects.create(celcat_name="L1 info (toutes sections et " "semestres confondus)", source=self.source) # Cas spéciaux de groupes sans semestre. Normalement un groupe # sans semestre ne possède pas de sous-groupe non plus, mais # certains cas font foirer la regex. Voici un exemple trouvé # dans la base de données de production. Group.objects.create(celcat_name="M1 GC (toutes sections et semestres " "confondus)", source=self.source) # Doit appartenir au groupe au-dessus. Group.objects.create(celcat_name="M1 GC s2 GA111", source=self.source) # Cas spécial avec les parenthèses Group.objects.create(celcat_name="M1 CHI-TCCM (EM) (toutes sections et" " semestres confondus)", source=self.source) Group.objects.create(celcat_name="M1 CHI-TCCM (EM) s2 TPA12", source=self.source) # Cas spécial avec un nombre supplémentaire dans le nom de groupe Group.objects.create(celcat_name="L1 4L s1 CM4L", source=self.source) Group.objects.create(celcat_name="L1 4L s1 TD4L1", source=self.source) Group.objects.create(celcat_name="L1 4L s1 TP4L12", source=self.source) def test_corresponds(self): cma = Group.objects.get(celcat_name="L1 info s2 CMA", source=self.source) tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2", source=self.source) tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21", source=self.source) cmb = Group.objects.get(celcat_name="L1 info s2 CMB", source=self.source) tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", source=self.source) tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", source=self.source) general = Group.objects.get(celcat_name="L1 info (toutes sections et " "semestres confondus)", source=self.source) self.assertFalse(cma.corresponds_to(*tda2.group_info)) self.assertFalse(cma.corresponds_to(*tpa21.group_info)) self.assertFalse(tda2.corresponds_to(*tpa21.group_info)) self.assertFalse(cmb.corresponds_to(*tdb2.group_info)) self.assertFalse(cmb.corresponds_to(*tpb21.group_info)) self.assertFalse(tdb2.corresponds_to(*tpb21.group_info)) self.assertFalse(cmb.corresponds_to(*tda2.group_info)) self.assertFalse(cmb.corresponds_to(*tpa21.group_info)) self.assertFalse(tdb2.corresponds_to(*tpa21.group_info)) self.assertTrue(tda2.corresponds_to(*cma.group_info)) self.assertTrue(tpa21.corresponds_to(*cma.group_info)) self.assertTrue(tpa21.corresponds_to(*tda2.group_info)) self.assertTrue(tdb2.corresponds_to(*cmb.group_info)) self.assertTrue(tpb21.corresponds_to(*cmb.group_info)) self.assertTrue(tpb21.corresponds_to(*tdb2.group_info)) self.assertFalse(tda2.corresponds_to(*cmb.group_info)) self.assertFalse(tpa21.corresponds_to(*cmb.group_info)) self.assertFalse(tpa21.corresponds_to(*tdb2.group_info)) self.assertFalse(general.corresponds_to(*cma.group_info)) self.assertFalse(general.corresponds_to(*cmb.group_info)) self.assertFalse(general.corresponds_to(*tda2.group_info)) self.assertFalse(general.corresponds_to(*tdb2.group_info)) self.assertFalse(general.corresponds_to(*tpa21.group_info)) self.assertFalse(general.corresponds_to(*tpb21.group_info)) self.assertTrue(cma.corresponds_to(*general.group_info)) self.assertTrue(cmb.corresponds_to(*general.group_info)) self.assertTrue(tda2.corresponds_to(*general.group_info)) self.assertTrue(tdb2.corresponds_to(*general.group_info)) self.assertTrue(tpa21.corresponds_to(*general.group_info)) self.assertTrue(tpb21.corresponds_to(*general.group_info)) def test_corresponds_no_semester(self): general = Group.objects.get(celcat_name="M1 GC (toutes sections et " "semestres confondus)", source=self.source) ga111 = Group.objects.get(celcat_name="M1 GC s2 GA111", source=self.source) self.assertTrue(ga111.corresponds_to(*general.group_info)) self.assertFalse(general.corresponds_to(*ga111.group_info)) def test_corresponds_number(self): cm4l = Group.objects.get(celcat_name="L1 4L s1 CM4L", source=self.source) td4l1 = Group.objects.get(celcat_name="L1 4L s1 TD4L1", source=self.source) tp4l12 = Group.objects.get(celcat_name="L1 4L s1 TP4L12", source=self.source) self.assertFalse(cm4l.corresponds_to(*td4l1.group_info)) self.assertFalse(cm4l.corresponds_to(*tp4l12.group_info)) self.assertFalse(td4l1.corresponds_to(*tp4l12.group_info)) self.assertTrue(td4l1.corresponds_to(*cm4l.group_info)) self.assertTrue(tp4l12.corresponds_to(*cm4l.group_info)) self.assertTrue(tp4l12.corresponds_to(*td4l1.group_info)) def test_correspond_parenthesis(self): general = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) (toutes" " sections et semestres confondus)") a12 = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) s2 TPA12") self.assertTrue(a12.corresponds_to(*general.group_info)) self.assertFalse(general.corresponds_to(*a12.group_info)) def test_get(self): cma = Group.objects.get(name="L1 info s2 CMA", source=self.source) tda2 = Group.objects.get(name="L1 info s2 TDA2", source=self.source) tpa21 = Group.objects.get(name="L1 info s2 TPA21", source=self.source) cmb = Group.objects.get(name="L1 info s2 CMB", source=self.source) tdb2 = Group.objects.get(name="L1 info s2 TDB2", source=self.source) tpb21 = Group.objects.get(name="L1 info s2 TPB21", source=self.source) general = Group.objects.get(celcat_name="L1 info (toutes sections et " "semestres confondus)", source=self.source) self.assertEqual(cma.celcat_name, "L1 info s2 CMA") self.assertEqual(tda2.celcat_name, "L1 info s2 TDA2") self.assertEqual(tpa21.celcat_name, "L1 info s2 TPA21") self.assertEqual(cmb.celcat_name, "L1 info s2 CMB") self.assertEqual(tdb2.celcat_name, "L1 info s2 TDB2") self.assertEqual(tpb21.celcat_name, "L1 info s2 TPB21") self.assertEqual(general.celcat_name, "L1 info (toutes sections et " "semestres confondus)") def test_parse(self): cma = Group.objects.get(celcat_name="L1 info s2 CMA", source=self.source) tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2", source=self.source) tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21", source=self.source) cmb = Group.objects.get(celcat_name="L1 info s2 CMB", source=self.source) tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", source=self.source) tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", source=self.source) general = Group.objects.get(celcat_name="L1 info (toutes sections et " "semestres confondus)", source=self.source) self.assertEqual(cma.group_info, ("L1 info", 2, "A")) self.assertEqual(tda2.group_info, ("L1 info", 2, "A2")) self.assertEqual(tpa21.group_info, ("L1 info", 2, "A21")) self.assertEqual(cmb.group_info, ("L1 info", 2, "B")) self.assertEqual(tdb2.group_info, ("L1 info", 2, "B2")) self.assertEqual(tpb21.group_info, ("L1 info", 2, "B21")) self.assertEqual(general.group_info, ("L1 info", None, "")) def test_parse_no_semester(self): general = Group.objects.get(celcat_name="M1 GC (toutes sections et " "semestres confondus)", source=self.source) ga111 = Group.objects.get(celcat_name="M1 GC s2 GA111", source=self.source) self.assertEqual(general.group_info, ("M1 GC", None, "")) self.assertEqual(ga111.group_info, ("M1 GC", 2, "A111")) def test_parse_number(self): cm4l = Group.objects.get(celcat_name="L1 4L s1 CM4L", source=self.source) td4l1 = Group.objects.get(celcat_name="L1 4L s1 TD4L1", source=self.source) tp4l12 = Group.objects.get(celcat_name="L1 4L s1 TP4L12", source=self.source) self.assertEqual(cm4l.group_info, ("L1 4L", 1, "4L")) self.assertEqual(td4l1.group_info, ("L1 4L", 1, "4L1")) self.assertEqual(tp4l12.group_info, ("L1 4L", 1, "4L12")) def test_parse_parenthesis(self): general = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) (toutes" " sections et semestres confondus)") a12 = Group.objects.get(celcat_name="M1 CHI-TCCM (EM) s2 TPA12") self.assertEqual(general.group_info, ("M1 CHI-TCCM (EM)", None, "")) self.assertEqual(a12.group_info, ("M1 CHI-TCCM (EM)", 2, "A12")) class RoomTestCase(TestCase): def setUp(self): self.day = datetime.datetime(year=2018, month=1, day=27) self.year = Year.objects.create(name="L1") self.source = Source.objects.create(url="http://example.org/") # Pas besoin de créer plus de groupes que ça, ni de le rendre # global group = Group.objects.create(celcat_name="L1 info s2 CMA", source=self.source) self.rooms = [ Room.objects.create(name=str(i)) for i in range(5) ] + [ Room.objects.create(name="Amphi {}".format(i)) for i in range(5, 7) ] # On n’insère pas ces salles dans la base de données, elles ne # servent que pour le test de formatage. self.formatted_rooms = [ Room(name="FSI / {}".format(str(i))) for i in range(5) ] + [ Room(name="FSI / Amphi {}".format(str(i))) for i in range(5, 7) ] hours = [({"begin": datetime.time(hour=14, minute=0)},), ({"begin": datetime.time(hour=16, minute=0)},), ({"begin": datetime.time(hour=13, minute=30)}, {"begin": datetime.time(hour=16, minute=0)}), ({"begin": datetime.time(hour=14, minute=0), "duration": 4},), ({"begin": datetime.time(hour=15, minute=30), "duration": 1},), ({"begin": datetime.time(hour=13, minute=0)}, {"begin": datetime.time(hour=17, minute=0)}), ()] for i, room in enumerate(self.rooms): for rn in hours[i]: begin = timezone.make_aware( datetime.datetime.combine(self.day, rn["begin"])) end = begin + datetime.timedelta(hours=rn.get("duration", 2)) course = Course.objects.create(source=self.source, begin=begin, end=end, celcat_id=0) course.groups.add(group) course.rooms.add(room) def __test_format(self, rooms, amphis): self.assertEqual(format_rooms([]), "") self.assertEqual(format_rooms(rooms[:1]), "Salle 0") self.assertEqual(format_rooms(rooms[:2]), "Salles 0, 1") self.assertEqual(format_rooms([amphis[0]]), "Amphi 5") self.assertEqual(format_rooms(amphis), "Amphi 5, Amphi 6") self.assertEqual(format_rooms([amphis[0]] + rooms[:1]), "Amphi 5, salle 0") self.assertEqual(format_rooms([amphis[0]] + rooms[:2]), "Amphi 5, salles 0, 1") self.assertEqual(format_rooms(amphis + rooms[:1]), "Amphi 5, Amphi 6, salle 0") self.assertEqual(format_rooms(amphis + rooms[:2]), "Amphi 5, Amphi 6, salles 0, 1") def test_format(self): amphis = self.rooms[-2:] self.__test_format(self.rooms, amphis) def test_reformat(self): amphis = self.formatted_rooms[-2:] self.__test_format(self.formatted_rooms, amphis) def test_qsjps(self): begin = timezone.make_aware(datetime.datetime.combine( self.day, datetime.time(hour=15, minute=0))) end = begin + datetime.timedelta(hours=2) rooms = Room.objects.qsjps(begin, end) self.assertEqual(rooms.count(), 2) self.assertNotIn(self.rooms[0], rooms) self.assertNotIn(self.rooms[1], rooms) self.assertNotIn(self.rooms[2], rooms) self.assertNotIn(self.rooms[3], rooms) self.assertNotIn(self.rooms[4], rooms) self.assertIn(self.rooms[5], rooms) self.assertIn(self.rooms[6], rooms) class UPS2018ParserTestCase(TestCase): @mock.patch("requests.get", side_effect=mock_requests_get) def setUp(self, *args, **kwargs): source = Source.objects.create(url="https://example.org/2018") self.room = Room.objects.create(name="Salle quelconque") self.room2 = Room.objects.create(name="Salle quelconque 2") self.group = Group(celcat_name="L3 Info s1 CMA") self.group.source = source self.group.save() self.parser = UPS2018Parser(source) def test_get_event(self): get_event = self.parser._Parser__get_event count = Course.objects.count() module_count = Module.objects.count() event = get_event( {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "(10:00-12:00)
COURS/TD
Cours quelconque;AAA" "
L3 Info s1 CMA;L3 Info s1 TDA2
" "Salle quelconque;Salle quelconque 2
Commentaire", "id": "0", "tag": ["abc", "def", "AAA"]}, timezone.make_aware(datetime.datetime(2018, 9, 21)), timezone.make_aware(datetime.datetime(2018, 9, 1)), timezone.make_aware(datetime.datetime(2018, 10, 1)), 2018, 38) ngroup = Group.objects.filter(name="L3 Info s1 TDA2").first() self.assertIsNotNone(ngroup) self.assertEqual(event.name, "Cours quelconque, AAA") self.assertEqual(event.type, "COURS/TD") self.assertIn(self.group, event.groups.all()) self.assertIn(ngroup, event.groups.all()) self.assertEqual(event.groups.count(), 2) self.assertIn(self.room, event.rooms.all()) self.assertIn(self.room2, event.rooms.all()) self.assertEqual(event.rooms.count(), 2) self.assertEqual(event.notes, "Commentaire") self.assertEqual(event.celcat_id, 0) self.assertEqual(event.begin, timezone.make_aware( datetime.datetime(2018, 9, 21, 10, 0, 0))) self.assertEqual(event.end, timezone.make_aware( datetime.datetime(2018, 9, 21, 12, 0, 0))) self.assertEqual(event.module.name, "AAA") count += 1 module_count += 1 self.assertEqual(count, Course.objects.count()) self.assertEqual(module_count, Module.objects.count()) events = [ { "text": "(10:00-12:00)
COURS/TD
Cours quelconque" "
L3 Info s1 CMA
Salle quelconque", "name": "Cours quelconque", "type": "COURS/TD", "group": self.group, "room": self.room, "id": "1", "tag": ["aaa", "Cours"], "meta": 1 }, { "text": "(10:00-12:00)
COURS/TD
Cours quelconque" "
L3 Info s1 TDA2
Salle quelconque 3", "name": "Cours quelconque", "type": "COURS/TD", "group": ngroup, "notes": "Salle quelconque 3", "id": "2" }, { "text": "(10:00-12:00)
COURS/TD
Cours quelconque" "
L3 Info s1 CMA
Salle quelconque 3
Commentaire", "name": "Cours quelconque", "type": "COURS/TD", "group": self.group, "notes": "Salle quelconque 3\nCommentaire", "id": "3" }, { "text": "(10:00-12:00)
COURS/TD" "
L3 Info s1 CMA
Salle quelconque 3", "name": "COURS/TD", "group": self.group, "notes": "Salle quelconque 3", "id": "4" }, { "text": "COURS/TD
L3 Info s1 CMA
Salle quelconque 3", "name": "COURS/TD", "group": self.group, "notes": "Salle quelconque 3", "id": "5" }, { "text": "L3 Info s1 CMA
Salle quelconque", "name": "Sans nom", "group": self.group, "room": self.room, "id": "6" }, { "text": "L3 Info s1 CMA
Salle quelconque 3", "name": "Sans nom", "group": self.group, "notes": "Salle quelconque 3", "id": "7" }, { "text": "(10:00-12:00)
L3 Info s1 CMA
Salle quelconque", "name": "Sans nom", "group": self.group, "room": self.room, "id": "8" } ] for e in events: event = get_event( {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": e["text"], "id": e["id"], "tag": e.get("tag", [])}, timezone.make_aware(datetime.datetime(2018, 9, 21)), timezone.make_aware(datetime.datetime(2018, 9, 1)), timezone.make_aware(datetime.datetime(2018, 10, 1)), 2018, 38) self.assertEqual(event.name, e["name"]) self.assertIn(e["group"], event.groups.all()) self.assertEqual(event.groups.count(), 1) self.assertEqual(str(event.celcat_id), e["id"]) if "type" in e: self.assertEqual(event.type, e["type"]) else: self.assertIsNone(event.type) if "room" in e: self.assertIn(e["room"], event.rooms.all()) self.assertEqual(event.rooms.count(), 1) else: self.assertEqual(event.rooms.count(), 0) if "notes" in e: self.assertEqual(event.notes, e["notes"]) else: self.assertIsNone(event.notes) if "tag" in e: self.assertEqual(event.module.name, e["tag"][e["meta"]]) module_count += 1 else: self.assertIsNone(event.module) count += 1 self.assertEqual(count, Course.objects.count()) self.assertEqual(module_count, Module.objects.count()) event = get_event( {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "Global Event"}, timezone.make_aware(datetime.datetime(2018, 9, 21)), timezone.make_aware(datetime.datetime(2018, 9, 1)), timezone.make_aware(datetime.datetime(2018, 10, 1)), 2018, 38) self.assertIsNone(event) self.assertEqual(count, Course.objects.count()) event = get_event( {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "L3 Info s1 CMA
Salle quelconque 2"}, timezone.make_aware(datetime.datetime(2018, 9, 21)), timezone.make_aware(datetime.datetime(2018, 9, 1)), timezone.make_aware(datetime.datetime(2018, 10, 1)), 2018, 39) self.assertIsNone(event) self.assertEqual(count, Course.objects.count()) @mock.patch("requests.get", side_effect=mock_requests_get) def test_get_events(self, *args, **kwargs): self.parser.get_source() courses = [ {"begin": timezone.make_aware(datetime.datetime(2018, 9, 21, 10, 00, 00)), "end": timezone.make_aware(datetime.datetime(2018, 9, 21, 12, 00, 00)), "id": 0}, {"begin": timezone.make_aware(datetime.datetime(2018, 10, 22, 10, 00, 00)), "end": timezone.make_aware(datetime.datetime(2018, 10, 22, 12, 00, 00)), "id": 2} ] for i, course in enumerate(self.parser.get_events( timezone.make_aware(datetime.datetime(2018, 9, 21)))): self.assertEqual(course.name, "Cours quelconque") self.assertEqual(course.type, "COURS/TD") self.assertIn(self.group, course.groups.all()) self.assertEqual(course.groups.count(), 1) self.assertIn(self.room, course.rooms.all()) self.assertEqual(course.rooms.count(), 1) self.assertIsNone(course.notes) self.assertEqual(course.begin, courses[i]["begin"]) self.assertEqual(course.end, courses[i]["end"]) self.assertEqual(course.celcat_id, courses[i]["id"]) self.assertEqual(course.module, None) self.assertEqual(i, len(courses) - 1) @mock.patch("requests.get", side_effect=mock_requests_get) def test_get_source(self, *args, **kwargs): events = self.parser.get_source() self.assertEquals(events, [ [{ "start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "(10:00-12:00)
COURS/TD
Cours quelconque" "
L3 Info s1 CMA
Salle quelconque", "id": "0", "tag": [], }], [{ "start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "(10:00-12:00)
COURS/TD
Cours quelconque" "
L3 Info s1 CMA
Salle quelconque", "id": "1", "tag": [], }, { "start": "2018-10-22T10:00:00", "end": "2018-10-22T12:00:00", "text": "(10:00-12:00)
COURS/TD
Cours quelconque" "
L3 Info s1 CMA
Salle quelconque", "id": "2", "tag": [], }], [], [], [], [], [], [], [], [], []]) def test_get_update_date(self): # Pas de date de mise à jour dans ce format self.assertIsNone(self.parser.get_update_date()) class UPS2018BrokenSourceTestCase(TestCase): @mock.patch("requests.get") def test_broken_source(self, mock_get): mock_get.return_value = mock_requests_get("") source = Source.objects.create(url="https://example.org/2018") with self.assertRaises(ParserError): UPS2018Parser(source) @mock.patch("requests.get") def test_half_broken_source(self, mock_get): source = Source.objects.create(url="https://example.org/2018") mock_get.side_effect = [ mock_requests_get(""), mock_requests_get(source.url) ] parser = UPS2018Parser(source) self.assertEqual(parser.months, [ "September, 2018", "October, 2018", "November, 2018", "December, 2018", "January, 2019", "February, 2019", "March, 2019", "April, 2019", "May, 2019", "June, 2019", "July, 2019" ])