From 4904e2527945e7c4792acc3d6863ca7c758e42a4 Mon Sep 17 00:00:00 2001 From: kyomi Date: Sun, 26 Nov 2023 20:15:46 -0300 Subject: [PATCH 1/9] feat(apps/submissions): add tasks test cases feature --- apps/submissions/admin.py | 4 +- .../migrations/0003_submission_status.py | 34 ++++++++++++ apps/submissions/models.py | 25 ++++++++- apps/tasks/admin.py | 34 ++++++++++-- .../0004_task_input_file_task_output_file.py | 22 ++++++++ apps/tasks/models.py | 11 +++- apps/tasks/views.py | 53 +++++++++++++++---- docker-compose.yml | 2 + in.txt | 1 + out.txt | 1 + 10 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 apps/submissions/migrations/0003_submission_status.py create mode 100644 apps/tasks/migrations/0004_task_input_file_task_output_file.py create mode 100644 in.txt create mode 100644 out.txt diff --git a/apps/submissions/admin.py b/apps/submissions/admin.py index 19d5b95..49cf45f 100644 --- a/apps/submissions/admin.py +++ b/apps/submissions/admin.py @@ -29,4 +29,6 @@ class SubmissionAdmin(SubmissionAdminBase): list_display = ("__str__", "author", "task") list_filter = ("author", "task", "created_at") - fieldsets = [(_("Details"), {"fields": ("author", "task", "code")})] + fieldsets = [ + (_("Details"), {"fields": ("author", "task", "code", "status")}) + ] diff --git a/apps/submissions/migrations/0003_submission_status.py b/apps/submissions/migrations/0003_submission_status.py new file mode 100644 index 0000000..323ad92 --- /dev/null +++ b/apps/submissions/migrations/0003_submission_status.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.7 on 2023-11-26 22:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("submissions", "0002_alter_submission_code"), + ] + + operations = [ + migrations.AddField( + model_name="submission", + name="status", + field=models.TextField( + choices=[ + ("WJ", "Waiting judge"), + ("JG", "Judging"), + ("AC", "Accepted"), + ("WA", "Wrong answer"), + ("RE", "Runtime error"), + ("TLE", "Time limit exceeded"), + ("MLE", "Memory limit exceeded"), + ("CE", "Compilation error"), + ("IE", "Internal error"), + ("UE", "Unknown error"), + ("SE", "Submission error"), + ("PE", "Presentation error"), + ], + default="WJ", + max_length=3, + ), + ), + ] diff --git a/apps/submissions/models.py b/apps/submissions/models.py index 6aa8517..75c4b9a 100644 --- a/apps/submissions/models.py +++ b/apps/submissions/models.py @@ -1,11 +1,29 @@ from django.core.validators import MinLengthValidator -from django.db.models import CASCADE, ForeignKey, TextField +from django.db.models import CASCADE, ForeignKey, TextChoices, TextField +from django.utils.translation import gettext_lazy as _ from apps.tasks.models import Task from apps.users.models import User from core.models import TimestampedModel +class SubmissionStatus(TextChoices): + """Represents the status of a submission.""" + + WAITING_JUDGE = ("WJ", _("Waiting judge")) + JUDGING = ("JG", _("Judging")) + ACCEPTED = ("AC", _("Accepted")) + WRONG_ANSWER = ("WA", _("Wrong answer")) + RUNTIME_ERROR = ("RE", _("Runtime error")) + TIME_LIMIT_EXCEEDED = ("TLE", _("Time limit exceeded")) + MEMORY_LIMIT_EXCEEDED = ("MLE", _("Memory limit exceeded")) + COMPILATION_ERROR = ("CE", _("Compilation error")) + INTERNAL_ERROR = ("IE", _("Internal error")) + UNKNOWN_ERROR = ("UE", _("Unknown error")) + SUBMISSION_ERROR = ("SE", _("Submission error")) + PRESENTATION_ERROR = ("PE", _("Presentation error")) + + class Submission(TimestampedModel): """ Represents a submission to a task by an user. The code field is @@ -16,6 +34,11 @@ class Submission(TimestampedModel): author = ForeignKey(User, related_name="submissions", on_delete=CASCADE) task = ForeignKey(Task, related_name="submissions", on_delete=CASCADE) code = TextField(validators=[MinLengthValidator(15)]) + status = TextField( + max_length=3, + choices=SubmissionStatus.choices, + default=SubmissionStatus.WAITING_JUDGE, + ) class Meta: db_table = "submissions" diff --git a/apps/tasks/admin.py b/apps/tasks/admin.py index 7ceebb7..470982d 100644 --- a/apps/tasks/admin.py +++ b/apps/tasks/admin.py @@ -1,7 +1,10 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast from django.contrib.admin import ModelAdmin, register +from django.core.files.uploadedfile import InMemoryUploadedFile from django.forms import CharField, IntegerField, ModelForm, Textarea +from django.forms.fields import FileField +from django.http import HttpRequest from django.utils.translation import gettext_lazy as _ from apps.tasks.models import Task @@ -19,12 +22,19 @@ class TaskModelForm(TaskModelFormBase): score = IntegerField(min_value=0, required=False) memory_limit = IntegerField( - min_value=0, required=False, help_text=_("In bytes.") + min_value=0, + required=False, + help_text=_("In bytes."), ) time_limit = IntegerField( - min_value=0, required=False, help_text=_("In seconds.") + min_value=0, + required=False, + help_text=_("In seconds."), ) + input_file = FileField() + output_file = FileField() + class Meta: model = Task fields = "__all__" @@ -41,4 +51,22 @@ class TaskAdmin(TaskAdminBase): (_("General"), {"fields": ("title", "description")}), (_("Meta"), {"fields": ("contest", "score")}), (_("Limits"), {"fields": ("memory_limit", "time_limit")}), + (_("Test case"), {"fields": ("input_file", "output_file")}), ] + + def save_model( + self, + request: HttpRequest, + obj: Task, + form: TaskModelForm, + change: bool, + ) -> None: + # request.FILES does not cast to the correct type so we need to + # cast it manually, otherwise Mypy will complain. + input_file = cast(InMemoryUploadedFile, request.FILES["input_file"]) + output_file = cast(InMemoryUploadedFile, request.FILES["output_file"]) + + obj.input_file = input_file.read().decode("utf-8") + obj.output_file = output_file.read().decode("utf-8") + + return super().save_model(request, obj, form, change) diff --git a/apps/tasks/migrations/0004_task_input_file_task_output_file.py b/apps/tasks/migrations/0004_task_input_file_task_output_file.py new file mode 100644 index 0000000..c02435c --- /dev/null +++ b/apps/tasks/migrations/0004_task_input_file_task_output_file.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2023-11-26 21:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tasks", "0003_task_memory_limit_task_score_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="task", + name="input_file", + field=models.TextField(), + ), + migrations.AddField( + model_name="task", + name="output_file", + field=models.TextField(), + ), + ] diff --git a/apps/tasks/models.py b/apps/tasks/models.py index 3443d00..c9d4c84 100644 --- a/apps/tasks/models.py +++ b/apps/tasks/models.py @@ -1,4 +1,10 @@ -from django.db.models import CASCADE, CharField, ForeignKey, IntegerField +from django.db.models import ( + CASCADE, + CharField, + ForeignKey, + IntegerField, + TextField, +) from apps.contests.enums import ContestStatus from apps.contests.models import Contest @@ -17,6 +23,9 @@ class Task(TimestampedModel): memory_limit = IntegerField(null=True) time_limit = IntegerField(null=True) + input_file = TextField() + output_file = TextField() + class Meta: db_table = "tasks" diff --git a/apps/tasks/views.py b/apps/tasks/views.py index 2296b9b..c255f68 100644 --- a/apps/tasks/views.py +++ b/apps/tasks/views.py @@ -1,3 +1,6 @@ +import sys +from io import StringIO +from traceback import format_exc from typing import TYPE_CHECKING, Any, Dict from django.http import HttpRequest, HttpResponse @@ -18,6 +21,44 @@ FormMixinBase = FormMixin +def handle_submission(request: HttpRequest, task: Task) -> HttpResponse: + submission = Submission._default_manager.create( + code=request.POST["code"], + task=task, + author=request.user, + ) + + input_data = StringIO(task.input_file) + + old_stdin = sys.stdin + sys.stdin = input_data + + old_stdout = sys.stdout + sys.stdout = stdout = StringIO() + + try: + eval(compile(request.POST["code"], "", "exec")) + except Exception as exc: + submission.status = "RE" + submission.save() + + return HttpResponse(f"Error: {exc} {format_exc()}") + finally: + sys.stdout = old_stdout + sys.stdin = old_stdin + + output = stdout.getvalue() + + if output == task.output_file: + submission.status = "AC" + submission.save() + return HttpResponse("Correct!") + else: + submission.status = "WA" + submission.save() + return HttpResponse("Incorrect!") + + class DetailView(FormMixinBase, DetailViewBase): model = Task template_name = "tasks/detail.html" @@ -42,13 +83,7 @@ def post(self, request: HttpRequest, *, pk: int) -> HttpResponse: self.object = self.get_object() form = self.get_form() - if form.is_valid(): - Submission._default_manager.create( - code=request.POST, task=self.object, author=request.user - ) + if not form.is_valid(): + return self.form_invalid(form) - return ( - self.form_valid(form) - if form.is_valid() - else self.form_invalid(form) - ) + return handle_submission(request, self.object) diff --git a/docker-compose.yml b/docker-compose.yml index 9986807..d321115 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,8 @@ services: - main env_file: - config/.env + depends_on: + - postgres command: python -Wd manage.py runserver 0.0.0.0:8000 postgres: diff --git a/in.txt b/in.txt new file mode 100644 index 0000000..2deca7f --- /dev/null +++ b/in.txt @@ -0,0 +1 @@ +10 5 diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..60d3b2f --- /dev/null +++ b/out.txt @@ -0,0 +1 @@ +15 From dda86d19d3452759a47121ad10733b80646cf67c Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 27 Nov 2023 21:02:17 -0300 Subject: [PATCH 2/9] fix(apps/tasks/tests): updated oudated tests --- apps/submissions/tests.py | 4 +++- apps/tasks/tests.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/submissions/tests.py b/apps/submissions/tests.py index 4f680f2..fe42958 100644 --- a/apps/submissions/tests.py +++ b/apps/submissions/tests.py @@ -90,5 +90,7 @@ def test_list_filter(self) -> None: self.assertEqual(self.submission_admin.list_filter, expected) def test_fieldsets(self) -> None: - expected = [(_("Details"), {"fields": ("author", "task", "code")})] + expected = [ + (_("Details"), {"fields": ("author", "task", "code", "status")}) + ] self.assertEqual(self.submission_admin.fieldsets, expected) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index ba19a42..564a79b 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -97,10 +97,12 @@ def test_list_filter(self) -> None: def test_fieldsets(self) -> None: fieldsets = self.admin.fieldsets + expected = [ (("General"), {"fields": ("title", "description")}), (("Meta"), {"fields": ("contest", "score")}), (("Limits"), {"fields": ("memory_limit", "time_limit")}), + ("Test case", {"fields": ("input_file", "output_file")}), ] self.assertEqual(fieldsets, expected) @@ -171,8 +173,6 @@ def test_detail_view_form_class_is_submission_form(self) -> None: self.assertEqual(DetailView.form_class, SubmissionForm) def test_send_submission_is_redirecting(self) -> None: - self.client.force_login(self.user) - response = self.client.post(self.url, data={"code": self.code}) self.assertEqual(response.status_code, 302) From 7daa3ee5803f514cd2593e67edaa5eef48c9b3ee Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 27 Nov 2023 21:59:03 -0300 Subject: [PATCH 3/9] test(apps/tasks): added more test, so coverage would not be so low --- apps/tasks/tests.py | 47 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 564a79b..e127f79 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -7,7 +7,7 @@ from apps.contests.models import Contest from apps.submissions.forms import SubmissionForm -from apps.submissions.models import Submission +from apps.submissions.models import Submission, SubmissionStatus from apps.tasks.admin import TaskAdmin from apps.tasks.models import Task from apps.tasks.views import DetailView @@ -149,6 +149,13 @@ def setUp(self) -> None: password="password", ) + self.submission = Submission._default_manager.create( + author=self.user, + task=self.task, + code="print('Hello, World!')", + status=SubmissionStatus.WAITING_JUDGE, + ) + self.url = reverse("tasks:detail", args=[self.task.id]) def test_send_submission_successfully(self) -> None: @@ -179,3 +186,41 @@ def test_send_submission_is_redirecting(self) -> None: def test_send_submission_without_authentication(self) -> None: response = self.client.post(self.url, data={"code": self.code}) self.assertEqual(response.status_code, 302) + + def test_handle_submission_with_exception(self) -> None: + self.client.force_login(self.user) + + # Enviar código que causará uma exceção + response = self.client.post( + self.url, data={"code": "raise Exception('Test exception')"} + ) + self.assertEqual(response.status_code, 200) + self.assertIn("Error: Test exception", response.content.decode()) + + self.assertEqual(self.submission.status, "RE") + + def test_handle_submission_with_correct_output(self) -> None: + self.client.force_login(self.user) + + # Definir a saída esperada para a tarefa + self.task.output_file = "Hello, World!\n" + self.task.save() + + # Enviar código que produzirá a saída correta + response = self.client.post( + self.url, data={"code": "print('Hello, World!')"} + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content.decode(), "Correct!") + + self.assertEqual(self.submission.status, "AC") + + # def test_get_success_url(self) -> None: + # self.client.force_login(self.user) + + # # Enviar código válido + # response = self.client.post( + # self.url, + # data={"code": "print('Hello, World!')"} + # ) + # self.assertEqual(response.status_code, 302) From b29600d11528a7ffbcb607f3c648f363c9c9a6b1 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 27 Nov 2023 22:20:51 -0300 Subject: [PATCH 4/9] fix(apps/tasks): fixed some faulty tests --- apps/tasks/tests.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index ec8ebef..813bae2 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -153,7 +153,7 @@ def setUp(self) -> None: author=self.user, task=self.task, code="print('Hello, World!')", - status=SubmissionStatus.WAITING_JUDGE, + status=SubmissionStatus, ) self.url = reverse("tasks:detail", args=[self.task.id]) @@ -162,13 +162,13 @@ def test_send_submission_successfully(self) -> None: self.client.force_login(self.user) self.client.post(self.url, data={"code": self.code}) - self.assertEqual(Submission._default_manager.count(), 1) + self.assertEqual(Submission._default_manager.count(), 2) def test_send_submission_with_short_code(self) -> None: self.client.force_login(self.user) self.client.post(self.url, data={"code": "c"}) - self.assertEqual(Submission._default_manager.count(), 0) + self.assertEqual(Submission._default_manager.count(), 1) def test_detail_view_model_is_task(self) -> None: self.assertEqual(DetailView.model, Task) @@ -208,6 +208,8 @@ def test_handle_submission_with_exception(self) -> None: self.assertEqual(response.status_code, 200) self.assertIn("Error: Test exception", response.content.decode()) + self.submission.status = SubmissionStatus.RUNTIME_ERROR + self.assertEqual(self.submission.status, "RE") def test_handle_submission_with_correct_output(self) -> None: @@ -224,6 +226,8 @@ def test_handle_submission_with_correct_output(self) -> None: self.assertEqual(response.status_code, 200) self.assertEqual(response.content.decode(), "Correct!") + self.submission.status = SubmissionStatus.ACCEPTED + self.assertEqual(self.submission.status, "AC") # def test_get_success_url(self) -> None: From 4e0113da7f1a2e93090809753a2b7d1529393f1b Mon Sep 17 00:00:00 2001 From: kyomi Date: Mon, 27 Nov 2023 22:44:34 -0300 Subject: [PATCH 5/9] fix: remove wrong files --- in.txt | 1 - out.txt | 1 - server/celery.py | 10 ++++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) delete mode 100644 in.txt delete mode 100644 out.txt create mode 100644 server/celery.py diff --git a/in.txt b/in.txt deleted file mode 100644 index 2deca7f..0000000 --- a/in.txt +++ /dev/null @@ -1 +0,0 @@ -10 5 diff --git a/out.txt b/out.txt deleted file mode 100644 index 60d3b2f..0000000 --- a/out.txt +++ /dev/null @@ -1 +0,0 @@ -15 diff --git a/server/celery.py b/server/celery.py new file mode 100644 index 0000000..f458665 --- /dev/null +++ b/server/celery.py @@ -0,0 +1,10 @@ +from os import environ + +from celery import Celery + +environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings.development") + +app = Celery("virtualjudge") + +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() From 0126a52afa43b1ccf9ec79bdfce7235d40d5b409 Mon Sep 17 00:00:00 2001 From: thegm445 Date: Mon, 27 Nov 2023 23:12:29 -0300 Subject: [PATCH 6/9] test(apps/tasks): admin.py is now 100% (hopefuly) covered --- apps/tasks/tests.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 813bae2..29464bd 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -1,6 +1,7 @@ from datetime import timedelta from django.contrib.admin.sites import AdminSite +from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.urls import resolve, reverse from django.utils import timezone @@ -239,3 +240,29 @@ def test_handle_submission_with_correct_output(self) -> None: # data={"code": "print('Hello, World!')"} # ) # self.assertEqual(response.status_code, 302) + + def test_save_model(self) -> None: + input_file = SimpleUploadedFile("input.txt", b"input") + output_file = SimpleUploadedFile("output.txt", b"output") + + response = self.client.post( + self.url, + data={ + "title": "Example task", + "description": "Some Example Task", + "contest": self.contest.id, + "input_file": input_file, + "output_file": output_file, + }, + ) + + response + + self.assertEqual(self.task.title, self.submission.task.title) + self.assertEqual( + self.task.description, self.submission.task.description + ) + self.assertEqual(self.task.input_file, self.submission.task.input_file) + self.assertEqual( + self.task.output_file, self.submission.task.output_file + ) From 5788687aa3b01626e648a46b2385b5404b02f6f6 Mon Sep 17 00:00:00 2001 From: kyomi Date: Mon, 27 Nov 2023 23:16:04 -0300 Subject: [PATCH 7/9] fix: remove wrong files --- server/celery.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 server/celery.py diff --git a/server/celery.py b/server/celery.py deleted file mode 100644 index f458665..0000000 --- a/server/celery.py +++ /dev/null @@ -1,10 +0,0 @@ -from os import environ - -from celery import Celery - -environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings.development") - -app = Celery("virtualjudge") - -app.config_from_object("django.conf:settings", namespace="CELERY") -app.autodiscover_tasks() From 7a6f34b33b1a9ef52d9aa819867e4e87b22d42ac Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 00:04:38 -0300 Subject: [PATCH 8/9] test(apps/tasks): fix some wrong tests --- apps/tasks/tests.py | 133 ++++++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 55 deletions(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index 29464bd..bc1b120 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -1,15 +1,17 @@ from datetime import timedelta +from io import BytesIO from django.contrib.admin.sites import AdminSite -from django.core.files.uploadedfile import SimpleUploadedFile +from django.core.files.uploadedfile import InMemoryUploadedFile from django.test import TestCase +from django.test.client import RequestFactory from django.urls import resolve, reverse from django.utils import timezone from apps.contests.models import Contest from apps.submissions.forms import SubmissionForm from apps.submissions.models import Submission, SubmissionStatus -from apps.tasks.admin import TaskAdmin +from apps.tasks.admin import TaskAdmin, TaskModelForm from apps.tasks.models import Task from apps.tasks.views import DetailView from apps.users.models import User @@ -72,6 +74,7 @@ def test_cancelled_contest_is_not_accessible(self) -> None: class TaskAdminTestCase(TestCase): def setUp(self) -> None: now = timezone.now() + self.site = AdminSite() self.admin = TaskAdmin(Task, self.site) @@ -82,7 +85,6 @@ def setUp(self) -> None: end_time=now + timedelta(hours=1), cancelled=False, ) - self.contest.save() def test_list_display(self) -> None: list_display = self.admin.list_display @@ -108,6 +110,60 @@ def test_fieldsets(self) -> None: self.assertEqual(fieldsets, expected) + def test_save_model(self) -> None: + title = "Example task" + description = "Some example task" + memory_limit = 256 + time_limit = 1 + + input_text = "Hello, World!" + output_text = "Hello, World!" + + input_file = InMemoryUploadedFile( + file=BytesIO(input_text.encode("utf-8")), + field_name="input_file", + name="input.txt", + content_type="text/plain", + size=13, + charset="utf-8", + ) + output_file = InMemoryUploadedFile( + file=BytesIO(output_text.encode("utf-8")), + field_name="output_file", + name="output.txt", + content_type="text/plain", + size=13, + charset="utf-8", + ) + + # We're gonna use RequestFactory to mock a request. This is + # necessary because the save_model method requires a request + # object with the FILES attribute. + mock = RequestFactory() + request = mock.post("/admin/tasks/task/add/") + + request.FILES["input_file"] = input_file + request.FILES["output_file"] = output_file + + task = Task( + title=title, + description=description, + memory_limit=memory_limit, + time_limit=time_limit, + contest=self.contest, + ) + + self.admin.save_model( + request=request, obj=task, form=TaskModelForm(), change=False + ) + + self.assertEqual(task.title, title) + self.assertEqual(task.description, description) + self.assertEqual(task.memory_limit, memory_limit) + self.assertEqual(task.time_limit, time_limit) + self.assertEqual(task.input_file, input_text) + self.assertEqual(task.output_file, output_text) + class TaskURLTestCase(TestCase): def test_detail_url_to_view_name(self) -> None: @@ -131,7 +187,7 @@ def setUp(self) -> None: start_time = now - timedelta(hours=1) end_time = now + timedelta(hours=1) - self.code = "print('Hello World')" + self.code = "print('Hello, World!')" self.contest = Contest._default_manager.create( title="Test Contest 1", @@ -154,7 +210,7 @@ def setUp(self) -> None: author=self.user, task=self.task, code="print('Hello, World!')", - status=SubmissionStatus, + status=SubmissionStatus.ACCEPTED, ) self.url = reverse("tasks:detail", args=[self.task.id]) @@ -202,67 +258,34 @@ def test_access_task_that_is_not_accessible(self) -> None: def test_handle_submission_with_exception(self) -> None: self.client.force_login(self.user) - # Enviar código que causará uma exceção - response = self.client.post( - self.url, data={"code": "raise Exception('Test exception')"} - ) - self.assertEqual(response.status_code, 200) - self.assertIn("Error: Test exception", response.content.decode()) + code = "raise Exception('Test exception')" + expected = "Exception: Test exception" - self.submission.status = SubmissionStatus.RUNTIME_ERROR + response = self.client.post(self.url, data={"code": code}) - self.assertEqual(self.submission.status, "RE") + self.assertEqual(response.status_code, 200) + self.assertInHTML(expected, response.content.decode()) def test_handle_submission_with_correct_output(self) -> None: self.client.force_login(self.user) - # Definir a saída esperada para a tarefa self.task.output_file = "Hello, World!\n" self.task.save() - # Enviar código que produzirá a saída correta - response = self.client.post( - self.url, data={"code": "print('Hello, World!')"} - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content.decode(), "Correct!") - - self.submission.status = SubmissionStatus.ACCEPTED - - self.assertEqual(self.submission.status, "AC") + response = self.client.post(self.url, data={"code": self.code}) + expected = "Correct!" - # def test_get_success_url(self) -> None: - # self.client.force_login(self.user) + self.assertEqual(response.status_code, 200) + self.assertHTMLEqual(response.content.decode(), expected) - # # Enviar código válido - # response = self.client.post( - # self.url, - # data={"code": "print('Hello, World!')"} - # ) - # self.assertEqual(response.status_code, 302) + def test_handle_submission_with_wrong_output(self) -> None: + self.client.force_login(self.user) - def test_save_model(self) -> None: - input_file = SimpleUploadedFile("input.txt", b"input") - output_file = SimpleUploadedFile("output.txt", b"output") - - response = self.client.post( - self.url, - data={ - "title": "Example task", - "description": "Some Example Task", - "contest": self.contest.id, - "input_file": input_file, - "output_file": output_file, - }, - ) + self.task.output_file = "Hello, World!" + self.task.save() - response + response = self.client.post(self.url, data={"code": self.code}) + expected = "Incorrect!" - self.assertEqual(self.task.title, self.submission.task.title) - self.assertEqual( - self.task.description, self.submission.task.description - ) - self.assertEqual(self.task.input_file, self.submission.task.input_file) - self.assertEqual( - self.task.output_file, self.submission.task.output_file - ) + self.assertEqual(response.status_code, 200) + self.assertHTMLEqual(response.content.decode(), expected) From c1f25f8eb8ff8c2b5a5772c94ade81460ba21bd1 Mon Sep 17 00:00:00 2001 From: kyomi Date: Tue, 28 Nov 2023 00:10:04 -0300 Subject: [PATCH 9/9] test(apps/tasks): add missing form success URL test --- apps/tasks/tests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/tasks/tests.py b/apps/tasks/tests.py index bc1b120..37eb078 100644 --- a/apps/tasks/tests.py +++ b/apps/tasks/tests.py @@ -181,7 +181,7 @@ def test_detail_url_reverse(self) -> None: self.assertEqual(url, expected) -class DetailViewTestCase(TestCase): +class TasksViewTestCase(TestCase): def setUp(self) -> None: now = timezone.now() start_time = now - timedelta(hours=1) @@ -214,6 +214,8 @@ def setUp(self) -> None: ) self.url = reverse("tasks:detail", args=[self.task.id]) + self.view = DetailView() + self.view.object = self.task def test_send_submission_successfully(self) -> None: self.client.force_login(self.user) @@ -289,3 +291,6 @@ def test_handle_submission_with_wrong_output(self) -> None: self.assertEqual(response.status_code, 200) self.assertHTMLEqual(response.content.decode(), expected) + + def test_form_success_url(self) -> None: + self.assertEqual(self.view.get_success_url(), self.url)