diff --git a/.coverage b/.coverage index a544a4fb6..9be488fc3 100644 Binary files a/.coverage and b/.coverage differ diff --git a/.gitignore b/.gitignore index 8aac9a272..259751217 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ uploads /data/ cypress/screenshots /package-lock.json +/frontend/package-lock.json +/frontend/frontend/package-lock.json +package-lock.json diff --git a/api/middleware.py b/api/middleware.py index 9bb4e33ca..5c1cd2074 100644 --- a/api/middleware.py +++ b/api/middleware.py @@ -85,7 +85,6 @@ def __call__(self, request): class DisableCSRFMiddleware(object): - def __init__(self, get_response): self.get_response = get_response diff --git a/api/models/indiening.py b/api/models/indiening.py index 9abd6d821..5190e4f2b 100644 --- a/api/models/indiening.py +++ b/api/models/indiening.py @@ -72,10 +72,6 @@ def __str__(self): return str(self.indiening_id) def save(self, *args, **kwargs): - # First save to generate the indiening_id if it doesn't exist - if not self.indiening_id: - super(Indiening, self).save(*args, **kwargs) - # Update the bestand path if it's still using the temporary path if "temp" in self.bestand.name: old_file = self.bestand diff --git a/api/models/project.py b/api/models/project.py index 3e72e193b..6cf3b282b 100644 --- a/api/models/project.py +++ b/api/models/project.py @@ -55,8 +55,8 @@ class Project(models.Model): vak = models.ForeignKey(Vak, on_delete=models.CASCADE) deadline = models.DateTimeField(null=True, blank=True) extra_deadline = models.DateTimeField(null=True, blank=True) - max_score = models.IntegerField(default=20) - max_groep_grootte = models.IntegerField(default=1) + max_score = models.PositiveSmallIntegerField(default=20) + max_groep_grootte = models.PositiveSmallIntegerField(default=1) student_groep = models.BooleanField(default=False, blank=True) zichtbaar = models.BooleanField(default=True, blank=True) gearchiveerd = models.BooleanField(default=False, blank=True) diff --git a/api/models/score.py b/api/models/score.py index f94565b91..c296e02fc 100644 --- a/api/models/score.py +++ b/api/models/score.py @@ -19,7 +19,7 @@ class Score(models.Model): """ score_id = models.AutoField(primary_key=True) - score = models.SmallIntegerField() + score = models.PositiveSmallIntegerField() indiening = models.ForeignKey("Indiening", on_delete=models.CASCADE) def __str__(self): diff --git a/api/models/vak.py b/api/models/vak.py index fd78219c8..48f0124bb 100644 --- a/api/models/vak.py +++ b/api/models/vak.py @@ -22,7 +22,7 @@ class Vak(models.Model): vak_id = models.AutoField(primary_key=True) naam = models.CharField(max_length=100) - jaartal = models.IntegerField(default=date.today().year) + jaartal = models.PositiveSmallIntegerField(default=date.today().year) gearchiveerd = models.BooleanField(default=False, blank=True) studenten = models.ManyToManyField( "Gebruiker", related_name="vak_gebruikers", blank=True diff --git a/api/tests/factories/indiening.py b/api/tests/factories/indiening.py index 35563f489..879995690 100644 --- a/api/tests/factories/indiening.py +++ b/api/tests/factories/indiening.py @@ -1,5 +1,5 @@ import factory -from api.models.indiening import Indiening, IndieningBestand +from api.models.indiening import Indiening from factory.django import DjangoModelFactory from factory import SubFactory from .groep import GroepFactory @@ -23,16 +23,5 @@ class Meta: ) status = factory.Faker("boolean") result = factory.Faker("paragraph") - artefacten = None - - indiening_bestanden = factory.RelatedFactory( - "api.tests.factories.indiening.IndieningBestandFactory", "indiening" - ) - - -class IndieningBestandFactory(DjangoModelFactory): - class Meta: - model = IndieningBestand - - indiening = SubFactory(IndieningFactory) bestand = FileField(filename="test.txt", data=b"file content") + artefacten = None diff --git a/api/tests/factories/project.py b/api/tests/factories/project.py index ec5e56923..f561c8d8d 100644 --- a/api/tests/factories/project.py +++ b/api/tests/factories/project.py @@ -29,5 +29,6 @@ class Meta: ) max_score = factory.Faker("random_int", min=10, max=100) max_groep_grootte = factory.Faker("random_int", min=1, max=5) + student_groep = factory.Faker("boolean") zichtbaar = factory.Faker("boolean") gearchiveerd = factory.Faker("boolean") diff --git a/api/tests/factories/score.py b/api/tests/factories/score.py index 844bf18d4..97ff855fb 100644 --- a/api/tests/factories/score.py +++ b/api/tests/factories/score.py @@ -14,6 +14,5 @@ class Meta: @classmethod def _create(cls, model_class, *args, **kwargs): indiening = kwargs.pop("indiening") - max_score = indiening.groep.project.max_score - kwargs["score"] = random.randint(0, max_score) + kwargs["score"] = random.randint(0, indiening.groep.project.max_score) return super()._create(model_class, indiening=indiening, *args, **kwargs) diff --git a/api/tests/factories/template.py b/api/tests/factories/template.py new file mode 100644 index 000000000..1b52c7f8b --- /dev/null +++ b/api/tests/factories/template.py @@ -0,0 +1,12 @@ +import factory +from factory.django import FileField +from api.models.template import Template +from api.tests.factories.gebruiker import UserFactory + + +class TemplateFactory(factory.django.DjangoModelFactory): + class Meta: + model = Template + + user = factory.SubFactory(UserFactory) + bestand = FileField(filename="template.txt", data=b"file content") diff --git a/api/tests/models/test_indiening.py b/api/tests/models/test_indiening.py index e0f065d06..cc579b813 100644 --- a/api/tests/models/test_indiening.py +++ b/api/tests/models/test_indiening.py @@ -1,11 +1,16 @@ from django.test import TestCase -from django.core.files.uploadedfile import SimpleUploadedFile -from api.tests.factories.indiening import IndieningFactory, IndieningBestandFactory +from api.tests.factories.indiening import IndieningFactory from api.models.indiening import upload_to +from unittest.mock import patch, MagicMock, call +from api.models.indiening import send_indiening_confirmation_mail +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +import os class IndieningModelTest(TestCase): - def setUp(self): + @patch("api.models.indiening.send_indiening_confirmation_mail") + def setUp(self, mock_send_mail): self.indiening = IndieningFactory.create() def test_str_method(self): @@ -20,9 +25,6 @@ def test_tijdstip(self): def test_status(self): self.assertIsNotNone(self.indiening.status) - def test_indiening_bestanden(self): - self.assertEqual(self.indiening.indiening_bestanden.count(), 1) - def test_upload_to(self): filename = "test_indiening.txt" expected_path = ( @@ -30,20 +32,116 @@ def test_upload_to(self): ) self.assertEqual(upload_to(self.indiening, filename), expected_path) + def test_artefacten(self): + self.assertIsNotNone(self.indiening.artefacten) + + def test_result(self): + self.assertIsNotNone(self.indiening.result) + + def test_bestand(self): + self.assertIsNotNone(self.indiening.bestand) + + @patch("smtplib.SMTP_SSL") + @patch("ssl.create_default_context") + def test_send_indiening_confirmation_mail( + self, mock_create_default_context, mock_smtp + ): + indiening = self.indiening + project = indiening.groep.project + + smtp_server_address = "smtp.gmail.com" + smtp_port = 465 + mail_username = os.environ.get("MAIL_USERNAME") + mail_app_password = os.environ.get("MAIL_APP_PASSWORD") + indiening_status = { + -1: "heeft niet alle testen geslaagd.", + 0: "wordt nog getest...", + 1: "heeft alle testen geslaagd!", + } + project_url = f"https://sel2-4.ugent.be/course/{project.vak.vak_id}/assignment/{project.project_id}" + indiening_url = f"https://sel2-4.ugent.be/course/{project.vak.vak_id}/assignment/ \ + {project.project_id}/submission/{indiening.indiening_id}" + + # Setup the mock SMTP_SSL object + mock_smtp_instance = MagicMock() + mock_smtp.return_value.__enter__.return_value = mock_smtp_instance + + # Call the function + send_indiening_confirmation_mail(indiening) + + # Check that create_default_context was called once + assert mock_create_default_context.call_count == 1 -class IndieningBestandModelTest(TestCase): - def setUp(self): - self.indiening_bestand = IndieningBestandFactory.create( - bestand=SimpleUploadedFile("file.txt", b"file_content") + # Check that SMTP_SSL was called with the correct arguments + assert mock_smtp.call_count == indiening.groep.studenten.count() + mock_smtp.assert_has_calls( + [ + call( + smtp_server_address, + smtp_port, + context=mock_create_default_context.return_value, + ) + ] + * indiening.groep.studenten.count() ) - def test_str_method(self): - self.assertEqual( - str(self.indiening_bestand), str(self.indiening_bestand.bestand.name) + # Check that login was called with the correct arguments + mock_smtp_instance.login.assert_called_with(mail_username, mail_app_password) + + # Check that sendmail was called once for each student + assert ( + mock_smtp_instance.sendmail.call_count == indiening.groep.studenten.count() ) - def test_indiening(self): - self.assertIsNotNone(self.indiening_bestand.indiening) + # Check that sendmail was called with the correct arguments + for student in indiening.groep.studenten.all(): + subject = "Indieningsontvangst" - def test_bestand(self): - self.assertIsNotNone(self.indiening_bestand.bestand) + email = MIMEMultipart("alternative") + email["Subject"] = subject + email["From"] = mail_username + email["To"] = student.user.email + + plain_text = f""" + Beste {student.user.first_name} {student.user.last_name}, + + Dit is een bevestiging dat uw indiening voor het project {project.titel} is ontvangen. + De indiening {indiening_status[indiening.status]}. + """ + + html_text = f""" + +
Beste {student.user.first_name} {student.user.last_name}
Dit is een bevestiging dat uw indiening voor het project \ + {project.titel} is ontvangen.
De indiening {indiening_status[indiening.status]}