8 Commits

Author SHA1 Message Date
  Alban Gruin 49856b7398 Merge branch 'ag/tests' into futur 4 months ago
  Alban Gruin 4980d9cb47 tests: test du comportement du parseur lorsque la source est mauvaise 7 months ago
  Alban Gruin 070d613b29 tests: vérification du nombre de cours dans le test du parseur 7 months ago
  Alban Gruin 0bafcacdad tests: ajout de tests pour le parseur UPS2018 8 months ago
  Alban Gruin f94afeb958 Merge branch 'ag/nique-toi-google' into futur 4 months ago
  Alban Gruin 5935f58813 templates: correction d’une faute d’orthographe 4 months ago
  Alban Gruin ef5bcdfb1f templates: ajout d’un message déconseillant l’utilisation de GCalendar 4 months ago
  Alban Gruin f915819b96 templates: ajout d’une liste de logiciels lisant les fichiers ICS 4 months ago

+ 34
- 1
templates/calendars.html View File

@@ -8,7 +8,40 @@
8 8
         Le format ICS (ou iCalendar) permet d’importer un calendrier
9 9
         dans un agenda électronique.<br />
10 10
         <a href="https://fr.wikipedia.org/wiki/ICalendar">En savoir plus</a>
11
-      </p>
11
+      <p>
12
+        Il existe plusieurs logiciels ou services permettant
13
+        d’utiliser ces fichiers :
14
+      <ul>
15
+        <li>Sur Linux et Windows, l’extension Lightning du logiciel
16
+        Thunderbird ;</li>
17
+        <li>Sur Mac et iOS, iCloud ;</li>
18
+        <li>Sur Android, l’application libre ICSDroid peut les
19
+        récupérer périodiquement et les afficher sur l’application
20
+        Agenda de base.
21
+        <a href="https://f-droid.org/fr/packages/at.bitfire.icsdroid/">Elle
22
+        est gratuite sur F-Droid</a> ;</li>
23
+        <li>Sur Web, NextCloud.</li>
24
+      </ul>
25
+      <p>
26
+        <b>N’utilisez pas Google Calendar pour synchroniser un
27
+        calendrier ICS</b>.  Ce service empêche de définir la
28
+        fréquence de synchronisation ou de forcer une mise à jour et
29
+        conserve les événements en cache, même si on supprime le
30
+        calendrier.  À cause de cela, il peut y avoir un délai de un
31
+        jour entre le changement d’une information sur celcatsanitizer
32
+        et sa prise en compte sans aucun recours possible.
33
+        <!-- Le lecteur attentif pourra se demander si il n’y a pas de
34
+conflit d’intérêt entre l’écosystème Android de base, dans lequel
35
+l’application de base (Agenda) ne peut se synchroniser qu’à Google
36
+Calendar à moins d’installer une application tierce (telles que
37
+DAVDroid ou ICSDroid, malheureusement payantes sur le Play Store mais
38
+gratuites sur F-Droid).
39
+
40
+Il pourra aussi se questionner sur la raison du mauvais support des
41
+ICS par ce service - serait-ce une technique pour inciter les
42
+utilisateurs à se servir de Google Calendar en priorité, au détriment
43
+des formats standards et des autres écosystèmes (par exemple, celui
44
+d’Apple), et ainsi attirer plus d’utilisateurs ? -->
12 45
       <ul>
13 46
         <li><a href="{% url "ics" timetable.year.slug timetable.slug group.slug %}">Un seul ICS pour tous les cours</a></li>
14 47
 {% for group in groups %}

+ 1
- 1
templates/mention_list.html View File

@@ -1,6 +1,6 @@
1 1
 {% extends "index.html" %}
2 2
 
3 3
 {% block title %}{{ year }} &ndash; {% endblock %}
4
-{% block pagetitle %}{{ year }} &ndash; Choississez votre mention{% endblock %}
4
+{% block pagetitle %}{{ year }} &ndash; Choisissez votre mention{% endblock %}
5 5
 {% block url %}{% url "groups" year.slug element.slug %}{% endblock %}
6 6
 {% block navigation %}<a href="{% url "index" %}">Retour à la liste des années</a>{% endblock %}

+ 264
- 0
tests.py View File

@@ -13,13 +13,45 @@
13 13
 #    You should have received a copy of the GNU Affero General Public License
14 14
 #    along with celcatsanitizer.  If not, see <http://www.gnu.org/licenses/>.
15 15
 
16
+from unittest import mock
17
+
16 18
 from django.test import TestCase
17 19
 from django.utils import timezone
18 20
 
21
+from .management.parsers.abstractparser import ParserError
22
+from .management.parsers.ups2018 import Parser as UPS2018Parser
19 23
 from .models import Course, Group, Room, Source, Timetable, Year
20 24
 from .utils import tz_now
21 25
 
22 26
 import datetime
27
+import os
28
+
29
+
30
+def mock_requests_get(*args, **kwargs):
31
+    class MockedResponse:
32
+        def __init__(self, content=""):
33
+            self.encoding = "utf-8"
34
+            self.content = content
35
+            self.status = 200
36
+
37
+        def raise_for_status(self):
38
+            return
39
+
40
+    def mocked_response_from_file(filename):
41
+        module_dir = os.path.dirname(__file__)
42
+        filepath = os.path.join(module_dir, filename)
43
+        with open(filepath, "r") as response:
44
+            return MockedResponse(response.read())
45
+
46
+    if args[0] == "https://example.org/2018":
47
+        if "params" not in kwargs or not kwargs["params"]:
48
+            return mocked_response_from_file("tests/data/2018/september.html")
49
+        elif kwargs["params"].get("Date") == "20181001":
50
+            return mocked_response_from_file("tests/data/2018/october.html")
51
+        else:
52
+            return mocked_response_from_file("tests/data/2018/empty.html")
53
+
54
+    return MockedResponse("<html></html>")
23 55
 
24 56
 
25 57
 class CourseTestCase(TestCase):
@@ -345,3 +377,235 @@ class RoomTestCase(TestCase):
345 377
         self.assertNotIn(self.rooms[4], rooms)
346 378
         self.assertIn(self.rooms[5], rooms)
347 379
         self.assertIn(self.rooms[6], rooms)
380
+
381
+
382
+class UPS2018ParserTestCase(TestCase):
383
+    @mock.patch("requests.get", side_effect=mock_requests_get)
384
+    def setUp(self, *args, **kwargs):
385
+        source = Source.objects.create(url="https://example.org/2018")
386
+        self.room = Room.objects.create(name="Salle quelconque")
387
+        self.room2 = Room.objects.create(name="Salle quelconque 2")
388
+
389
+        self.group = Group(celcat_name="L3 Info s1 CMA")
390
+        self.group.source = source
391
+        self.group.save()
392
+
393
+        self.parser = UPS2018Parser(source)
394
+
395
+    def test_get_event(self):
396
+        get_event = self.parser._Parser__get_event
397
+        count = Course.objects.count()
398
+
399
+        event = get_event(
400
+            {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00",
401
+             "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque;AAA"
402
+             "<br>L3 Info s1 CMA;L3 Info s1 TDA2<br>"
403
+             "Salle quelconque;Salle quelconque 2<br>Commentaire"},
404
+            timezone.make_aware(datetime.datetime(2018, 9, 21)),
405
+            timezone.make_aware(datetime.datetime(2018, 9, 1)),
406
+            timezone.make_aware(datetime.datetime(2018, 10, 1)),
407
+            2018, 38)
408
+
409
+        ngroup = Group.objects.filter(name="L3 Info s1 TDA2").first()
410
+        self.assertIsNotNone(ngroup)
411
+
412
+        self.assertEqual(event.name, "Cours quelconque, AAA")
413
+        self.assertEqual(event.type, "COURS/TD")
414
+        self.assertIn(self.group, event.groups.all())
415
+        self.assertIn(ngroup, event.groups.all())
416
+        self.assertEqual(event.groups.count(), 2)
417
+        self.assertIn(self.room, event.rooms.all())
418
+        self.assertIn(self.room2, event.rooms.all())
419
+        self.assertEqual(event.rooms.count(), 2)
420
+        self.assertEqual(event.notes, "Commentaire")
421
+        self.assertEqual(event.begin, timezone.make_aware(
422
+            datetime.datetime(2018, 9, 21, 10, 0, 0)))
423
+        self.assertEqual(event.end, timezone.make_aware(
424
+            datetime.datetime(2018, 9, 21, 12, 0, 0)))
425
+
426
+        self.assertEqual(count, Course.objects.count() - 1)
427
+        count += 1
428
+
429
+        events = [
430
+            {
431
+                "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque"
432
+                "<br>L3 Info s1 CMA<br>Salle quelconque",
433
+                "name": "Cours quelconque", "type": "COURS/TD",
434
+                "group": self.group,
435
+                "room": self.room,
436
+            },
437
+            {
438
+                "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque"
439
+                "<br>L3 Info s1 TDA2<br>Salle quelconque 3",
440
+                "name": "Cours quelconque",
441
+                "type": "COURS/TD",
442
+                "group": ngroup,
443
+                "notes": "Salle quelconque 3"
444
+            },
445
+            {
446
+                "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque"
447
+                "<br>L3 Info s1 CMA<br>Salle quelconque 3<br>Commentaire",
448
+                "name": "Cours quelconque",
449
+                "type": "COURS/TD",
450
+                "group": self.group,
451
+                "notes": "Salle quelconque 3\nCommentaire",
452
+            },
453
+            {
454
+                "text": "(10:00-12:00)<br>COURS/TD"
455
+                "<br>L3 Info s1 CMA<br>Salle quelconque 3",
456
+                "name": "COURS/TD",
457
+                "group": self.group,
458
+                "notes": "Salle quelconque 3"
459
+            },
460
+            {
461
+                "text": "COURS/TD<br>L3 Info s1 CMA<br>Salle quelconque 3",
462
+                "name": "COURS/TD",
463
+                "group": self.group,
464
+                "notes": "Salle quelconque 3"
465
+            },
466
+            {
467
+                "text": "L3 Info s1 CMA<br>Salle quelconque",
468
+                "name": "Sans nom",
469
+                "group": self.group,
470
+                "room": self.room
471
+            },
472
+            {
473
+                "text": "L3 Info s1 CMA<br>Salle quelconque 3",
474
+                "name": "Sans nom",
475
+                "group": self.group,
476
+                "notes": "Salle quelconque 3"
477
+            },
478
+            {
479
+                "text": "(10:00-12:00)<br>L3 Info s1 CMA<br>Salle quelconque",
480
+                "name": "Sans nom",
481
+                "group": self.group,
482
+                "room": self.room
483
+            }
484
+        ]
485
+
486
+        for e in events:
487
+            event = get_event(
488
+                {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00",
489
+                 "text": e["text"]},
490
+                timezone.make_aware(datetime.datetime(2018, 9, 21)),
491
+                timezone.make_aware(datetime.datetime(2018, 9, 1)),
492
+                timezone.make_aware(datetime.datetime(2018, 10, 1)),
493
+                2018, 38)
494
+
495
+            self.assertEqual(event.name, e["name"])
496
+            self.assertIn(e["group"], event.groups.all())
497
+            self.assertEqual(event.groups.count(), 1)
498
+
499
+            if "type" in e:
500
+                self.assertEqual(event.type, e["type"])
501
+            else:
502
+                self.assertIsNone(event.type)
503
+
504
+            if "room" in e:
505
+                self.assertIn(e["room"], event.rooms.all())
506
+                self.assertEqual(event.rooms.count(), 1)
507
+            else:
508
+                self.assertEqual(event.rooms.count(), 0)
509
+
510
+            if "notes" in e:
511
+                self.assertEqual(event.notes, e["notes"])
512
+            else:
513
+                self.assertIsNone(event.notes)
514
+
515
+            self.assertEqual(count, Course.objects.count() - 1)
516
+            count += 1
517
+
518
+        event = get_event(
519
+            {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00",
520
+             "text": "Global Event"},
521
+            timezone.make_aware(datetime.datetime(2018, 9, 21)),
522
+            timezone.make_aware(datetime.datetime(2018, 9, 1)),
523
+            timezone.make_aware(datetime.datetime(2018, 10, 1)),
524
+            2018, 38)
525
+        self.assertIsNone(event)
526
+        self.assertEqual(count, Course.objects.count())
527
+
528
+        event = get_event(
529
+            {"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00",
530
+             "text": "L3 Info s1 CMA<br>Salle quelconque 2"},
531
+            timezone.make_aware(datetime.datetime(2018, 9, 21)),
532
+            timezone.make_aware(datetime.datetime(2018, 9, 1)),
533
+            timezone.make_aware(datetime.datetime(2018, 10, 1)),
534
+            2018, 39)
535
+        self.assertIsNone(event)
536
+        self.assertEqual(count, Course.objects.count())
537
+
538
+    @mock.patch("requests.get", side_effect=mock_requests_get)
539
+    def test_get_events(self, *args, **kwargs):
540
+        self.parser.get_source()
541
+        courses = [
542
+            {"begin":
543
+             timezone.make_aware(datetime.datetime(2018, 9, 21, 10, 00, 00)),
544
+             "end":
545
+             timezone.make_aware(datetime.datetime(2018, 9, 21, 12, 00, 00))},
546
+            {"begin":
547
+             timezone.make_aware(datetime.datetime(2018, 10, 22, 10, 00, 00)),
548
+             "end":
549
+             timezone.make_aware(datetime.datetime(2018, 10, 22, 12, 00, 00))}
550
+        ]
551
+
552
+        for i, course in enumerate(self.parser.get_events(
553
+                timezone.make_aware(datetime.datetime(2018, 9, 21)))):
554
+            self.assertEqual(course.name, "Cours quelconque")
555
+            self.assertEqual(course.type, "COURS/TD")
556
+            self.assertIn(self.group, course.groups.all())
557
+            self.assertEqual(course.groups.count(), 1)
558
+            self.assertIn(self.room, course.rooms.all())
559
+            self.assertEqual(course.rooms.count(), 1)
560
+            self.assertIsNone(course.notes)
561
+            self.assertEqual(course.begin, courses[i]["begin"])
562
+            self.assertEqual(course.end, courses[i]["end"])
563
+
564
+        self.assertEqual(i, len(courses) - 1)
565
+
566
+    @mock.patch("requests.get", side_effect=mock_requests_get)
567
+    def test_get_source(self, *args, **kwargs):
568
+        events = self.parser.get_source()
569
+        self.assertEquals(events, [
570
+            [{
571
+                "start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00",
572
+                "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque"
573
+                "<br>L3 Info s1 CMA<br>Salle quelconque"
574
+            }], [{
575
+                "start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00",
576
+                "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque"
577
+                "<br>L3 Info s1 CMA<br>Salle quelconque"
578
+            }, {
579
+                "start": "2018-10-22T10:00:00", "end": "2018-10-22T12:00:00",
580
+                "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque"
581
+                "<br>L3 Info s1 CMA<br>Salle quelconque"
582
+            }], [], [], [], [], [], [], [], [], []])
583
+
584
+    def test_get_update_date(self):
585
+        # Pas de date de mise à jour dans ce format
586
+        self.assertIsNone(self.parser.get_update_date())
587
+
588
+
589
+class UPS2018BrokenSourceTestCase(TestCase):
590
+    @mock.patch("requests.get")
591
+    def test_broken_source(self, mock_get):
592
+        mock_get.return_value = mock_requests_get("")
593
+
594
+        source = Source.objects.create(url="https://example.org/2018")
595
+        with self.assertRaises(ParserError):
596
+            UPS2018Parser(source)
597
+
598
+    @mock.patch("requests.get")
599
+    def test_half_broken_source(self, mock_get):
600
+        source = Source.objects.create(url="https://example.org/2018")
601
+        mock_get.side_effect = [
602
+            mock_requests_get(""),
603
+            mock_requests_get(source.url)
604
+        ]
605
+
606
+        parser = UPS2018Parser(source)
607
+        self.assertEqual(parser.months, [
608
+            "September, 2018", "October, 2018", "November, 2018",
609
+            "December, 2018", "January, 2019", "February, 2019", "March, 2019",
610
+            "April, 2019", "May, 2019", "June, 2019", "July, 2019"
611
+        ])

+ 50
- 0
tests/data/2018/empty.html View File

@@ -0,0 +1,50 @@
1
+<script>
2
+function do_something() {
3
+alert("something");
4
+}
5
+</script>
6
+
7
+			<option value="August, 2017">August, 2017</option>
8
+			<option value="September, 2017">September, 2017</option>
9
+			<option value="October, 2017">October, 2017</option>
10
+			<option value="November, 2017">November, 2017</option>
11
+			<option value="December, 2017">December, 2017</option>
12
+			<option value="January, 2018">January, 2018</option>
13
+			<option value="February, 2018">February, 2018</option>
14
+			<option value="March, 2018">March, 2018</option>
15
+			<option value="April, 2018">April, 2018</option>
16
+			<option value="May, 2018">May, 2018</option>
17
+			<option value="June, 2018">June, 2018</option>
18
+			<option value="July, 2018">July, 2018</option>
19
+			<option value="August, 2018">August, 2018</option>
20
+			<option value="September, 2018">September, 2018</option>
21
+			<option value="October, 2018">October, 2018</option>
22
+			<option value="November, 2018">November, 2018</option>
23
+			<option value="December, 2018">December, 2018</option>
24
+			<option value="January, 2019">January, 2019</option>
25
+			<option value="February, 2019">February, 2019</option>
26
+			<option value="March, 2019">March, 2019</option>
27
+			<option value="April, 2019">April, 2019</option>
28
+			<option value="May, 2019">May, 2019</option>
29
+			<option value="June, 2019">June, 2019</option>
30
+			<option value="July, 2019">July, 2019</option>
31
+
32
+<script>
33
+function do_something_else() {
34
+var v = "a variable";
35
+var vv = "another_variable";
36
+do_something();
37
+}
38
+</script>
39
+
40
+<script>
41
+function courses() {
42
+var v = {};
43
+v.events.list = [];;
44
+}
45
+</script>
46
+
47
+<script>
48
+courses();
49
+do_something_else();
50
+</script>

+ 50
- 0
tests/data/2018/october.html View File

@@ -0,0 +1,50 @@
1
+<script>
2
+function do_something() {
3
+alert("something");
4
+}
5
+</script>
6
+
7
+			<option value="August, 2017">August, 2017</option>
8
+			<option value="September, 2017">September, 2017</option>
9
+			<option value="October, 2017">October, 2017</option>
10
+			<option value="November, 2017">November, 2017</option>
11
+			<option value="December, 2017">December, 2017</option>
12
+			<option value="January, 2018">January, 2018</option>
13
+			<option value="February, 2018">February, 2018</option>
14
+			<option value="March, 2018">March, 2018</option>
15
+			<option value="April, 2018">April, 2018</option>
16
+			<option value="May, 2018">May, 2018</option>
17
+			<option value="June, 2018">June, 2018</option>
18
+			<option value="July, 2018">July, 2018</option>
19
+			<option value="August, 2018">August, 2018</option>
20
+			<option value="September, 2018">September, 2018</option>
21
+			<option selected="selected" value="October, 2018">October, 2018</option>
22
+			<option value="November, 2018">November, 2018</option>
23
+			<option value="December, 2018">December, 2018</option>
24
+			<option value="January, 2019">January, 2019</option>
25
+			<option value="February, 2019">February, 2019</option>
26
+			<option value="March, 2019">March, 2019</option>
27
+			<option value="April, 2019">April, 2019</option>
28
+			<option value="May, 2019">May, 2019</option>
29
+			<option value="June, 2019">June, 2019</option>
30
+			<option value="July, 2019">July, 2019</option>
31
+
32
+<script>
33
+function do_something_else() {
34
+var v = "a variable";
35
+var vv = "another_variable";
36
+do_something();
37
+}
38
+</script>
39
+
40
+<script>
41
+function courses() {
42
+var v = {};
43
+v.events.list = [{"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque<br>L3 Info s1 CMA<br>Salle quelconque"}, {"start": "2018-10-22T10:00:00", "end": "2018-10-22T12:00:00", "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque<br>L3 Info s1 CMA<br>Salle quelconque"}];;
44
+}
45
+</script>
46
+
47
+<script>
48
+courses();
49
+do_something_else();
50
+</script>

+ 51
- 0
tests/data/2018/september.html View File

@@ -0,0 +1,51 @@
1
+<script>
2
+function do_something() {
3
+alert("something");
4
+}
5
+</script>
6
+
7
+			<option value="August, 2017">August, 2017</option>
8
+			<option value="September, 2017">September, 2017</option>
9
+			<option value="October, 2017">October, 2017</option>
10
+			<option value="November, 2017">November, 2017</option>
11
+			<option value="December, 2017">December, 2017</option>
12
+			<option value="January, 2018">January, 2018</option>
13
+			<option value="February, 2018">February, 2018</option>
14
+			<option value="March, 2018">March, 2018</option>
15
+			<option value="April, 2018">April, 2018</option>
16
+			<option value="May, 2018">May, 2018</option>
17
+			<option value="June, 2018">June, 2018</option>
18
+			<option value="July, 2018">July, 2018</option>
19
+			<option value="August, 2018">August, 2018</option>
20
+			<option selected="selected" value="September, 2018">September, 2018</option>
21
+			<option value="October, 2018">October, 2018</option>
22
+			<option value="November, 2018">November, 2018</option>
23
+			<option value="December, 2018">December, 2018</option>
24
+			<option value="January, 2019">January, 2019</option>
25
+			<option value="February, 2019">February, 2019</option>
26
+			<option value="March, 2019">March, 2019</option>
27
+			<option value="April, 2019">April, 2019</option>
28
+			<option value="May, 2019">May, 2019</option>
29
+			<option value="June, 2019">June, 2019</option>
30
+			<option value="July, 2019">July, 2019</option>
31
+
32
+<script>
33
+  function do_something_else() {
34
+      var v = "a variable";
35
+      var vv = "another_variable";
36
+
37
+      do_something();
38
+  }
39
+</script>
40
+
41
+<script>
42
+function courses() {
43
+var v = {};
44
+v.events.list = [{"start": "2018-09-21T10:00:00", "end": "2018-09-21T12:00:00", "text": "(10:00-12:00)<br>COURS/TD<br>Cours quelconque<br>L3 Info s1 CMA<br>Salle quelconque"}];;
45
+}
46
+</script>
47
+
48
+<script>
49
+courses();
50
+do_something_else();
51
+</script>