diff --git a/backend/Dockerfile.dev b/backend/Dockerfile similarity index 100% rename from backend/Dockerfile.dev rename to backend/Dockerfile diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod deleted file mode 100644 index 9e0245ee..00000000 --- a/backend/Dockerfile.prod +++ /dev/null @@ -1,22 +0,0 @@ -FROM python:3.11.4-alpine3.18 as requirements - -RUN pip install poetry-plugin-export - -WORKDIR /code - -COPY pyproject.toml poetry.lock ./ - -RUN poetry export --without-hashes --format=requirements.txt > requirements.txt - - -FROM python:3.11.4-alpine3.18 - -RUN apk add --no-cache gettext libintl - -WORKDIR /code - -COPY --from=requirements /code/requirements.txt . - -RUN pip install -r requirements.txt --no-cache-dir - -COPY . . diff --git a/backend/api/fixtures/realistic/realistic.yaml b/backend/api/fixtures/realistic/realistic.yaml index 2b101567..e2da30a3 100644 --- a/backend/api/fixtures/realistic/realistic.yaml +++ b/backend/api/fixtures/realistic/realistic.yaml @@ -172,7 +172,6 @@ time_limit: 10 memory_limit: 50 show_log: false - show_artifact: false - model: api.extracheck pk: 1 fields: @@ -183,7 +182,6 @@ time_limit: 30 memory_limit: 128 show_log: true - show_artifact: true # MARK: Students - model: api.student @@ -693,43 +691,36 @@ fields: extra_check: 0 log_file: fixtures/realistic/projects/0/0/submissions/0/submission_1/logs/log_extra_check_0.txt - artifact: "" - model: api.extracheckresult pk: 3 fields: extra_check: 1 log_file: fixtures/realistic/projects/0/0/submissions/0/submission_1/logs/log_extra_check_1.txt - artifact: fixtures/realistic/projects/0/0/submissions/0/submission_1/artifacts/artifact_extra_check_1.zip - model: api.extracheckresult pk: 5 fields: extra_check: 0 log_file: fixtures/realistic/projects/0/0/submissions/0/submission_2/logs/log_extra_check_0.txt - artifact: "" - model: api.extracheckresult pk: 6 fields: extra_check: 1 log_file: fixtures/realistic/projects/0/0/submissions/0/submission_2/logs/log_extra_check_1.txt - artifact: fixtures/realistic/projects/0/0/submissions/0/submission_2/artifacts/artifact_extra_check_1.zip - model: api.extracheckresult pk: 8 fields: extra_check: 0 log_file: "" - artifact: "" - model: api.extracheckresult pk: 9 fields: extra_check: 1 log_file: "" - artifact: "" - model: api.extracheckresult pk: 11 fields: extra_check: 0 log_file: fixtures/realistic/projects/0/0/submissions/1/submission_2/logs/log_extra_check_0.txt - artifact: "" - model: api.extracheckresult pk: 12 fields: diff --git a/backend/api/locale/en/LC_MESSAGES/django.po b/backend/api/locale/en/LC_MESSAGES/django.po index 51b4c470..90c40b6e 100755 --- a/backend/api/locale/en/LC_MESSAGES/django.po +++ b/backend/api/locale/en/LC_MESSAGES/django.po @@ -34,61 +34,61 @@ msgstr "Docker image is ready" msgid "dockerimage.state.error" msgstr "Docker image failed to build" -#: models/submission.py:62 +#: models/submission.py:61 msgid "submission.state.queued" msgstr "Queued" -#: models/submission.py:63 +#: models/submission.py:62 msgid "submission.state.running" msgstr "Running" -#: models/submission.py:64 +#: models/submission.py:63 msgid "submission.state.success" msgstr "Success" -#: models/submission.py:65 +#: models/submission.py:64 msgid "submission.state.failed" msgstr "Failed" -#: models/submission.py:70 +#: models/submission.py:69 msgid "submission.error.blockedextension" msgstr "The zip file contains a file with a non-allowed extension." -#: models/submission.py:71 +#: models/submission.py:70 msgid "submission.error.obligatedextensionnotfound" msgstr "" "The submitted zip file doesn't have any file with an obligated file " "extension." -#: models/submission.py:72 +#: models/submission.py:71 msgid "submission.error.filedirnotfound" msgstr "The submitted zip file lacks an obligated directory." -#: models/submission.py:75 +#: models/submission.py:74 msgid "submission.error.dockerimageerror" msgstr "try again later." -#: models/submission.py:76 +#: models/submission.py:75 msgid "submission.error.timelimit" msgstr "Timelimit exceeded." -#: models/submission.py:77 +#: models/submission.py:76 msgid "submission.error.memorylimit" msgstr "Memorylimit exceeded." -#: models/submission.py:78 +#: models/submission.py:77 msgid "submission.error.checkerror" msgstr "A check failed." -#: models/submission.py:79 +#: models/submission.py:78 msgid "submission.error.runtimeerror" msgstr "Crashed." -#: models/submission.py:80 +#: models/submission.py:79 msgid "submission.error.unknown" msgstr "Unkown error." -#: models/submission.py:81 +#: models/submission.py:80 msgid "submission.error.failedstructurecheck" msgstr "The zip file doesn't have the right structure." diff --git a/backend/api/locale/nl/LC_MESSAGES/django.po b/backend/api/locale/nl/LC_MESSAGES/django.po index 4e39a9b6..dd29b575 100755 --- a/backend/api/locale/nl/LC_MESSAGES/django.po +++ b/backend/api/locale/nl/LC_MESSAGES/django.po @@ -34,61 +34,61 @@ msgstr "Docker image is klaar." msgid "dockerimage.state.error" msgstr "Docker image is gefaald om te bouwen." -#: models/submission.py:62 +#: models/submission.py:61 msgid "submission.state.queued" msgstr "wachten" -#: models/submission.py:63 +#: models/submission.py:62 msgid "submission.state.running" msgstr "lopen" -#: models/submission.py:64 +#: models/submission.py:63 msgid "submission.state.success" msgstr "succes" -#: models/submission.py:65 +#: models/submission.py:64 msgid "submission.state.failed" msgstr "gefaald" -#: models/submission.py:70 +#: models/submission.py:69 msgid "submission.error.blockedextension" msgstr "De zip file bevat een niet toegelaten bestandstype." -#: models/submission.py:71 +#: models/submission.py:70 msgid "submission.error.obligatedextensionnotfound" msgstr "" "Er is geen enkel bestand met een bepaalde bestandstype die verplicht is in " "het ingediende zip-bestand." -#: models/submission.py:72 +#: models/submission.py:71 msgid "submission.error.filedirnotfound" msgstr "De ingediende zip file mankeerd een verplichtte map." -#: models/submission.py:75 +#: models/submission.py:74 msgid "submission.error.dockerimageerror" msgstr "Probeer later opnieuw." -#: models/submission.py:76 +#: models/submission.py:75 msgid "submission.error.timelimit" msgstr "Tijdslimit bereikt." -#: models/submission.py:77 +#: models/submission.py:76 msgid "submission.error.memorylimit" msgstr "Geheugenlimiet bereikt." -#: models/submission.py:78 +#: models/submission.py:77 msgid "submission.error.checkerror" msgstr "Een check faalde." -#: models/submission.py:79 +#: models/submission.py:78 msgid "submission.error.runtimeerror" msgstr "Crashed." -#: models/submission.py:80 +#: models/submission.py:79 msgid "submission.error.unknown" msgstr "Onbekende fout." -#: models/submission.py:81 +#: models/submission.py:80 msgid "submission.error.failedstructurecheck" msgstr "De ingediende zip file heeft niet de juiste structuur." diff --git a/backend/api/logic/get_file_path.py b/backend/api/logic/get_file_path.py index c75adb0f..f0b735d0 100644 --- a/backend/api/logic/get_file_path.py +++ b/backend/api/logic/get_file_path.py @@ -56,8 +56,3 @@ def get_docker_image_file_path(instance: DockerImage, _: str) -> str: def get_docker_image_tag(instance: DockerImage) -> str: return f"{DOCKER_BUILD_ROOT_NAME}_{instance.id}" - - -def get_extra_check_artifact_file_path(instance: ExtraCheckResult, uuid: str) -> str: - return (f"{_get_project_dir_path(instance.submission.group.project)}" - f"submissions/{instance.submission.group.id}/{uuid}/artifacts/{_get_uuid()}.zip") diff --git a/backend/api/migrations/0025_extracheckresult_artifact.py b/backend/api/migrations/0025_extracheckresult_artifact.py deleted file mode 100644 index 7e3984aa..00000000 --- a/backend/api/migrations/0025_extracheckresult_artifact.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.0.4 on 2024-05-13 21:33 - -from api.logic.get_file_path import get_extra_check_artifact_file_path -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0024_alter_dockerimage_state'), - ] - - operations = [ - migrations.AddField( - model_name='extracheckresult', - name='artifact', - field=models.FileField(max_length=256, null=True, upload_to=get_extra_check_artifact_file_path), - ), - migrations.AddField( - model_name='extracheck', - name='show_artifact', - field=models.BooleanField(default=True), - ), - ] diff --git a/backend/api/models/checks.py b/backend/api/models/checks.py index dfb7e2f0..efc7b42d 100644 --- a/backend/api/models/checks.py +++ b/backend/api/models/checks.py @@ -14,6 +14,8 @@ class FileExtension(models.Model): unique=True ) +# TODO: Remove zip.* translations + class StructureCheck(models.Model): """Model that represents a structure check for a project. @@ -98,6 +100,7 @@ class ExtraCheck(models.Model): ) # Maximum memory the container uses in MB + # TODO: Set max and min memory_limit = models.PositiveSmallIntegerField( default=128, blank=False, @@ -110,10 +113,3 @@ class ExtraCheck(models.Model): blank=False, null=False ) - - # Whether the artifacts should made available to the student - show_artifact = models.BooleanField( - default=True, - blank=False, - null=False - ) diff --git a/backend/api/models/submission.py b/backend/api/models/submission.py index ee3afdb1..20294604 100644 --- a/backend/api/models/submission.py +++ b/backend/api/models/submission.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING -from api.logic.get_file_path import (get_extra_check_artifact_file_path, - get_extra_check_log_file_path, +from api.logic.get_file_path import (get_extra_check_log_file_path, get_submission_file_path) from api.models.checks import ExtraCheck, StructureCheck from api.models.group import Group @@ -138,11 +137,3 @@ class ExtraCheckResult(CheckResult): blank=False, null=True ) - - # File path for the artifact of the extra checks - artifact = models.FileField( - upload_to=get_extra_check_artifact_file_path, - max_length=256, - blank=False, - null=True - ) diff --git a/backend/api/permissions/group_permissions.py b/backend/api/permissions/group_permissions.py index 00f15f6a..e75da21d 100644 --- a/backend/api/permissions/group_permissions.py +++ b/backend/api/permissions/group_permissions.py @@ -1,10 +1,8 @@ -from api.models.group import Group from api.permissions.role_permissions import (is_assistant, is_student, is_teacher) from authentication.models import User from rest_framework.permissions import SAFE_METHODS, BasePermission from rest_framework.request import Request -from rest_framework.views import APIView from rest_framework.viewsets import ViewSet @@ -61,23 +59,6 @@ def has_object_permission(self, request: Request, view: ViewSet, group) -> bool: class GroupSubmissionPermission(BasePermission): """Permission class for submission related group endpoints""" - def has_permission(self, request: Request, view: APIView) -> bool: - user: User = request.user - group_id = view.kwargs.get('pk') - group: Group | None = Group.objects.get(id=group_id) if group_id else None - - if group is None: - return True - - # Teachers and assistants of that course can view all submissions - if is_teacher(user): - return group.project.course.teachers.filter(id=user.teacher.id).exists() - - if is_assistant(user): - return group.project.course.assistants.filter(id=user.assistant.id).exists() - - return is_student(user) and group.students.filter(id=user.student.id).exists() - def had_object_permission(self, request: Request, view: ViewSet, group) -> bool: user: User = request.user course = group.project.course diff --git a/backend/api/permissions/project_permissions.py b/backend/api/permissions/project_permissions.py index a13b4946..55ff054a 100644 --- a/backend/api/permissions/project_permissions.py +++ b/backend/api/permissions/project_permissions.py @@ -13,6 +13,11 @@ def has_permission(self, request: Request, view: ViewSet) -> bool: """Check if user has permission to view a general project endpoint.""" user: User = request.user + # TODO: Sure return True corresponds with the comments made above + # The general project endpoint that lists all projects is not accessible for any role. + if request.method in SAFE_METHODS: + return True + # We only allow teachers and assistants to create new projects. return is_teacher(user) or is_assistant(user) diff --git a/backend/api/permissions/submission_permissions.py b/backend/api/permissions/submission_permissions.py index 7180a0fa..7bc7f642 100644 --- a/backend/api/permissions/submission_permissions.py +++ b/backend/api/permissions/submission_permissions.py @@ -57,18 +57,3 @@ def has_object_permission(self, request: Request, view: APIView, obj: ExtraCheck return obj.extra_check.show_log return True - - -class ExtraCheckResultArtifactPermission(ExtraCheckResultPermission): - def has_object_permission(self, request: Request, view: APIView, obj: ExtraCheckResult) -> bool: - result = super().has_object_permission(request, view, obj) - - if not result: - return False - - user: User = cast(User, request.user) - - if is_student(user): - return obj.extra_check.show_artifact - - return True diff --git a/backend/api/serializers/submission_serializer.py b/backend/api/serializers/submission_serializer.py index acdf26f8..db2d505c 100644 --- a/backend/api/serializers/submission_serializer.py +++ b/backend/api/serializers/submission_serializer.py @@ -39,9 +39,6 @@ def to_representation(self, instance: ExtraCheckResult) -> dict | None: representation["log_file"] = request.build_absolute_uri( reverse("extra-check-result-detail", args=[str(instance.id)]) + "log/" ) - representation["artifact"] = request.build_absolute_uri( - reverse("extra-check-result-detail", args=[str(instance.id)]) + "artifact/" - ) return representation return None diff --git a/backend/api/signals.py b/backend/api/signals.py index 82bc905c..b705255a 100644 --- a/backend/api/signals.py +++ b/backend/api/signals.py @@ -7,26 +7,24 @@ from api.models.student import Student from api.models.submission import (ExtraCheckResult, StateEnum, StructureCheckResult, Submission) -from api.tasks.docker_image import (task_docker_image_build, - task_docker_image_remove) +from api.tasks.docker_image import task_docker_image_build from api.tasks.extra_check import task_extra_check_start from api.tasks.structure_check import task_structure_check_start from authentication.models import User from authentication.signals import user_created -from django.db.models.signals import post_delete, post_save, pre_delete +from django.db.models.signals import post_delete, post_save from django.dispatch import Signal, receiver -# MARK: Signals +# Signals run_docker_image_build = Signal() -run_docker_image_remove = Signal() run_all_checks = Signal() run_structure_checks = Signal() run_extra_checks = Signal() -# MARK: Receivers +# Receivers @receiver(user_created) @@ -46,11 +44,6 @@ def _run_docker_image_build(docker_image: DockerImage, **_): task_docker_image_build.apply_async((docker_image,)) -@receiver(run_docker_image_remove) -def _run_docker_image_remove(docker_image: DockerImage, **_): - task_docker_image_remove.apply_async((docker_image,)) - - @receiver(run_all_checks) def _run_all_checks(submission: Submission, **_): # Get all checks @@ -82,7 +75,7 @@ def _run_extra_checks(submission: Submission, **_): task_extra_check_start.apply_async((True, extra_check_result,)) -# MARK: Hooks +# Hooks @receiver(post_save, sender=StructureCheck) @@ -115,6 +108,7 @@ def hook_extra_check(sender, instance: ExtraCheck, **kwargs): @receiver(post_save, sender=Submission) def hook_submission(sender, instance: Submission, created: bool, **kwargs): + # TODO: Maybe remove the raw check if created and not kwargs.get('raw', False): run_all_checks.send(sender=Submission, submission=instance) pass @@ -122,16 +116,11 @@ def hook_submission(sender, instance: Submission, created: bool, **kwargs): @receiver(post_save, sender=DockerImage) def hook_docker_image(sender, instance: DockerImage, created: bool, **kwargs): + # Run when it's created if created: run_docker_image_build.send(sender=DockerImage, docker_image=instance) - -@receiver(pre_delete, sender=DockerImage) -def hook_docker_image_delete(sender, instance: DockerImage, **kwargs): - run_docker_image_remove.send(sender=DockerImage, docker_image=instance) - - -# MARK: Helpers +# Helpers # Get all structure checks and create a result for each one diff --git a/backend/api/tasks/docker_image.py b/backend/api/tasks/docker_image.py index 3636ef36..246d5934 100644 --- a/backend/api/tasks/docker_image.py +++ b/backend/api/tasks/docker_image.py @@ -3,6 +3,7 @@ from api.logic.get_file_path import get_docker_image_tag from api.models.docker import DockerImage, StateEnum from celery import shared_task +from notifications.signals import NotificationType, notification_create from ypovoli.settings import MEDIA_ROOT @@ -12,6 +13,8 @@ def task_docker_image_build(docker_image: DockerImage): docker_image.state = StateEnum.BUILDING docker_image.save() + notification_type = NotificationType.DOCKER_IMAGE_BUILD_SUCCESS + # Build the image try: client = docker.from_env() @@ -20,10 +23,18 @@ def task_docker_image_build(docker_image: DockerImage): docker_image.state = StateEnum.READY except (docker.errors.APIError, docker.errors.BuildError, TypeError): docker_image.state = StateEnum.ERROR - # TODO: Sent notification + notification_type = NotificationType.DOCKER_IMAGE_BUILD_ERROR + finally: + # Update the state + docker_image.save() - # Update the state - docker_image.save() + # Send notification + notification_create.send( + sender=DockerImage, + type=notification_type, + queryset=[docker_image.owner], + arguments={"name": docker_image.name}, + ) @shared_task diff --git a/backend/api/tasks/extra_check.py b/backend/api/tasks/extra_check.py index 5ddb1565..1f63e87a 100644 --- a/backend/api/tasks/extra_check.py +++ b/backend/api/tasks/extra_check.py @@ -13,10 +13,10 @@ from api.models.docker import StateEnum as DockerStateEnum from api.models.submission import ErrorMessageEnum, ExtraCheckResult, StateEnum from celery import shared_task -from django.core.files import File from django.core.files.base import ContentFile from docker.models.containers import Container from docker.types import LogConfig +from notifications.signals import NotificationType, notification_create from requests.exceptions import ConnectionError @@ -36,12 +36,22 @@ def task_extra_check_start(structure_check_result: bool, extra_check_result: Ext extra_check_result.error_message = ErrorMessageEnum.DOCKER_IMAGE_ERROR extra_check_result.save() + notification_create.send( + sender=ExtraCheckResult, + type=NotificationType.EXTRA_CHECK_FAIL, + queryset=list(extra_check_result.submission.group.students.all()), + arguments={"name": extra_check_result.extra_check.name}, + ) + return structure_check_result # Will probably never happen but doesn't hurt to check while extra_check_result.submission.running_checks: sleep(1) + # Notification type + notification_type = NotificationType.EXTRA_CHECK_SUCCESS + # Lock extra_check_result.submission.running_checks = True @@ -114,41 +124,49 @@ def task_extra_check_start(structure_check_result: bool, extra_check_result: Ext case 1: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.CHECK_ERROR + notification_type = NotificationType.EXTRA_CHECK_FAIL # Time limit case 2: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.TIME_LIMIT + notification_type = NotificationType.EXTRA_CHECK_FAIL # Memory limit case 3: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.MEMORY_LIMIT + notification_type = NotificationType.EXTRA_CHECK_FAIL # Catch all non zero exit codes case _: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.RUNTIME_ERROR + notification_type = NotificationType.EXTRA_CHECK_FAIL # Docker image error except (docker.errors.APIError, docker.errors.ImageNotFound): extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.DOCKER_IMAGE_ERROR + notification_type = NotificationType.EXTRA_CHECK_FAIL # Runtime error except docker.errors.ContainerError: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.RUNTIME_ERROR + notification_type = NotificationType.EXTRA_CHECK_FAIL # Timeout error except ConnectionError: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.TIME_LIMIT + notification_type = NotificationType.EXTRA_CHECK_FAIL # Unknown error except Exception: extra_check_result.result = StateEnum.FAILED extra_check_result.error_message = ErrorMessageEnum.UNKNOWN + notification_type = NotificationType.EXTRA_CHECK_FAIL # Cleanup and data saving # Start by saving any logs @@ -165,6 +183,14 @@ def task_extra_check_start(structure_check_result: bool, extra_check_result: Ext extra_check_result.log_file.save(submission_uuid, content=ContentFile(logs), save=False) + # Send notification + notification_create.send( + sender=ExtraCheckResult, + type=notification_type, + queryset=list(extra_check_result.submission.group.students.all()), + arguments={"name": extra_check_result.extra_check.name}, + ) + # Zip and save any possible artifacts memory_zip = io.BytesIO() if os.listdir(artifacts_directory): diff --git a/backend/api/tests/test_project.py b/backend/api/tests/test_project.py index 0684088e..4e69fcfd 100644 --- a/backend/api/tests/test_project.py +++ b/backend/api/tests/test_project.py @@ -805,7 +805,7 @@ def test_submission_status_non_empty_groups(self): {"non_empty_groups": 2, "groups_submitted": 0, "extra_checks_passed": 0, "structure_checks_passed": 0}, ) - def test_submission_status_groups_submitted_and_not_passed_checks(self): + def test_submission_status_groups_submitted_and_passed_checks(self): """Retrieve the submission status for a project.""" course = create_course(name="test course", academic_startyear=2024) project = create_project( diff --git a/backend/api/views/course_view.py b/backend/api/views/course_view.py index 3adc2e6c..c3f42786 100644 --- a/backend/api/views/course_view.py +++ b/backend/api/views/course_view.py @@ -10,9 +10,9 @@ from api.serializers.assistant_serializer import (AssistantIDSerializer, AssistantSerializer) from api.serializers.course_serializer import (CourseCloneSerializer, + SaveInvitationLinkSerializer, CourseSerializer, CreateCourseSerializer, - SaveInvitationLinkSerializer, StudentJoinSerializer, StudentLeaveSerializer, TeacherJoinSerializer, @@ -39,6 +39,7 @@ class CourseViewSet(viewsets.ModelViewSet): serializer_class = CourseSerializer permission_classes = [IsAdminUser | CoursePermission] + # TODO: Creating should return the info of the new object and not a message "created" (General TODO) def create(self, request: Request, *_): """Override the create method to add the teacher to the course""" serializer = CreateCourseSerializer(data=request.data, context={"request": request}) diff --git a/backend/api/views/docker_view.py b/backend/api/views/docker_view.py index 6fad8423..525ced88 100644 --- a/backend/api/views/docker_view.py +++ b/backend/api/views/docker_view.py @@ -1,19 +1,22 @@ from api.models.docker import DockerImage from api.permissions.docker_permissions import DockerPermission from api.serializers.docker_serializer import DockerImageSerializer -from api.views.pagination.basic_pagination import BasicPagination +from rest_framework.permissions import IsAdminUser from django.db.models import Q from django.db.models.manager import BaseManager from rest_framework.decorators import action from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin) -from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet +from api.views.pagination.basic_pagination import BasicPagination + + +# TODO: Remove update abilities, maybe? -class DockerImageViewSet(RetrieveModelMixin, UpdateModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet): +class DockerImageViewSet(RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): queryset = DockerImage.objects.all() serializer_class = DockerImageSerializer diff --git a/backend/api/views/group_view.py b/backend/api/views/group_view.py index 7c118bcd..c3595f4d 100644 --- a/backend/api/views/group_view.py +++ b/backend/api/views/group_view.py @@ -40,6 +40,7 @@ def students(self, request, **_): ) return Response(serializer.data) + # TODO: I can access this endpoint unauthorized @action(detail=True, permission_classes=[IsAdminUser | GroupSubmissionPermission]) def submissions(self, request, **_): """Returns a list of submissions for the given group""" diff --git a/backend/api/views/submission_view.py b/backend/api/views/submission_view.py index 37911477..9010b64e 100644 --- a/backend/api/views/submission_view.py +++ b/backend/api/views/submission_view.py @@ -1,9 +1,8 @@ from api.models.submission import (ExtraCheckResult, StructureCheckResult, Submission) from api.permissions.submission_permissions import ( - ExtraCheckResultArtifactPermission, ExtraCheckResultLogPermission, - ExtraCheckResultPermission, StructureCheckResultPermission, - SubmissionPermission) + ExtraCheckResultLogPermission, ExtraCheckResultPermission, + StructureCheckResultPermission, SubmissionPermission) from api.serializers.submission_serializer import ( ExtraCheckResultSerializer, StructureCheckResultSerializer, SubmissionSerializer) @@ -16,6 +15,7 @@ from rest_framework.viewsets import GenericViewSet +# TODO: Permission to ask for logs class SubmissionViewSet(RetrieveModelMixin, GenericViewSet): queryset = Submission.objects.all() serializer_class = SubmissionSerializer @@ -42,20 +42,11 @@ class ExtraCheckResultViewSet(RetrieveModelMixin, GenericViewSet): serializer_class = ExtraCheckResultSerializer permission_classes = [ExtraCheckResultPermission] - @action(detail=True, permission_classes=[IsAdminUser | ExtraCheckResultArtifactPermission]) + @action(detail=True, permission_classes=[IsAdminUser | ExtraCheckResultLogPermission]) def log(self, request, **__): extra_check_result: ExtraCheckResult = self.get_object() if not extra_check_result.log_file: return Response({"message": _("extra_check_result.download.log")}, status=404) - return FileResponse(open(extra_check_result.log_file.path, "rb"), as_attachment=True, filename="log.txt") - - @action(detail=True, permission_classes=[IsAdminUser | ExtraCheckResultLogPermission]) - def artifact(self, request, **__): - extra_check_result: ExtraCheckResult = self.get_object() - - if not extra_check_result.artifact: - return Response({"message": _("extra_check_result.download.artifact")}, status=404) - - return FileResponse(open(extra_check_result.artifact.path, "rb"), as_attachment=True, filename="artifact.zip") + return FileResponse(open(extra_check_result.log_file.path, "rb"), as_attachment=True) diff --git a/backend/data/fixtures/realistic/projects/0/0/checks/generate_gibberish.sh b/backend/data/fixtures/realistic/projects/0/0/checks/generate_gibberish.sh index 9690ec1e..182840c5 100755 --- a/backend/data/fixtures/realistic/projects/0/0/checks/generate_gibberish.sh +++ b/backend/data/fixtures/realistic/projects/0/0/checks/generate_gibberish.sh @@ -1,7 +1,5 @@ #!/bin/bash -# generate gibberish logs - # Function to generate a random sequence of characters generate_sequence() { cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 50 @@ -16,7 +14,3 @@ while [ $count -le 50 ]; do generate_sequence count=$((count+1)) done - -# Generate an artifact - -wget https://golang.org/doc/gopher/modelsheet.jpg -P artifacts diff --git a/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_1/artifacts/artifact_extra_check_0.zip b/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_1/artifacts/artifact_extra_check_0.zip deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_1/artifacts/artifact_extra_check_1.zip b/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_1/artifacts/artifact_extra_check_1.zip deleted file mode 100644 index 912548a7..00000000 Binary files a/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_1/artifacts/artifact_extra_check_1.zip and /dev/null differ diff --git a/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_2/artifacts/artifact_extra_check_0.zip b/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_2/artifacts/artifact_extra_check_0.zip deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_2/artifacts/artifact_extra_check_1.zip b/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_2/artifacts/artifact_extra_check_1.zip deleted file mode 100644 index 912548a7..00000000 Binary files a/backend/data/fixtures/realistic/projects/0/0/submissions/0/submission_2/artifacts/artifact_extra_check_1.zip and /dev/null differ diff --git a/development.yml b/development.yml index 67ca5ea3..1d0bb3e4 100644 --- a/development.yml +++ b/development.yml @@ -35,7 +35,7 @@ services: nginx: <<: *common-keys-selab container_name: nginx - image: nginx:alpine-slim + image: nginx:latest ports: - 80:80 - 443:443 @@ -52,7 +52,7 @@ services: container_name: backend build: context: $BACKEND_DIR - dockerfile: Dockerfile.dev + dockerfile: Dockerfile command: sh -c "./setup.sh; python manage.py runsslserver 0.0.0.0:8000" volumes: - ${BACKEND_DIR}:/code @@ -63,7 +63,7 @@ services: container_name: celery build: context: $BACKEND_DIR - dockerfile: Dockerfile.dev + dockerfile: Dockerfile command: sh -c "./setup.sh && celery -A ypovoli worker -l DEBUG" volumes: - ${BACKEND_DIR}:/code diff --git a/frontend/src/components/teachers_assistants/buttons/LeaveCourseButton.vue b/frontend/src/components/teachers_assistants/buttons/LeaveCourseButton.vue index 2793174d..064abf29 100644 --- a/frontend/src/components/teachers_assistants/buttons/LeaveCourseButton.vue +++ b/frontend/src/components/teachers_assistants/buttons/LeaveCourseButton.vue @@ -26,7 +26,6 @@ const courseValue = ref(props.course); */ async function leaveCourse(): Promise { try { - console.log(props.user.getRole()); // Depending on the user's role, call the correct service if (props.user.getRole() === 'types.roles.assistant') { await assistantLeaveCourse(courseValue.value.id, props.user.id); diff --git a/frontend/src/test/unit/services/admin_service.test.ts b/frontend/src/test/unit/services/admin_service.test.ts index e9e51d41..61230f54 100644 --- a/frontend/src/test/unit/services/admin_service.test.ts +++ b/frontend/src/test/unit/services/admin_service.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useAdmin } from '@/composables/services/admin.service'; import { User } from '@/types/users/User.ts'; diff --git a/frontend/src/test/unit/services/assistant_service.test.ts b/frontend/src/test/unit/services/assistant_service.test.ts index 27874d7a..0cb5e9a0 100644 --- a/frontend/src/test/unit/services/assistant_service.test.ts +++ b/frontend/src/test/unit/services/assistant_service.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useAssistant } from '@/composables/services/assistant.service.ts'; import { Assistant } from '@/types/users/Assistant'; @@ -6,16 +5,11 @@ import { Assistant } from '@/types/users/Assistant'; const { assistants, assistant, - getAssistantByID, getAssistantsByCourse, getAssistants, - createAssistant, deleteAssistant, - - assistantJoinCourse, - assistantLeaveCourse, } = useAssistant(); function resetService(): void { diff --git a/frontend/src/test/unit/services/course_service.test.ts b/frontend/src/test/unit/services/course_service.test.ts index bbbb81de..39cf368e 100644 --- a/frontend/src/test/unit/services/course_service.test.ts +++ b/frontend/src/test/unit/services/course_service.test.ts @@ -1,25 +1,9 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { Course } from '@/types/Course.ts'; import { useCourses } from '@/composables/services/course.service.ts'; -const { - pagination, - courses, - course, - - getCourseByID, - searchCourses, - getCourses, - getCoursesByStudent, - getCoursesByTeacher, - getCourseByAssistant, - - createCourse, - cloneCourse, - deleteCourse, -} = useCourses(); +const { courses, course, getCourseByID, getCourses, getCoursesByStudent, createCourse } = useCourses(); function resetService(): void { course.value = null; diff --git a/frontend/src/test/unit/services/faculty_service.test.ts b/frontend/src/test/unit/services/faculty_service.test.ts index be6359d1..b9ce9c62 100644 --- a/frontend/src/test/unit/services/faculty_service.test.ts +++ b/frontend/src/test/unit/services/faculty_service.test.ts @@ -1,17 +1,8 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useFaculty } from '@/composables/services/faculty.service.ts'; import { Faculty } from '@/types/Faculty'; -const { - faculties, - faculty, - getFacultyByID, - getFaculties, - - createFaculty, - deleteFaculty, -} = useFaculty(); +const { faculties, faculty, getFacultyByID, getFaculties, createFaculty } = useFaculty(); function resetService(): void { faculty.value = null; diff --git a/frontend/src/test/unit/services/group_service.test.ts b/frontend/src/test/unit/services/group_service.test.ts index c65789f1..a58734ab 100644 --- a/frontend/src/test/unit/services/group_service.test.ts +++ b/frontend/src/test/unit/services/group_service.test.ts @@ -1,118 +1,64 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, it, expect } from 'vitest'; - -describe('placeholder', (): void => { - it('aaaaaaaa', () => {}); +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ + +import { describe, it, expect, assertType } from 'vitest'; +import { useGroup } from '@/composables/services/group.service.ts'; +import { type Project } from '@/types/Project'; + +const { groups, group, getGroupByID, getGroupsByProject, getGroupsByStudent } = useGroup(); + +function resetService(): void { + group.value = null; + groups.value = null; +} + +describe('group', (): void => { + it('gets group data by id', async () => { + resetService(); + + await getGroupByID('0'); + expect(group.value).not.toBeNull(); + expect(group.value?.id).toBe('0'); + expect(group.value?.score).toBe(20); + assertType(group.value?.project!); + expect(group.value?.students).toBeNull(); + expect(group.value?.submissions).toBeNull(); + }); + + it('gets groups data by project', async () => { + resetService(); + + await getGroupsByProject('3'); + expect(groups).not.toBeNull(); + expect(Array.isArray(groups.value)).toBe(true); + expect(groups.value?.length).toBe(2); + + expect(groups.value?.[0].id).toBe('0'); + expect(groups.value?.[0].score).toBe(20); + assertType(groups.value?.[0].project!); + expect(groups.value?.[0].students).toBeNull; + expect(groups.value?.[0].submissions).toBeNull(); + + expect(groups.value?.[1].id).toBe('1'); + expect(groups.value?.[1].score).toBe(18); + assertType(groups.value?.[1].project!); + expect(groups.value?.[1].students).toBeNull; + expect(groups.value?.[1].submissions).toBeNull(); + }); + + it('gets groups data by student', async () => { + resetService(); + + await getGroupsByStudent('1'); + expect(groups.value).not.toBeNull(); + expect(Array.isArray(groups.value)).toBe(true); + expect(groups.value?.length).toBe(1); + + expect(groups.value?.[0].id).toBe('0'); + expect(groups.value?.[0].score).toBe(20); + assertType(groups.value?.[0].project!); + expect(groups.value?.[0].students).toBeNull; + expect(groups.value?.[0].submissions).toBeNull(); + }); }); - -// /* eslint-disable @typescript-eslint/no-unused-vars */ -// import { describe, it, expect } from 'vitest'; -// import { useGroup } from '@/composables/services/group.service.ts'; -// import { Group } from '@/types/Group'; -// import { useProject } from '@/composables/services/project.service'; -// import { type Project } from '@/types/Project'; - -// const { -// groups, -// group, -// getGroupByID, -// getGroupsByProject, -// getGroupsByStudent, - -// createGroup, -// deleteGroup, -// } = useGroup(); - -// function resetService(): void { -// group.value = null; -// groups.value = null; -// } - -// const { project, getProjectByID } = useProject(); - -// describe('group', (): void => { -// it('gets group data by id', async () => { -// resetService(); - -// await getGroupByID('0'); -// expect(group.value).not.toBeNull(); -// expect(group.value?.score).toBe(20); -// expect(group.value?.id).toBe('0'); -// expect(group.value?.project).toBeNull(); -// expect(group.value?.students).toBeNull(); -// expect(group.value?.submissions).toBeNull(); -// }); - -// it('gets groups data by project', async () => { -// resetService(); - -// await getGroupsByProject('0'); -// expect(groups.value).not.toBeNull(); -// expect(Array.isArray(groups.value)).toBe(true); -// expect(groups.value?.length).toBe(2); - -// expect(groups.value?.[0]).not.toBeNull(); -// expect(groups.value?.[0]?.score).toBe(20); -// expect(groups.value?.[0]?.id).toBe('0'); -// expect(groups.value?.[0]?.project).toBeNull(); -// expect(groups.value?.[0]?.students).toBeNull(); -// expect(groups.value?.[0]?.submissions).toBeNull(); - -// expect(groups.value?.[1]).not.toBeNull(); -// expect(groups.value?.[1]?.score).toBe(18); -// expect(groups.value?.[1]?.id).toBe('1'); -// expect(groups.value?.[1]?.project).toBeNull(); -// expect(groups.value?.[1]?.students).toBeNull(); -// expect(groups.value?.[1]?.submissions).toBeNull(); -// }); - -// it('gets groups data by student', async () => { -// resetService(); - -// await getGroupsByStudent('1'); -// expect(groups.value).not.toBeNull(); -// expect(Array.isArray(groups.value)).toBe(true); -// expect(groups.value?.length).toBe(1); - -// expect(groups.value?.[0]).not.toBeNull(); -// expect(groups.value?.[0]?.score).toBe(20); -// expect(groups.value?.[0]?.id).toBe('0'); -// expect(groups.value?.[0]?.project).toBeNull(); -// expect(groups.value?.[0]?.students).toBeNull(); -// expect(groups.value?.[0]?.submissions).toBeNull(); -// }); - -// it('create group', async () => { -// resetService(); - -// const projectId = '0'; -// await getProjectByID(projectId); -// const exampleProject: Project | null = project.value; -// expect(exampleProject).not.toBeNull(); - -// const exampleGroup = new Group( -// '', // id -// 10, // score -// exampleProject, // project -// [], // students -// [], // submissions -// ); - -// await getGroupsByProject(projectId); - -// expect(groups).not.toBeNull(); -// expect(Array.isArray(groups.value)).toBe(true); -// const prevLength = groups.value?.length ?? 0; - -// await createGroup(exampleGroup, projectId); -// await getGroupsByProject(projectId); - -// expect(groups).not.toBeNull(); -// expect(Array.isArray(groups.value)).toBe(true); -// expect(groups.value?.length).toBe(prevLength + 1); - -// // Only check for fields that are sent to the backend -// expect(groups.value?.[prevLength]?.score).toBe(10); -// expect(groups.value?.[prevLength]?.project).toBeNull(); -// }); -// }); diff --git a/frontend/src/test/unit/services/project_service.test.ts b/frontend/src/test/unit/services/project_service.test.ts index 7dd17ce5..4e10e315 100644 --- a/frontend/src/test/unit/services/project_service.test.ts +++ b/frontend/src/test/unit/services/project_service.test.ts @@ -1,193 +1,187 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, it, expect } from 'vitest'; - -describe('placeholder', (): void => { - it('aaaaaaaa', () => {}); +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ + +import { describe, it, expect, assertType } from 'vitest'; +import { useProject } from '@/composables/services/project.service.ts'; +import { type Course } from '@/types/Course'; +import { type SubmissionStatus } from '@/types/SubmisionStatus'; +import { type Group } from '@/types/Group'; + +const { projects, project, getProjectByID, getProjectsByCourse, getProjectsByStudent } = useProject(); + +function resetService(): void { + project.value = null; + projects.value = null; +} + +describe('project', (): void => { + it('gets project data by id', async () => { + resetService(); + + await getProjectByID('0'); + expect(project.value).not.toBeNull(); + expect(project.value?.name).toBe('project0'); + expect(project.value?.description).toBe('project0 description'); + expect(project.value?.visible).toBe(true); + expect(project.value?.archived).toBe(false); + expect(project.value?.locked_groups).toBe(false); + expect(project.value?.start_date).toStrictEqual(new Date('November 11, 2024 01:15:00')); + expect(project.value?.deadline).toStrictEqual(new Date('November 11, 2025 01:15:00')); + expect(project.value?.max_score).toBe(100); + expect(project.value?.score_visible).toBe(true); + expect(project.value?.group_size).toBe(5); + assertType(project.value?.course!); + assertType(project.value?.status!); + assertType(project.value?.structure_file!); + expect(project.value?.structureChecks).toBe(null); + expect(project.value?.extra_checks).toBe(null); + assertType(project.value?.groups!); + expect(project.value?.submissions).toBe(null); + }); + + it('gets projects data by course', async () => { + resetService(); + + await getProjectsByCourse('1'); + expect(projects.value).not.toBeNull(); + expect(Array.isArray(projects.value)).toBe(true); + expect(projects.value?.length).toBe(2); + + expect(projects.value?.[0]?.name).toBe('project0'); + expect(projects.value?.[0]?.description).toBe('project0 description'); + expect(projects.value?.[0]?.visible).toBe(true); + expect(projects.value?.[0]?.archived).toBe(false); + expect(projects.value?.[0]?.locked_groups).toBe(false); + expect(projects.value?.[0]?.start_date).toStrictEqual(new Date('November 11, 2024 01:15:00')); + expect(projects.value?.[0]?.deadline).toStrictEqual(new Date('November 11, 2025 01:15:00')); + expect(projects.value?.[0]?.max_score).toBe(100); + expect(projects.value?.[0]?.score_visible).toBe(true); + expect(projects.value?.[0]?.group_size).toBe(5); + assertType(projects.value?.[0]?.course!); + assertType(projects.value?.[0]?.status!); + assertType(projects.value?.[0]?.structure_file!); + expect(projects.value?.[0]?.structureChecks).toBe(null); + expect(projects.value?.[0]?.extra_checks).toBe(null); + assertType(projects.value?.[0]?.groups!); + expect(projects.value?.[0]?.submissions).toBe(null); + + expect(projects.value?.[1]?.name).toBe('project1'); + expect(projects.value?.[1]?.description).toBe('project1 description'); + expect(projects.value?.[1]?.visible).toBe(true); + expect(projects.value?.[1]?.archived).toBe(false); + expect(projects.value?.[1]?.locked_groups).toBe(false); + expect(projects.value?.[1]?.start_date).toStrictEqual(new Date('December 11, 2024 01:15:00')); + expect(projects.value?.[1]?.deadline).toStrictEqual(new Date('December 11, 2025 01:15:00')); + expect(projects.value?.[1]?.max_score).toBe(50); + expect(projects.value?.[1]?.score_visible).toBe(false); + expect(projects.value?.[1]?.group_size).toBe(8); + assertType(projects.value?.[1]?.course!); + assertType(projects.value?.[1]?.status!); + assertType(projects.value?.[1]?.structure_file!); + expect(projects.value?.[1]?.structureChecks).toBe(null); + expect(projects.value?.[1]?.extra_checks).toBe(null); + assertType(projects.value?.[1]?.groups!); + expect(projects.value?.[1]?.submissions).toBe(null); + }); + + it('gets projects data by student', async () => { + resetService(); + + await getProjectsByStudent('1'); + expect(projects).not.toBeNull(); + expect(Array.isArray(projects.value)).toBe(true); + expect(projects.value?.length).toBe(2); + expect(projects.value).not.toBeNull(); + + expect(projects.value?.[0]?.name).toBe('project0'); + expect(projects.value?.[0]?.description).toBe('project0 description'); + expect(projects.value?.[0]?.visible).toBe(true); + expect(projects.value?.[0]?.archived).toBe(false); + expect(projects.value?.[0]?.locked_groups).toBe(false); + expect(projects.value?.[0]?.start_date).toStrictEqual(new Date('November 11, 2024 01:15:00')); + expect(projects.value?.[0]?.deadline).toStrictEqual(new Date('November 11, 2025 01:15:00')); + expect(projects.value?.[0]?.max_score).toBe(100); + expect(projects.value?.[0]?.score_visible).toBe(true); + expect(projects.value?.[0]?.group_size).toBe(5); + assertType(projects.value?.[0]?.course!); + assertType(projects.value?.[0]?.status!); + assertType(projects.value?.[0]?.structure_file!); + expect(projects.value?.[0]?.structureChecks).toBe(null); + expect(projects.value?.[0]?.extra_checks).toBe(null); + assertType(projects.value?.[0]?.groups!); + expect(projects.value?.[0]?.submissions).toBe(null); + + expect(projects.value?.[1]?.name).toBe('project1'); + expect(projects.value?.[1]?.description).toBe('project1 description'); + expect(projects.value?.[1]?.visible).toBe(true); + expect(projects.value?.[1]?.archived).toBe(false); + expect(projects.value?.[1]?.locked_groups).toBe(false); + expect(projects.value?.[1]?.start_date).toStrictEqual(new Date('December 11, 2024 01:15:00')); + expect(projects.value?.[1]?.deadline).toStrictEqual(new Date('December 11, 2025 01:15:00')); + expect(projects.value?.[1]?.max_score).toBe(50); + expect(projects.value?.[1]?.score_visible).toBe(false); + expect(projects.value?.[1]?.group_size).toBe(8); + assertType(projects.value?.[1]?.course!); + assertType(projects.value?.[1]?.status!); + assertType(projects.value?.[1]?.structure_file!); + expect(projects.value?.[1]?.structureChecks).toBe(null); + expect(projects.value?.[1]?.extra_checks).toBe(null); + assertType(projects.value?.[1]?.groups!); + expect(projects.value?.[1]?.submissions).toBe(null); + }); + + it('create project', async () => { + resetService(); + + // const courseId = '1'; + // await getCourseByID(courseId); + + // const exampleProject = new Project( + // '', // id + // 'project_name', // name + // 'project_description', // description + // true, // visible + // false, // archived + // false, // locked_groups + // new Date('November 1, 2024 04:20:00'), // start_data + // new Date('November 2, 2024 04:20:00'), // deadline + // 20, // max_score + // false, // score_visible + // 5, // group_size + // course.value!, // course + // new SubmissionStatus(0, 0, 0, 0), // submission_status + // null, // structureChecks + // null, // extra_checks + // null, // groups + // null, // submissions + // ); + + // await getProjectsByCourse(courseId); + + // expect(projects).not.toBeNull(); + // expect(Array.isArray(projects.value)).toBe(true); + // const prevLength = projects.value?.length ?? 0; + + // await createProject(exampleProject, courseId, 0); + // await getProjectsByCourse(courseId); + + // expect(projects).not.toBeNull(); + // expect(Array.isArray(projects.value)).toBe(true); + // expect(projects.value?.length).toBe(prevLength + 1); + + // // Only check for fields that are sent to the backend + // expect(projects.value?.[prevLength]?.name).toBe('project_name'); + // expect(projects.value?.[prevLength]?.description).toBe('project_description'); + // expect(projects.value?.[prevLength]?.visible).toBe(true); + // expect(projects.value?.[prevLength]?.archived).toBe(false); + // expect(projects.value?.[prevLength]?.locked_groups).toBe(false); + // expect(projects.value?.[prevLength]?.start_date).toStrictEqual(new Date('November 1, 2024 04:20:00')); + // expect(projects.value?.[prevLength]?.deadline).toStrictEqual(new Date('November 2, 2024 04:20:00')); + // expect(projects.value?.[prevLength]?.max_score).toBe(20); + // expect(projects.value?.[prevLength]?.score_visible).toBe(false); + // expect(projects.value?.[prevLength]?.group_size).toBe(5); + // expect(projects.value?.[prevLength]?.structure_file).toBe(null); + // expect(projects.value?.[prevLength]?.course).toBe(null); + }); }); - -// /* eslint-disable @typescript-eslint/no-unused-vars */ -// import { describe, it, expect } from 'vitest'; -// import { useProject } from '@/composables/services/project.service.ts'; -// import { useCourses } from '@/composables/services/course.service'; -// import { Project } from '@/types/Project'; -// import { Course } from '@/types/Course'; - -// const { -// projects, -// project, -// getProjectByID, -// getProjectsByCourse, -// getProjectsByCourseAndDeadline, -// getProjectsByStudent, - -// createProject, -// deleteProject, -// } = useProject(); - -// const { course, getCourseByID } = useCourses(); - -// function resetService(): void { -// project.value = null; -// projects.value = null; -// } - -// describe('project', (): void => { -// it('gets project data by id', async () => { -// resetService(); - -// await getProjectByID('0'); -// expect(project.value).not.toBeNull(); -// expect(project.value?.name).toBe('sel2'); -// expect(project.value?.course.id).toBe('1'); -// expect(project.value?.course).toBeInstanceOf(Course); -// expect(project.value?.description).toBe('this is a test'); -// expect(project.value?.visible).toBe(true); -// expect(project.value?.archived).toBe(false); -// expect(project.value?.locked_groups).toBe(false); -// expect(project.value?.start_date).toStrictEqual(new Date('July 21, 2024 01:15:00')); -// expect(project.value?.deadline).toStrictEqual(new Date('July 23, 2024 01:15:00')); -// expect(project.value?.max_score).toBe(100); -// expect(project.value?.score_visible).toBe(true); -// expect(project.value?.group_size).toBe(8); -// expect(project.value?.course.id).toBe('1'); -// expect(project.value?.structureChecks).toBeNull(); -// expect(project.value?.extra_checks).toBeNull(); -// expect(project.value?.groups).toBeNull(); -// expect(project.value?.submissions).toBeNull(); -// }); - -// it('gets projects data by course', async () => { -// resetService(); - -// await getProjectsByCourse('1'); -// expect(projects).not.toBeNull(); -// expect(Array.isArray(projects.value)).toBe(true); -// expect(projects.value?.length).toBe(2); -// expect(projects.value).not.toBeNull(); -// expect(projects.value?.[0]?.name).toBe('sel2'); -// expect(projects.value?.[0]?.course.id).toBe('1'); -// expect(projects.value?.[0].course).toBeInstanceOf(Course); -// expect(projects.value?.[0]?.description).toBe('this is a test'); -// expect(projects.value?.[0]?.visible).toBe(true); -// expect(projects.value?.[0]?.archived).toBe(false); -// expect(projects.value?.[0]?.locked_groups).toBe(false); -// expect(projects.value?.[0]?.start_date).toStrictEqual(new Date('July 21, 2024 01:15:00')); -// expect(projects.value?.[0]?.deadline).toStrictEqual(new Date('July 23, 2024 01:15:00')); -// expect(projects.value?.[0]?.max_score).toBe(100); -// expect(projects.value?.[0]?.score_visible).toBe(true); -// expect(projects.value?.[0]?.group_size).toBe(8); -// expect(projects.value?.[0]?.course.id).toBe('1'); -// expect(projects.value?.[0]?.structureChecks).toBeNull(); -// expect(projects.value?.[0]?.extra_checks).toBeNull(); -// expect(projects.value?.[0]?.groups).toBeNull(); -// expect(projects.value?.[0]?.submissions).toBeNull(); - -// expect(projects.value?.[1]?.name).toBe('sel3'); -// expect(projects.value?.[1].course).toBeInstanceOf(Course); -// expect(projects.value?.[1]?.course.id).toBe('1'); -// expect(projects.value?.[1]?.description).toBe('make a project'); -// expect(projects.value?.[1]?.visible).toBe(true); -// expect(projects.value?.[1]?.archived).toBe(false); -// expect(projects.value?.[1]?.locked_groups).toBe(false); -// expect(projects.value?.[1]?.start_date).toStrictEqual(new Date('July 21, 2024 01:15:00')); -// expect(projects.value?.[1]?.deadline).toStrictEqual(new Date('July 23, 2024 01:15:00')); -// expect(projects.value?.[1]?.max_score).toBe(20); -// expect(projects.value?.[1]?.score_visible).toBe(false); -// expect(projects.value?.[1]?.group_size).toBe(3); -// expect(projects.value?.[1]?.course.id).toBe('1'); -// expect(projects.value?.[1]?.structureChecks).toBeNull(); -// expect(projects.value?.[1]?.extra_checks).toBeNull(); -// expect(projects.value?.[1]?.groups).toBeNull(); -// expect(projects.value?.[1]?.submissions).toBeNull(); -// }); - -// it('gets projects data', async () => { -// resetService(); - -// await getProjectsByStudent('1'); -// expect(projects).not.toBeNull(); -// expect(Array.isArray(projects.value)).toBe(true); -// expect(projects.value?.length).toBe(2); -// expect(projects.value).not.toBeNull(); -// expect(projects.value?.[0]?.name).toBe('sel2'); -// expect(projects.value?.[0]?.description).toBe('this is a test'); -// expect(projects.value?.[0]?.visible).toBe(true); -// expect(projects.value?.[0]?.archived).toBe(false); -// expect(projects.value?.[0]?.locked_groups).toBe(false); -// expect(projects.value?.[0]?.start_date).toStrictEqual(new Date('July 21, 2024 01:15:00')); -// expect(projects.value?.[0]?.deadline).toStrictEqual(new Date('July 23, 2024 01:15:00')); -// expect(projects.value?.[0]?.max_score).toBe(100); -// expect(projects.value?.[0]?.score_visible).toBe(true); -// expect(projects.value?.[0]?.group_size).toBe(8); -// expect(projects.value?.[0]?.course.id).toBe('1'); -// expect(projects.value?.[0]?.structureChecks).toBeNull(); -// expect(projects.value?.[0]?.extra_checks).toBeNull(); -// expect(projects.value?.[0]?.groups).toBeNull(); -// expect(projects.value?.[0]?.submissions).toBeNull(); - -// expect(projects.value?.[1]?.name).toBe('sel3'); -// expect(projects.value?.[1]?.course.id).toBe('1'); -// expect(projects.value?.[1]?.description).toBe('make a project'); -// expect(projects.value?.[1]?.visible).toBe(true); -// expect(projects.value?.[1]?.archived).toBe(false); -// expect(projects.value?.[1]?.locked_groups).toBe(false); -// expect(projects.value?.[1]?.start_date).toStrictEqual(new Date('July 21, 2024 01:15:00')); -// expect(projects.value?.[1]?.deadline).toStrictEqual(new Date('July 23, 2024 01:15:00')); -// expect(projects.value?.[1]?.max_score).toBe(20); -// expect(projects.value?.[1]?.score_visible).toBe(false); -// expect(projects.value?.[1]?.group_size).toBe(3); -// expect(projects.value?.[1]?.structureChecks).toBeNull(); -// expect(projects.value?.[1]?.extra_checks).toBeNull(); -// expect(projects.value?.[1]?.groups).toBeNull(); -// expect(projects.value?.[1]?.submissions).toBeNull(); -// }); - -// it('create project', async () => { -// resetService(); - -// const courseId = '1'; -// await getCourseByID(courseId); - -// const exampleProject = new Project( -// '', // id -// 'project_name', // name -// 'project_description', // description -// true, // visible -// false, // archived -// false, // locked_groups -// new Date('November 1, 2024 04:20:00'), // start_data -// new Date('November 2, 2024 04:20:00'), // deadline -// 20, // max_score -// false, // score_visible -// 5, // group_size -// null, // structure_file -// ); - -// await getProjectsByCourse(courseId); - -// expect(projects).not.toBeNull(); -// expect(Array.isArray(projects.value)).toBe(true); -// const prevLength = projects.value?.length ?? 0; - -// // await createProject(JSON.stringify(exampleProject), courseId); -// // await getProjectsByCourse(courseId); - -// // expect(projects).not.toBeNull(); -// // expect(Array.isArray(projects.value)).toBe(true); -// // expect(projects.value?.length).toBe(prevLength + 1); - -// // // Only check for fields that are sent to the backend -// // expect(projects.value?.[prevLength]?.name).toBe('project_name'); -// // expect(projects.value?.[prevLength]?.description).toBe('project_description'); -// // expect(projects.value?.[prevLength]?.visible).toBe(true); -// // expect(projects.value?.[prevLength]?.archived).toBe(false); -// // expect(projects.value?.[prevLength]?.locked_groups).toBe(false); -// // expect(projects.value?.[prevLength]?.start_date).toStrictEqual(new Date('November 1, 2024 04:20:00')); -// // expect(projects.value?.[prevLength]?.deadline).toStrictEqual(new Date('November 2, 2024 04:20:00')); -// // expect(projects.value?.[prevLength]?.max_score).toBe(20); -// // expect(projects.value?.[prevLength]?.score_visible).toBe(false); -// // expect(projects.value?.[prevLength]?.group_size).toBe(5); -// // expect(projects.value?.[prevLength]?.structure_file).toBe(null); -// // expect(projects.value?.[prevLength]?.course).toBe(null); -// }); -// }); diff --git a/frontend/src/test/unit/services/setup/data.ts b/frontend/src/test/unit/services/setup/data.ts index f25084c9..2a8357d3 100644 --- a/frontend/src/test/unit/services/setup/data.ts +++ b/frontend/src/test/unit/services/setup/data.ts @@ -1,17 +1,4 @@ -export const groups = [ - { - id: '0', - score: 20, - project: '0', - students: ['1', '2', '3', '000201247011'], - }, - { - id: '1', - score: 18, - project: '0', - students: ['1', '2', '3', '000201247011'], - }, -]; +import { File } from "buffer"; export const courses = [ { @@ -107,41 +94,127 @@ export const courses = [ }, ]; +export const submissionStatuses = [ + { + non_empty_groups: 5, + groups_submitted: 4, + structure_checks_passed: 3, + extra_checks_passed: 1, + }, + { + non_empty_groups: 6, + groups_submitted: 5, + structure_checks_passed: 4, + extra_checks_passed: 2, + }, +]; + +export const submissions = [ + { + id: '1', + submission_number: 1, + submission_time: new Date('July 21, 2024 01:15:00'), + zip: new File(['1', '2'], 'submission.zip', { type: 'application/zip' }), + extraCheckResults: [], + structureCheckResults: [], + is_valid: true, + }, + { + id: '2', + submission_number: 2, + submission_time: new Date('July 21, 2024 01:15:00'), + zip: new File(['3', '4'], 'submission.zip', { type: 'application/zip' }), + extraCheckResults: [], + structureCheckResults: [], + is_valid: true, + }, +]; + +const groups = [ + { + id: '0', + score: 20, + project: {}, + students: ['1', '2', '3', '000201247011'], + submissions: [submissions[0]], + }, + { + id: '1', + score: 18, + project: {}, + students: ['1', '2', '3', '000201247011'], + submissions: [submissions[1]], + }, +]; + export const projects = [ { id: '0', - course: courses[0], - name: 'sel2', - description: 'this is a test', + name: 'project0', + description: 'project0 description', visible: true, archived: false, locked_groups: false, - start_date: new Date('July 21, 2024 01:15:00'), - deadline: new Date('July 23, 2024 01:15:00'), + start_date: new Date('November 11, 2024 01:15:00'), + deadline: new Date('November 11, 2025 01:15:00'), max_score: 100, score_visible: true, - group_size: 8, - submissions: ['1', '2'], - groups: ['0', '1'], + group_size: 5, + course: courses[0], + status: submissionStatuses[0], + structure_file: new File(['byte1', 'byte2', 'byte3'], 'submission.zip', { type: 'application/zip' }), + structureChecks: null, + extra_checks: null, + groups: [groups[0], groups[1]], + submissions: [submissions[0], submissions[1]], }, { - id: 1, + id: '1', + name: 'project1', + description: 'project1 description', + visible: true, + archived: false, + locked_groups: false, + start_date: new Date('December 11, 2024 01:15:00'), + deadline: new Date('December 11, 2025 01:15:00'), + max_score: 50, + score_visible: false, + group_size: 8, course: courses[0], - name: 'sel3', - description: 'make a project', + status: submissionStatuses[1], + structure_file: new File(['byte1', 'byte2'], 'submission.zip', { type: 'application/zip' }), + structureChecks: null, + extra_checks: null, + groups: [groups[0], groups[1]], + submissions: [submissions[0], submissions[1]], + }, + { + id: '3', + name: 'project3', + description: 'project3 description', visible: true, archived: false, locked_groups: false, - start_date: new Date('July 21, 2024 01:15:00'), - deadline: new Date('July 23, 2024 01:15:00'), - max_score: 20, + start_date: new Date('June 11, 2024 01:15:00'), + deadline: new Date('July 11, 2025 01:15:00'), + max_score: 150, score_visible: false, - group_size: 3, + group_size: 12, + course: [], + status: [], + structure_file: new File(['byte1', 'byte2'], 'submission.zip', { type: 'application/zip' }), + structureChecks: null, + extra_checks: null, + groups: [], submissions: [], - groups: ['0', '1'], }, ]; +groups[0].project = projects[2]; +groups[1].project = projects[2]; + +export { groups }; + export const faculties = [ { id: 'sciences', name: 'wetenschappen' }, { id: 'football', name: 'voetbal' }, @@ -335,24 +408,3 @@ export const structureChecks = [ name: 'folder3/folder3-1', }, ]; - -export const submissions = [ - { - id: '1', - group: '1', - files: [], - extra_checks_results: [], - submission_number: 1, - submission_time: new Date('July 21, 2024 01:15:00'), - structureChecks_passed: true, - }, - { - id: '2', - group: '1', - files: [], - extra_checks_results: [], - submission_number: 2, - submission_time: new Date('July 21, 2024 01:15:00'), - structureChecks_passed: true, - }, -]; diff --git a/frontend/src/test/unit/services/setup/get_handlers.ts b/frontend/src/test/unit/services/setup/get_handlers.ts index 51a805ba..ce898fe9 100644 --- a/frontend/src/test/unit/services/setup/get_handlers.ts +++ b/frontend/src/test/unit/services/setup/get_handlers.ts @@ -21,7 +21,17 @@ export const getHandlers = [ return HttpResponse.json(groups.find((x) => x.id === params.id)); }), http.get(baseUrl + endpoints.submissions.retrieve.replace('{id}', ':id'), ({ params }) => { - return HttpResponse.json(submissions.find((x) => x.id === params.id)); + const submission = submissions.find((x) => x.id === params.id); + // Convert to a ResponseSubmission object + const responseSubmission = { + id: submission?.id, + submission_number: submission?.submission_number, + submission_time: submission?.submission_time, + zip: submission?.zip, + results: [], // We can leave this empty since the conversion to a valid results array is not the purpose of these tests + is_valid: submission?.is_valid, + }; + return HttpResponse.json(responseSubmission); }), http.get(baseUrl + endpoints.structureChecks.retrieve.replace('{id}', ':id'), ({ params }) => { return HttpResponse.json(structureChecks.find((x) => x.id === params.id)); @@ -45,12 +55,23 @@ export const getHandlers = [ return HttpResponse.json(courses.find((x) => x.id === params.id)); }), http.get(baseUrl + endpoints.groups.byProject.replace('{projectId}', ':id'), ({ params }) => { - return HttpResponse.json(groups.filter((x) => x.project === params.id)); + return HttpResponse.json(groups.filter((x) => x.project.id === params.id)); }), http.get(baseUrl + endpoints.submissions.byProject.replace('{projectId}', ':id'), ({ params }) => { const project = projects.find((x) => x.id === params.id); const submittedSubmissions = project !== null && project !== undefined ? project.submissions : []; - return HttpResponse.json(submissions.filter((x) => submittedSubmissions.includes(x.id))); + + // Convert to a ResponseSubmission object + const projectSubmissions = submissions.filter((x) => submittedSubmissions.map((y) => y.id).includes(x.id)); + const responseSubmissions = projectSubmissions.map((x) => ({ + id: x?.id, + submission_number: x?.submission_number, + submission_time: x?.submission_time, + zip: x?.zip, + results: [], + is_valid: x?.is_valid, + })); + return HttpResponse.json(responseSubmissions); }), http.get(baseUrl + endpoints.teachers.byCourse.replace('{courseId}', ':id'), ({ params }) => { const course = courses.find((x) => x.id === params.id); @@ -84,8 +105,10 @@ export const getHandlers = [ }), http.get(baseUrl + endpoints.submissions.status.replace('{projectId}', ':id'), ({ params }) => { const project = projects.find((x) => x.id === params.id); - const groupIds = project !== null && project !== undefined ? project.groups : []; - const submissionIds = project !== null && project !== undefined ? project.submissions : []; + const groups = project !== null && project !== undefined ? project.groups : []; + const groupIds = groups.map((group) => group.id); + const submissions = project !== null && project !== undefined ? project.submissions : []; + const submissionIds = submissions.map((submission) => submission.id); return HttpResponse.json({ groups_submitted: new Set(submissions.filter((x) => submissionIds.includes(x.id)).map((x) => x.group)).size, @@ -106,7 +129,19 @@ export const getHandlers = [ ); }), http.get(baseUrl + endpoints.submissions.byGroup.replace('{groupId}', ':id'), ({ params }) => { - return HttpResponse.json(submissions.filter((x) => x.group === params.id)); + const group = groups.find((x) => (x.id = params.id)); + const groupSubmissions = submissions.filter((x) => group?.submissions.includes(x)); + + // Convert to a ResponseSubmission object + const responseSubmissions = groupSubmissions.map((x) => ({ + id: x?.id, + submission_number: x?.submission_number, + submission_time: x?.submission_time, + zip: x?.zip, + results: [], + is_valid: x?.is_valid, + })); + return HttpResponse.json(responseSubmissions); }), http.get(baseUrl + endpoints.faculties.retrieve.replace('{id}', ':id'), ({ params }) => { return HttpResponse.json(faculties.find((x) => x.id === params.id)); diff --git a/frontend/src/test/unit/services/setup/post_handlers.ts b/frontend/src/test/unit/services/setup/post_handlers.ts index e935501c..1a7f61f7 100644 --- a/frontend/src/test/unit/services/setup/post_handlers.ts +++ b/frontend/src/test/unit/services/setup/post_handlers.ts @@ -59,6 +59,7 @@ export const postHandlers = [ const requestBody = new TextDecoder().decode(buffer); const newGroup = JSON.parse(requestBody); groups.push(newGroup); + console.log(JSON.stringify(newGroup)); return HttpResponse.json(groups); }), http.post(baseUrl + endpoints.projects.byCourse.replace('{courseId}', ':id'), async ({ request }) => { diff --git a/frontend/src/test/unit/services/structure_check.test.ts b/frontend/src/test/unit/services/structure_check.test.ts index 5e8a6c6f..55bf7b8a 100644 --- a/frontend/src/test/unit/services/structure_check.test.ts +++ b/frontend/src/test/unit/services/structure_check.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useStructureCheck } from '@/composables/services/structure_check.service.ts'; import { StructureCheck } from '@/types/StructureCheck'; @@ -8,7 +7,6 @@ const { structureCheck, getStructureCheckByID, getStructureCheckByProject, - createStructureCheck, deleteStructureCheck, } = useStructureCheck(); diff --git a/frontend/src/test/unit/services/student_service.test.ts b/frontend/src/test/unit/services/student_service.test.ts index 6b8ddc12..d9eb1ca9 100644 --- a/frontend/src/test/unit/services/student_service.test.ts +++ b/frontend/src/test/unit/services/student_service.test.ts @@ -1,25 +1,8 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useStudents } from '@/composables/services/student.service.ts'; import { Student } from '@/types/users/Student'; -const { - students, - student, - - getStudentByID, - getStudents, - getStudentsByCourse, - getStudentsByGroup, - - createStudent, - deleteStudent, - - studentJoinCourse, - studentLeaveCourse, - studentJoinGroup, - studentLeaveGroup, -} = useStudents(); +const { students, student, getStudentByID, getStudents, getStudentsByCourse, createStudent } = useStudents(); function resetService(): void { student.value = null; diff --git a/frontend/src/test/unit/services/submission_service.test.ts b/frontend/src/test/unit/services/submission_service.test.ts index a1743670..88009e1d 100644 --- a/frontend/src/test/unit/services/submission_service.test.ts +++ b/frontend/src/test/unit/services/submission_service.test.ts @@ -1,9 +1,4 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, it } from 'vitest'; -describe('submissions', (): void => { - it('gets submissions data by id', async () => {}); -}); -/* +import { describe, it, expect } from 'vitest'; import { useSubmission } from '@/composables/services/submission.service.ts'; const { submissions, submission, getSubmissionByID, getSubmissionByProject, getSubmissionByGroup } = useSubmission(); @@ -18,10 +13,12 @@ describe('submissions', (): void => { resetService(); await getSubmissionByID('1'); + + // Only check for the relevant fields passed in the get handler expect(submission.value).not.toBeNull(); - expect(submission.value?.files).toEqual([]); - expect(submission.value?.submission_number).toBe(1); - expect(submission.value?.submission_time).toEqual(new Date('July 21, 2024 01:15:00')); + expect(submission.value?.submission_number).toEqual(1); + expect(submission.value?.submission_time).toStrictEqual(new Date('July 21, 2024 01:15:00')); + expect(submission.value?.is_valid).toEqual(true); }); it('gets submissions data by group', async () => { @@ -30,15 +27,12 @@ describe('submissions', (): void => { await getSubmissionByGroup('1'); expect(submissions).not.toBeNull(); expect(Array.isArray(submissions.value)).toBe(true); - expect(submissions.value?.length).toBe(2); - - expect(submissions.value?.[0]?.files).toEqual([]); - expect(submissions.value?.[0]?.submission_number).toBe(1); - expect(submissions.value?.[0]?.submission_time).toEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.length).toBe(1); - expect(submissions.value?.[1]?.files).toEqual([]); - expect(submissions.value?.[1]?.submission_number).toBe(2); - expect(submissions.value?.[1]?.submission_time).toEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.[0]?.id).toEqual('1'); + expect(submissions.value?.[0]?.submission_number).toEqual(1); + expect(submissions.value?.[0]?.submission_time).toStrictEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.[0]?.is_valid).toEqual(true); }); it('gets submissions data by project', async () => { @@ -49,13 +43,14 @@ describe('submissions', (): void => { expect(Array.isArray(submissions.value)).toBe(true); expect(submissions.value?.length).toBe(2); - expect(submissions.value?.[0]?.files).toEqual([]); - expect(submissions.value?.[0]?.submission_number).toBe(1); - expect(submissions.value?.[0]?.submission_time).toEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.[0]?.id).toEqual('1'); + expect(submissions.value?.[0]?.submission_number).toEqual(1); + expect(submissions.value?.[0]?.submission_time).toStrictEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.[0]?.is_valid).toEqual(true); - expect(submissions.value?.[1]?.files).toEqual([]); - expect(submissions.value?.[1]?.submission_number).toBe(2); - expect(submissions.value?.[1]?.submission_time).toEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.[1]?.id).toEqual('2'); + expect(submissions.value?.[1]?.submission_number).toEqual(2); + expect(submissions.value?.[1]?.submission_time).toStrictEqual(new Date('July 21, 2024 01:15:00')); + expect(submissions.value?.[1]?.is_valid).toEqual(true); }); }); - */ diff --git a/frontend/src/test/unit/services/submission_status_service.test.ts b/frontend/src/test/unit/services/submission_status_service.test.ts index d7a90797..5b24e7bc 100644 --- a/frontend/src/test/unit/services/submission_status_service.test.ts +++ b/frontend/src/test/unit/services/submission_status_service.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useSubmissionStatus } from '@/composables/services/submission_status.service.ts'; diff --git a/frontend/src/test/unit/services/teacher_service.test.ts b/frontend/src/test/unit/services/teacher_service.test.ts index 66ee99eb..7b946b62 100644 --- a/frontend/src/test/unit/services/teacher_service.test.ts +++ b/frontend/src/test/unit/services/teacher_service.test.ts @@ -1,21 +1,7 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { useTeacher } from '@/composables/services/teacher.service.ts'; -const { - teachers, - teacher, - - getTeacherByID, - getTeachersByCourse, - getTeachers, - - createTeacher, - deleteTeacher, - - teacherJoinCourse, - teacherLeaveCourse, -} = useTeacher(); +const { teachers, teacher, getTeacherByID, getTeachersByCourse, getTeachers } = useTeacher(); function resetService(): void { teacher.value = null; diff --git a/frontend/src/test/unit/types/data.ts b/frontend/src/test/unit/types/data.ts index 255ddc3c..ca641a5b 100644 --- a/frontend/src/test/unit/types/data.ts +++ b/frontend/src/test/unit/types/data.ts @@ -1,3 +1,5 @@ +import { File } from "buffer"; + export const assistantData = { id: 'assistant1_id', username: 'assistant1', @@ -81,12 +83,11 @@ export const facultyData = { name: 'faculty1_name', }; -export const groupData = { - id: 'group1_id', - score: 10, - project: null, - students: [], - submissions: [], +export const submissionStatusData = { + non_empty_groups: 5, + groups_submitted: 4, + structure_checks_passed: 3, + extra_checks_passed: 1, }; export const projectData = { @@ -101,14 +102,23 @@ export const projectData = { max_score: 10, score_visible: true, group_size: 3, - structure_file: null, + structure_file: [], course: courseData, + status: submissionStatusData, structureChecks: [], extra_checks: [], groups: [], submissions: [], }; +export const groupData = { + id: 'group1_id', + score: 10, + project: projectData, + students: [], + submissions: [], +}; + export const responseData = { message: 'response message', }; @@ -121,18 +131,11 @@ export const structureCheckData = { project: null, }; -export const submissionStatusData = { - non_empty_groups: 5, - groups_submitted: 4, - structure_checks_passed: 3, - extra_checks_passed: 1, -}; - export const submissionData = { id: 'submission1_id', submission_number: 1, submission_time: new Date('November 1, 2024 04:20:00'), - files: [], + zip: new File(['byte1', 'byte2', 'byte3'], 'submission.zip', { type: 'application/zip' }), extra_check_results: [], structure_check_results: [], is_valid: true, diff --git a/frontend/src/test/unit/types/group.test.ts b/frontend/src/test/unit/types/group.test.ts index 3806aa63..68870f0d 100644 --- a/frontend/src/test/unit/types/group.test.ts +++ b/frontend/src/test/unit/types/group.test.ts @@ -1,46 +1,38 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; - -describe('placeholder', (): void => { - it('aaaaaaaa', () => {}); +import { Group } from '@/types/Group'; +import { groupData } from './data'; +import { createGroup } from './helper'; + +describe('group type', () => { + it('create instance of group with correct properties', () => { + const group = createGroup(groupData); + + expect(group).toBeInstanceOf(Group); + expect(group.id).toBe(groupData.id); + expect(group.score).toBe(groupData.score); + expect(group.project).toBe(groupData.project); + expect(group.students).toStrictEqual(groupData.students); + expect(group.submissions).toStrictEqual(groupData.submissions); + }); + + it('create a minimal group instance from JSON data', () => { + const groupJSON = { ...groupData }; + const group = Group.fromJSON(groupJSON); + + expect(group).toBeInstanceOf(Group); + expect(group.id).toBe(groupData.id); + expect(group.score).toBe(groupData.score); + }); + + it('create a full group instance from JSON data', () => { + const groupJSON = { ...groupData }; + const group = Group.fromJSONFullObject(groupJSON); + + expect(group).toBeInstanceOf(Group); + expect(group.id).toBe(groupData.id); + expect(group.score).toBe(groupData.score); + expect(group.project).toBe(groupData.project); + expect(group.students).toStrictEqual(groupData.students); + expect(group.submissions).toStrictEqual(groupData.submissions); + }); }); - -// import { describe, it, expect } from 'vitest'; - -// import { Group } from '@/types/Group'; -// import { groupData } from './data'; -// import { createGroup } from './helper'; - -// describe('group type', () => { -// it('create instance of group with correct properties', () => { -// const group = createGroup(groupData); - -// expect(group).toBeInstanceOf(Group); -// expect(group.id).toBe(groupData.id); -// expect(group.score).toBe(groupData.score); -// expect(group.project).toBe(groupData.project); -// expect(group.students).toStrictEqual(groupData.students); -// expect(group.submissions).toStrictEqual(groupData.submissions); -// }); - -// it('create a minimal group instance from JSON data', () => { -// const groupJSON = { ...groupData }; -// const group = Group.fromJSON(groupJSON); - -// expect(group).toBeInstanceOf(Group); -// expect(group.id).toBe(groupData.id); -// expect(group.score).toBe(groupData.score); -// }); - -// it('create a full group instance from JSON data', () => { -// const groupJSON = { ...groupData }; -// const group = Group.fromJSONFullObject(groupJSON); - -// expect(group).toBeInstanceOf(Group); -// expect(group.id).toBe(groupData.id); -// expect(group.score).toBe(groupData.score); -// expect(group.project).toBe(groupData.project); -// expect(group.students).toStrictEqual(groupData.students); -// expect(group.submissions).toStrictEqual(groupData.submissions); -// }); -// }); diff --git a/frontend/src/test/unit/types/helper.ts b/frontend/src/test/unit/types/helper.ts index bab3fede..5346c982 100644 --- a/frontend/src/test/unit/types/helper.ts +++ b/frontend/src/test/unit/types/helper.ts @@ -130,6 +130,7 @@ export function createProject(projectData: any): Project { projectData.score_visible, projectData.group_size, projectData.course, + projectData.status, projectData.structure_file, projectData.structureChecks.slice(), projectData.extra_checks.slice(), @@ -166,7 +167,7 @@ export function createSubmission(submissionData: any): Submission { submissionData.id, submissionData.submission_number, submissionData.submission_time, - submissionData.files.slice(), + submissionData.zip, submissionData.extra_check_results.slice(), submissionData.structure_check_results.slice(), submissionData.is_valid, diff --git a/frontend/src/test/unit/types/project.test.ts b/frontend/src/test/unit/types/project.test.ts index cd5f0296..7548e444 100644 --- a/frontend/src/test/unit/types/project.test.ts +++ b/frontend/src/test/unit/types/project.test.ts @@ -1,55 +1,48 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; +import { Project } from '@/types/Project'; +import { projectData } from './data'; +import { createProject } from './helper'; -describe('placeholder', (): void => { - it('aaaaaaaa', () => {}); -}); - -// import { describe, it, expect } from 'vitest'; - -// import { Project } from '@/types/Project'; -// import { projectData } from './data'; -// import { createProject } from './helper'; +describe('project type', () => { + it('create instance of project with correct properties', () => { + const project = createProject(projectData); -// describe('project type', () => { -// it('create instance of project with correct properties', () => { -// const project = createProject(projectData); + expect(project).toBeInstanceOf(Project); + expect(project.id).toBe(projectData.id); + expect(project.name).toBe(projectData.name); + expect(project.description).toBe(projectData.description); + expect(project.visible).toBe(projectData.visible); + expect(project.archived).toBe(projectData.archived); + expect(project.locked_groups).toBe(projectData.locked_groups); + expect(project.start_date).toStrictEqual(projectData.start_date); + expect(project.deadline).toStrictEqual(projectData.deadline); + expect(project.max_score).toBe(projectData.max_score); + expect(project.score_visible).toBe(projectData.score_visible); + expect(project.group_size).toBe(projectData.group_size); + expect(project.structure_file).toStrictEqual(projectData.structure_file); + expect(project.course).toStrictEqual(projectData.course); + expect(project.status).toStrictEqual(projectData.status); + expect(project.structureChecks).toStrictEqual(projectData.structureChecks); + expect(project.extra_checks).toStrictEqual(projectData.extra_checks); + expect(project.groups).toStrictEqual(projectData.groups); + expect(project.submissions).toStrictEqual(projectData.submissions); + }); -// expect(project).toBeInstanceOf(Project); -// expect(project.id).toBe(projectData.id); -// expect(project.name).toBe(projectData.name); -// expect(project.description).toBe(projectData.description); -// expect(project.visible).toBe(projectData.visible); -// expect(project.archived).toBe(projectData.archived); -// expect(project.locked_groups).toBe(projectData.locked_groups); -// expect(project.start_date).toStrictEqual(projectData.start_date); -// expect(project.deadline).toStrictEqual(projectData.deadline); -// expect(project.max_score).toBe(projectData.max_score); -// expect(project.score_visible).toBe(projectData.score_visible); -// expect(project.group_size).toBe(projectData.group_size); -// expect(project.structure_file).toStrictEqual(projectData.structure_file); -// expect(project.course).toStrictEqual(projectData.course); -// expect(project.structureChecks).toStrictEqual(projectData.structureChecks); -// expect(project.extra_checks).toStrictEqual(projectData.extra_checks); -// expect(project.groups).toStrictEqual(projectData.groups); -// expect(project.submissions).toStrictEqual(projectData.submissions); -// }); + it('create a project instance from JSON data', () => { + const projectJSON = { ...projectData }; + const project = Project.fromJSON(projectJSON); -// it('create a project instance from JSON data', () => { -// const projectJSON = { ...projectData }; -// const project = Project.fromJSON(projectJSON); - -// expect(project).toBeInstanceOf(Project); -// expect(project.id).toBe(projectData.id); -// expect(project.name).toBe(projectData.name); -// expect(project.description).toBe(projectData.description); -// expect(project.visible).toBe(projectData.visible); -// expect(project.archived).toBe(projectData.archived); -// expect(project.locked_groups).toBe(projectData.locked_groups); -// expect(project.start_date).toStrictEqual(projectData.start_date); -// expect(project.deadline).toStrictEqual(projectData.deadline); -// expect(project.max_score).toBe(projectData.max_score); -// expect(project.score_visible).toBe(projectData.score_visible); -// expect(project.group_size).toBe(projectData.group_size); -// }); -// }); + expect(project).toBeInstanceOf(Project); + expect(project.id).toBe(projectData.id); + expect(project.name).toBe(projectData.name); + expect(project.description).toBe(projectData.description); + expect(project.visible).toBe(projectData.visible); + expect(project.archived).toBe(projectData.archived); + expect(project.locked_groups).toBe(projectData.locked_groups); + expect(project.start_date).toStrictEqual(projectData.start_date); + expect(project.deadline).toStrictEqual(projectData.deadline); + expect(project.max_score).toBe(projectData.max_score); + expect(project.score_visible).toBe(projectData.score_visible); + expect(project.group_size).toBe(projectData.group_size); + }); +}); diff --git a/frontend/src/test/unit/types/submission.test.ts b/frontend/src/test/unit/types/submission.test.ts index e8a08750..627ecd56 100644 --- a/frontend/src/test/unit/types/submission.test.ts +++ b/frontend/src/test/unit/types/submission.test.ts @@ -1,14 +1,10 @@ -import { describe, it } from 'vitest'; -/* +import { describe, it, expect } from 'vitest'; +import { File } from "buffer"; + import { Submission } from '@/types/submission/Submission.ts'; import { submissionData } from './data'; import { createSubmission } from './helper'; - */ -/* TODO change files to zip */ -describe('submission type', () => { - it('create instance of submission with correct properties', () => {}); -}); -/* + describe('submission type', () => { it('create instance of submission with correct properties', () => { const submission = createSubmission(submissionData); @@ -17,7 +13,7 @@ describe('submission type', () => { expect(submission.id).toBe(submissionData.id); expect(submission.submission_number).toStrictEqual(submissionData.submission_number); expect(submission.submission_time).toStrictEqual(submissionData.submission_time); - expect(submission.files).toStrictEqual(submissionData.files); + expect(submission.zip).toStrictEqual(submissionData.zip); expect(submission.extraCheckResults).toStrictEqual(submissionData.extra_check_results); expect(submission.structureCheckResults).toStrictEqual(submissionData.structure_check_results); expect(submission.is_valid).toBe(submissionData.is_valid); @@ -28,7 +24,7 @@ describe('submission type', () => { id: 'submission1_id', submission_number: 1, submission_time: new Date('November 1, 2024 04:20:00'), - files: [], + zip: new File(['byte1', 'byte2'], 'submission.zip', { type: 'application/zip' }), results: [], is_valid: true, }; @@ -36,13 +32,12 @@ describe('submission type', () => { const submission = Submission.fromJSON(responseSubmissionJSON); expect(submission).toBeInstanceOf(Submission); - expect(submission.id).toBe(submissionData.id); - expect(submission.submission_number).toStrictEqual(submissionData.submission_number); - expect(submission.submission_time).toStrictEqual(submissionData.submission_time); - expect(submission.files).toStrictEqual(submissionData.files); + expect(submission.id).toBe(responseSubmissionJSON.id); + expect(submission.submission_number).toStrictEqual(responseSubmissionJSON.submission_number); + expect(submission.submission_time).toStrictEqual(responseSubmissionJSON.submission_time); + expect(submission.zip).toStrictEqual(responseSubmissionJSON.zip); expect(submission.extraCheckResults).toStrictEqual([]); expect(submission.structureCheckResults).toStrictEqual([]); - expect(submission.is_valid).toBe(submissionData.is_valid); + expect(submission.is_valid).toBe(responseSubmissionJSON.is_valid); }); }); - */ diff --git a/frontend/src/types/submission/Submission.ts b/frontend/src/types/submission/Submission.ts index 32b57745..6c70b2a9 100644 --- a/frontend/src/types/submission/Submission.ts +++ b/frontend/src/types/submission/Submission.ts @@ -45,7 +45,7 @@ export class Submission { } /** - * Convert a submission object to a submission instance. + * Convert a ResponseSubmission object to a submission instance. * * @param submission */ diff --git a/production.yml b/production.yml index e80fa8ae..7c40cc42 100644 --- a/production.yml +++ b/production.yml @@ -34,7 +34,7 @@ x-common-keys-selab: &common-keys-selab services: nginx: <<: *common-keys-selab - image: nginx:alpine-slim + image: nginx:latest container_name: nginx_prod ports: - 80:80 @@ -62,7 +62,7 @@ services: container_name: backend_prod build: context: $BACKEND_DIR - dockerfile: Dockerfile.prod + dockerfile: Dockerfile command: sh -c "./setup.sh && gunicorn --config gunicorn_config.py ypovoli.wsgi:application" depends_on: - postgres @@ -80,11 +80,10 @@ services: container_name: celery_prod build: context: $BACKEND_DIR - dockerfile: Dockerfile.prod + dockerfile: Dockerfile command: celery -A ypovoli worker -l ERROR volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /var/lib/docker/overlay2:/var/lib/docker/overlay2 + - ${BACKEND_DIR}:/code depends_on: - backend - redis diff --git a/test.yml b/test.yml index 24ec2d9b..05051584 100644 --- a/test.yml +++ b/test.yml @@ -34,7 +34,7 @@ x-common-keys-selab_test: &common-keys-selab_test services: nginx: <<: *common-keys-selab_test - image: nginx:alpine-slim + image: nginx:latest container_name: nginx volumes: - ${DATA_DIR}/nginx/nginx.test.conf:/etc/nginx/nginx.conf:ro @@ -48,7 +48,7 @@ services: container_name: backend build: context: $BACKEND_DIR - dockerfile: Dockerfile.dev + dockerfile: Dockerfile command: sh -c "./setup.sh && python manage.py runsslserver 0.0.0.0:8000" volumes: - $BACKEND_DIR:/code @@ -60,7 +60,7 @@ services: container_name: celery build: context: $BACKEND_DIR - dockerfile: Dockerfile.dev + dockerfile: Dockerfile command: sh -c "./setup.sh && celery -A ypovoli worker -l DEBUG" volumes: - $BACKEND_DIR:/code