From f01a03d747a151d59b0291f333bbd2d9cc7f2204 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 15:47:48 +0100 Subject: [PATCH 01/18] chore: startup of file structure checks --- backend/api/views/check_folder_structure.py | 137 ++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 backend/api/views/check_folder_structure.py diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py new file mode 100644 index 00000000..9c9e5279 --- /dev/null +++ b/backend/api/views/check_folder_structure.py @@ -0,0 +1,137 @@ +import zipfile +import os + +data_directory = "../../../data" + + +def get_parent_directories(dir_path): + components = dir_path.split('/') + parents = set() + current_path = "" + for component in components: + if current_path != "": + current_path = current_path + "/" + component + else: + current_path = component + parents.add(current_path) + return parents + + +def list_zip_directories(zip_file_path): + """ + List all directories in a zip file. + :param zip_file_path: Path where the zip file is located. + :return: List of directory names. + """ + dirs = set() + with zipfile.ZipFile(zip_file_path, 'r') as zip_ref: + info_list = zip_ref.infolist() + for file in info_list: + if file.is_dir(): + dir_path = file.filename[:-1] + parent_dirs = get_parent_directories(dir_path) + dirs.update(parent_dirs) + return dirs + + +def get_zip_structure(root_path): + directory_structure = {} + base, _ = os.path.splitext(root_path) + inhoud = list_zip_directories(root_path) + inhoud.add(".") + # print("inhoud") + # print(inhoud) + for inh in inhoud: + directory_structure[inh] = { + 'obligated_extensions': set(), + 'blocked_extensions': set() + } + with zipfile.ZipFile(root_path, 'r') as zip_file: + file_names = zip_file.namelist() + for file_name in file_names: + # print(file_name) + parts = file_name.rsplit('/', 1) # You can also use '\\' if needed + if len(parts) == 2: + map, file = parts + _, file_extension = os.path.splitext(file) + file_extension = file_extension[1:] + if not file_extension == "": + directory_structure[map]["obligated_extensions"].add(file_extension) + else: + _, file_extension = os.path.splitext(file_name) + file_extension = file_extension[1:] + directory_structure["."]["obligated_extensions"].add(file_extension) + return directory_structure + + +def check_zip_content(root_path, dir_path, obligated_extensions, blocked_extensions): + """ + Check the content of a directory without traversing subdirectories. + :param dir_path: The path of the zip we need to check. + :param obligated_extensions: The file extensions that are obligated to be present. + :param blocked_extensions: The file extensions that are forbidden to be present. + :return: + True: All checks pass. + False: At least 1 blocked extension is found or 1 obligated extension is not found. + """ + dir_path = dir_path.replace('\\', '/') + # print(f"looking in the {dir_path} subdirectory") + with zipfile.ZipFile(root_path, 'r') as zip_file: + zip_contents = set(zip_file.namelist()) + #print(zip_contents) + found_obligated = set() # To track found obligated extensions + if dir_path == ".": + files_in_subdirectory = [file for file in zip_contents if "/" not in file] + else: + files_in_subdirectory = [file[len(dir_path)+1:] for file in zip_contents if (file.startswith(dir_path) and '/' not in file[len(dir_path)+1:] and file[len(dir_path)+1:] != "")] + # print(files_in_subdirectory) + for file in files_in_subdirectory: + _, file_extension = os.path.splitext(file) + file_extension = file_extension[1:] # file_extension[1:] to remove the . + # print(file_extension) + if file_extension in blocked_extensions: + print(f"Error: {file_extension} found in '{dir_path}' is not allowed.") + return False + elif file_extension in obligated_extensions: + found_obligated.add(file_extension) + return set(obligated_extensions) <= found_obligated + + +def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders=False): + """ + Check the structure of a zip file. + :param zip_file_path: Path to the zip file. + :param folder_structure: Dictionary representing the expected folder structure. + :return: + True: Zip file structure matches the expected structure. + False: Zip file structure does not match the expected structure. + """ + base, _ = os.path.splitext(zip_file_path) + struc = [f for f in folder_structure.keys() if not f == "."] + # print(struc) + dirs = list_zip_directories(zip_file_path) + for dir in struc: + if dir not in dirs: + print(f"Error: Directory '{dir}' not defined.") + return False + + with zipfile.ZipFile(zip_file_path, 'r') as zip_file: + # zip_contents = set(zip_file.namelist()) + # print(f"all contents in the zip are {zip_contents}") + for directory, info in folder_structure.items(): + # base_name, _ = os.path.splitext(zip_file_path) + obligated_extensions = info.get('obligated_extensions', set()) + blocked_extensions = info.get('blocked_extensions', set()) + + result = check_zip_content(zip_file_path, directory, obligated_extensions, blocked_extensions) + if not result: + return False + # Check for any directories not present in the folder structure dictionary + # print(struc) + # print(dirs) + if restrict_extra_folders: + for actual_directory in dirs: + if actual_directory not in struc: + print(f"Error: Directory '{actual_directory}' not defined in the folder structure dictionary.") + return False + return True From b75c47b815c8a0ea9f6c9a51cde77be30fc62a21 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 17:29:41 +0100 Subject: [PATCH 02/18] chore: fxed get structure --- backend/api/views/check_folder_structure.py | 25 ++++++++++++++++++--- backend/api/views/project_view.py | 1 + 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index 9c9e5279..3c878932 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -1,7 +1,28 @@ import zipfile import os +from api.models.checks import StructureCheck +from api.models.project import Project +from api.models.extension import FileExtension + +data_directory = "../../../data" # ../data\structures\zip_struct1.zip + + +def parseZipFile(project, dir_path): + struct = get_zip_structure(dir_path) + for key, value in struct.items(): + check = StructureCheck.objects.create( + name=key, + project=project + ) + for ext in value["obligated_extensions"]: + extensie, _ = FileExtension.objects.get_or_create( + extension=ext + ) + print(extensie) + check.obligated_extensions.add(extensie.id) + project.structure_checks.add(check) + print(key, value) -data_directory = "../../../data" def get_parent_directories(dir_path): @@ -39,8 +60,6 @@ def get_zip_structure(root_path): base, _ = os.path.splitext(root_path) inhoud = list_zip_directories(root_path) inhoud.add(".") - # print("inhoud") - # print(inhoud) for inh in inhoud: directory_structure[inh] = { 'obligated_extensions': set(), diff --git a/backend/api/views/project_view.py b/backend/api/views/project_view.py index 1b2c37d6..d14111b0 100644 --- a/backend/api/views/project_view.py +++ b/backend/api/views/project_view.py @@ -1,6 +1,7 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response +from api.views.check_folder_structure import get_zip_structure, check_zip_structure, data_directory, parseZipFile from ..models.project import Project from ..serializers.project_serializer import ProjectSerializer from ..serializers.group_serializer import GroupSerializer From 620b906f1fb8d3346803884835583bb050ae9b97 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 20:44:54 +0100 Subject: [PATCH 03/18] chore: fixed reading zip files --- backend/api/fixtures/checks.yaml | 12 ------------ backend/api/views/check_folder_structure.py | 1 - 2 files changed, 13 deletions(-) diff --git a/backend/api/fixtures/checks.yaml b/backend/api/fixtures/checks.yaml index ac162764..134302e7 100644 --- a/backend/api/fixtures/checks.yaml +++ b/backend/api/fixtures/checks.yaml @@ -1,15 +1,3 @@ -- model: api.structurecheck - pk: 1 - fields: - name: '.' - project: 123456 - obligated_extensions: - - 3 - - 4 - blocked_extensions: - - 1 - - 2 - - model: api.extracheck pk: 1 fields: diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index 3c878932..43634c57 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -18,7 +18,6 @@ def parseZipFile(project, dir_path): extensie, _ = FileExtension.objects.get_or_create( extension=ext ) - print(extensie) check.obligated_extensions.add(extensie.id) project.structure_checks.add(check) print(key, value) From 72a0ee2c3496baa0dd6a4718eb3cf4aee64cd88f Mon Sep 17 00:00:00 2001 From: Bram Meir Date: Thu, 7 Mar 2024 21:00:35 +0100 Subject: [PATCH 04/18] chore: one link for checks in project --- backend/api/serializers/project_serializer.py | 10 +++-- backend/api/views/project_view.py | 43 +++++++++++++++++-- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/backend/api/serializers/project_serializer.py b/backend/api/serializers/project_serializer.py index f9c458d5..5f4ee39f 100644 --- a/backend/api/serializers/project_serializer.py +++ b/backend/api/serializers/project_serializer.py @@ -7,12 +7,14 @@ class ProjectSerializer(serializers.ModelSerializer): many=False, read_only=True, view_name="course-detail" ) - structure_checks = serializers.HyperlinkedRelatedField( - many=True, read_only=True, view_name="structure-check-detail" + structure_checks = serializers.HyperlinkedIdentityField( + view_name="project-structure-checks", + read_only=True ) - extra_checks = serializers.HyperlinkedRelatedField( - many=True, read_only=True, view_name="extra-check-detail" + extra_checks = serializers.HyperlinkedIdentityField( + view_name="project-extra-checks", + read_only=True ) groups = serializers.HyperlinkedIdentityField( diff --git a/backend/api/views/project_view.py b/backend/api/views/project_view.py index d14111b0..99d2eca6 100644 --- a/backend/api/views/project_view.py +++ b/backend/api/views/project_view.py @@ -1,10 +1,13 @@ -from rest_framework import viewsets, status +from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.exceptions import NotFound +from django.utils.translation import gettext_lazy as _ from api.views.check_folder_structure import get_zip_structure, check_zip_structure, data_directory, parseZipFile from ..models.project import Project from ..serializers.project_serializer import ProjectSerializer from ..serializers.group_serializer import GroupSerializer +from ..serializers.checks_serializer import StructureCheckSerializer, ExtraCheckSerializer class ProjectViewSet(viewsets.ModelViewSet): @@ -27,6 +30,40 @@ def groups(self, request, pk=None): except Project.DoesNotExist: # Invalid project ID - return Response( - status=status.HTTP_404_NOT_FOUND, data={"message": "Project not found"} + raise NotFound(_('project.status.not_found')) + + @action(detail=True, methods=["get"]) + def structure_checks(self, request, pk=None): + """Returns the structure checks for the given project""" + + try: + queryset = Project.objects.get(id=pk) + checks = queryset.structure_checks.all() + + # Serialize the check objects + serializer = StructureCheckSerializer( + checks, many=True, context={"request": request} ) + return Response(serializer.data) + + except Project.DoesNotExist: + # Invalid project ID + raise NotFound(_('project.status.not_found')) + + @action(detail=True, methods=["get"]) + def extra_checks(self, request, pk=None): + """Returns the extra checks for the given project""" + + try: + queryset = Project.objects.get(id=pk) + checks = queryset.extra_checks.all() + + # Serialize the check objects + serializer = ExtraCheckSerializer( + checks, many=True, context={"request": request} + ) + return Response(serializer.data) + + except Project.DoesNotExist: + # Invalid project ID + raise NotFound(_('project.status.not_found')) From 196e195eb90406ce06afda054cba044710ff6cc1 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 21:01:37 +0100 Subject: [PATCH 05/18] chore: added retrieving of structure checks for project --- backend/api/views/check_folder_structure.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index 43634c57..e580e95c 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -20,8 +20,21 @@ def parseZipFile(project, dir_path): ) check.obligated_extensions.add(extensie.id) project.structure_checks.add(check) - print(key, value) - + # print(key, value) + + +def checkZipFile(project, dir_path): + project_structure_checks = StructureCheck.objects.filter(project=project.id) + for struct in project_structure_checks: + print(struct.id) + print(struct.name) + print("obligated_extensions") + for ext in struct.obligated_extensions.all(): + print(" ", ext.extension) + print("blocked_extensions") + for ext in struct.blocked_extensions.all(): + print(" ", ext.extension) + print("=========================") def get_parent_directories(dir_path): From b1dbb18012951c69a446fbec604081559852189a Mon Sep 17 00:00:00 2001 From: Bram Meir Date: Thu, 7 Mar 2024 21:08:09 +0100 Subject: [PATCH 06/18] fix: update tests --- backend/api/tests/test_project.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/api/tests/test_project.py b/backend/api/tests/test_project.py index 03dfe7cd..126e4257 100644 --- a/backend/api/tests/test_project.py +++ b/backend/api/tests/test_project.py @@ -371,7 +371,7 @@ def test_project_structure_checks(self): self.assertEqual(retrieved_project["archived"], project.archived) self.assertEqual(retrieved_project["course"], expected_course_url) - response = self.client.get(retrieved_project["structure_checks"][0], follow=True) + response = self.client.get(retrieved_project["structure_checks"], follow=True) # Check if the response was successful self.assertEqual(response.status_code, 200) @@ -380,7 +380,7 @@ def test_project_structure_checks(self): self.assertEqual(response.accepted_media_type, "application/json") # Parse the JSON content from the response - content_json = json.loads(response.content.decode("utf-8")) + content_json = json.loads(response.content.decode("utf-8"))[0] self.assertEqual(int(content_json["id"]), checks.id) @@ -437,7 +437,7 @@ def test_project_extra_checks(self): retrieved_project = content_json[0] - response = self.client.get(retrieved_project["extra_checks"][0], follow=True) + response = self.client.get(retrieved_project["extra_checks"], follow=True) # Check if the response was successful self.assertEqual(response.status_code, 200) @@ -446,7 +446,7 @@ def test_project_extra_checks(self): self.assertEqual(response.accepted_media_type, "application/json") # Parse the JSON content from the response - content_json = json.loads(response.content.decode("utf-8")) + content_json = json.loads(response.content.decode("utf-8"))[0] self.assertEqual(int(content_json["id"]), checks.id) self.assertEqual(content_json["project"], "http://testserver" + reverse( From aee9ad8f16028dabc1555ccc462cd1933ab82cc1 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 21:31:55 +0100 Subject: [PATCH 07/18] chore: finalized initialisation of file checks and added data --- backend/api/views/check_folder_structure.py | 18 ++++++++++++++++-- data/structures/mixed_template.zip | Bin 0 -> 822 bytes data/structures/only_files_template.zip | Bin 0 -> 474 bytes data/structures/only_folders_template.zip | Bin 0 -> 638 bytes data/structures/remake.zip | Bin 0 -> 3234 bytes data/structures/root.zip | Bin 0 -> 226 bytes data/structures/zip_struct1.zip | Bin 0 -> 2136 bytes data/tests/mixed.zip | Bin 0 -> 1660 bytes data/tests/only_files.zip | Bin 0 -> 618 bytes data/tests/only_folders.zip | Bin 0 -> 990 bytes data/tests/test_zip1struct1.zip | Bin 0 -> 2136 bytes data/tests/test_zip2struct1.zip | Bin 0 -> 1988 bytes data/tests/test_zip3struct1.zip | Bin 0 -> 2258 bytes data/tests/test_zip4struct1.zip | Bin 0 -> 1822 bytes 14 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 data/structures/mixed_template.zip create mode 100644 data/structures/only_files_template.zip create mode 100644 data/structures/only_folders_template.zip create mode 100644 data/structures/remake.zip create mode 100644 data/structures/root.zip create mode 100644 data/structures/zip_struct1.zip create mode 100644 data/tests/mixed.zip create mode 100644 data/tests/only_files.zip create mode 100644 data/tests/only_folders.zip create mode 100644 data/tests/test_zip1struct1.zip create mode 100644 data/tests/test_zip2struct1.zip create mode 100644 data/tests/test_zip3struct1.zip create mode 100644 data/tests/test_zip4struct1.zip diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index e580e95c..742788b3 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -23,8 +23,9 @@ def parseZipFile(project, dir_path): # print(key, value) -def checkZipFile(project, dir_path): +def checkZipFile(project, dir_path, restrict_extra_folders=False): project_structure_checks = StructureCheck.objects.filter(project=project.id) + """ for struct in project_structure_checks: print(struct.id) print(struct.name) @@ -35,6 +36,19 @@ def checkZipFile(project, dir_path): for ext in struct.blocked_extensions.all(): print(" ", ext.extension) print("=========================") + """ + + structuur = {} + for struct in project_structure_checks: + structuur[struct.name] = { + 'obligated_extensions': set(), + 'blocked_extensions': set() + } + for ext in struct.obligated_extensions.all(): + structuur[struct.name]["obligated_extensions"].add(ext.extension) + for ext in struct.blocked_extensions.all(): + structuur[struct.name]["blocked_extensions"].add(ext.extension) + return check_zip_structure(structuur, dir_path, restrict_extra_folders=restrict_extra_folders) def get_parent_directories(dir_path): @@ -109,7 +123,7 @@ def check_zip_content(root_path, dir_path, obligated_extensions, blocked_extensi # print(f"looking in the {dir_path} subdirectory") with zipfile.ZipFile(root_path, 'r') as zip_file: zip_contents = set(zip_file.namelist()) - #print(zip_contents) + # print(zip_contents) found_obligated = set() # To track found obligated extensions if dir_path == ".": files_in_subdirectory = [file for file in zip_contents if "/" not in file] diff --git a/data/structures/mixed_template.zip b/data/structures/mixed_template.zip new file mode 100644 index 0000000000000000000000000000000000000000..456c258b7a948ec1e68ae92e48baa5437412adde GIT binary patch literal 822 zcmWIWW@h1H0D-^BX%S!sl;B{HVMxo*Nl7g-)DI2eWMI|}d6D`$_(f`I1vdjD%L`@( z1~3tT(*$886UtJHigOav^$Jqb;D&(=#A(=LoX#q9(d ztPAxTGtea<^KiNb-7IOWW|b$Fq!#I=XF|LPG8BX{3}s}JW5yNc641~WV0h~YVj^OI z6%qp^m?q2sGmU{^Nuv{zX(+Kl*kH`KfEf&OWw>PZU#n{ z7t9O{U?Ko+jxL%xoIrCja}(23^$POR!A7(JwSzEDBecMSO(WoE hbkk5>c>&2ZQ0|N^X`Y})LL8nLpm^1YecoxR!lv3JKiw)%eF`_An9-tL2X;~K%wohRLW z{xJTzL0TlGN&2)q=!KN6B`F&Q=~{d5@l{fPzcr#~P{X6^q}CYK3E@AxMzvLgS{N}J zqyu)m!w&ox(FtYVVUi;dV?=Bz4Yv%4WcaM_$K%E?LGg`EbyHDYaj3qeEa~|NUN{Jj zc;BBv9n0AFeT8t_K}cyo?)h*O!ul_u{b3sYjmr8t$wQtP2XR=&anxg2cHlC@;;_s8 z!V|$V0s_S^`#G-*m;Qm`g-z90R81FD+@AF|z6$rE9!&Hn^>EMxPJ6oh>>2m~%qFBlVkfI60upxRS~a1zC^G~;7F z1KNaDBGhJX)lGZ;=6K`Jk>8v6sNeccvGu1=UL~0_A#Y!88!uGI8aWrS?eiDUqpHWehToC_HZ=ZPG&e0Rh90xa#yHnTQz@9?O; zR%YeYEHT_@_1Kmt(06C5nab0{eS+oeD0=$Z3d1A;f_4c@C(AjK5zxs$4uX;@fYT<5 z#Hj-GHH%eWs z^EnEKp$?QtA)q)`YdM25%kVB-R-hCT32@pplKz$RsEAu=+Wo_!WI6#&n@p0XTDX#1 bh{K&$pvj~%S=pS${F#IAJ9zp9nuPoZU*!%e literal 0 HcmV?d00001 diff --git a/data/structures/root.zip b/data/structures/root.zip new file mode 100644 index 0000000000000000000000000000000000000000..4e703157f5a0929e00ca8b7dd224e0f3e31150ce GIT binary patch literal 226 zcmWIWW@Zs#0D+R2vO?J3$a1~ hY7?3*2(8%cL1@)Rbs)%ARyL3{6A+dG=`aw70RSa#9%=vp literal 0 HcmV?d00001 diff --git a/data/structures/zip_struct1.zip b/data/structures/zip_struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..bd156cd2a3301c7dd2b28cc7520b8b7447a1ddb4 GIT binary patch literal 2136 zcma)-y-ve05XaLGSYfEdz(Dwz7!ulUAQmK8I?#~~g~V+m5*pbl(C!R80(}D}76cLx zz{<=6u(BZ-v1`b=t}l(F8}k84(18XDOW05}_|S^lSkZ<{i5JAgi37ak zDxS@#uate$%A#U5T#+bd$?=l}D!*$0!^FvXkpMn8=fvr$gjbC)r-f0N@R3c1&_ZFo zo5~OW)~cd?H(frg=}{OF=A=v+lTVGXhG{MdBD>uxm1`Ehs+_=&J$V*gTp*fskf>!I z4*0EE0W2~O5vweZfL!q#fn@v0MFyN{wI$Bx;i(8N1OG2N&`qlj^lAxhc6#vvXj**$ z9?l0&i~xA~pX3JP31a|=$r_-k7aXD(1<>@CgjL8RKJX7tlg0rclXXat%|pG6h_4$7 z08Lf`K$;I!j0K1$YXRb8J`s!tfVz0%-%%bR-WLWG#sd(O^+<@#!*&G`ZpMg!XtE+8 XM)^c(Obd%5VToUNRm-~PDVFsMDkRQV literal 0 HcmV?d00001 diff --git a/data/tests/mixed.zip b/data/tests/mixed.zip new file mode 100644 index 0000000000000000000000000000000000000000..509911007ed853f9249aa3ede21c717e5d25b976 GIT binary patch literal 1660 zcmb8wPfo%>6bA5z1*5nTFvbXB>5dYL9zeIcAV!ICCDMWz3ojTb(&}VVmy9#fj%LY`SHyZlEq*1@T*aW9`J`WCX zJ-uu}COdZk!b{64loVkWOdf}wzB~4N0wd6h{aWLAEY2DJy0MB-5OKWvY~TkY_hEb( zh9QcUHB7D>pG{Gr*o?edG^J~Fj+R-DX6+lTZfEM=kAr@guZM2@^!40TF%BB;m(h>cDSZ#g zGVwjoGj`VaY^YkO8LJ=@>t!g$61Npitr@y0ZP4AaaB0#XMpvnv?E$%UJW`Oy?+E`m Ievkq91EnNEO#lD@ literal 0 HcmV?d00001 diff --git a/data/tests/only_files.zip b/data/tests/only_files.zip new file mode 100644 index 0000000000000000000000000000000000000000..55a7402c58fd5025e0827be9af1f98886c1802d0 GIT binary patch literal 618 zcmWIWW@h1H0D;9xX%S!sl;C5KVaQ0$$;mIzFUm>L%StQ?4dG;9UKscy6@*JGxEUB( zUNAE-fQbOOIcw0&;RKqKnVXoNs#lPg4mM&lP&)|YG-3{#5gb4xit|$o^hzp9z$Q!w z>IPw)CQL&!fg5N-S!z*nPGY)VK}s6fkZzy>AdF!MBa<96t}u~+2hoy75EDIwSRo;V z$4t!dK{nF^XeLr<0gb~NQV1tvh841L-9Y0IAqO-KYp5Yi!wffM)6N1-LkUHofmlP3 Ul?|wpfdvSi7#SFZfS7>+00f?N$p8QV literal 0 HcmV?d00001 diff --git a/data/tests/only_folders.zip b/data/tests/only_folders.zip new file mode 100644 index 0000000000000000000000000000000000000000..012a030475b2e794d5b13dfec96d37dce82581db GIT binary patch literal 990 zcmb8t%?g4*5C`xvHLNb-sYCb%B6;j)bcrqo1<@%YJq1P|(6J}znR=S=(mOPxtGnr% zrtGp8e=|FO{kf`jfu7?oTqg&%88Cz}I_!hf$XP8rpiUQYFvfMg^+3^L6(HICTsRj5 zQxKSByQwuphrGs?CAD(ZW<8QitI~}c^vEm^9a@#vy&-s`pP1{UcXP9w zkwvT2jncBzssslt%Ran4I`TaAG|wMeIZaWU2wI*3W{a+L{=CbgYk}xKq;6gznMQq$ za>`5q5J)@# zD>Dzk%7$RXt|8~TzBI0q>vF%lobUf{w<^_|B~Im_dph~^_uAsIJBR{8J1nI`Kdrk# zNQs{g=;(y@yo+#5sMBZdjIi68Cala7*zhd&lDdm0t>Z~4iId1n2~*PV_NQy^6iLrO zj=)_*GtU}r6Q^Ag$|A4l1Os0IJZON8`G6$oLIZ_I*idHhp%t~Uq79c4FNlc~2T*cP zyR}t3n^9jW`=*seht+UJqSz(JPZFs7t^o`iC-+4H_~M)sr>_!THNu=0#$m!&HW@(+ z`p2Uy+f+XMTdRup-E{e~rpIAKn3FPPO+Gck8a8uD5ZUcksa&&gsd558_T*huxIi@R zokT73aL6Cc3P8v>L`Yd40lDH20?G8qhYUEAv?b2w;i(8N1OE#h=q9NHy;?$>ofbX- zP0|P8(R|><2!NM=B{vvP7z02|)&Nbt;1I|f`4$DG!6intV4oq9_nRe z__~n*&}1b5r1?O_Sb%7<79c+66TxTzs1HwAjXYv_Ul>pr4?s-TBOx{q+ZAMRGe!hN alNA9m$|p)=T38ebOZ<0RwXA!dVp+et6wGG; literal 0 HcmV?d00001 diff --git a/data/tests/test_zip2struct1.zip b/data/tests/test_zip2struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..fbce9306ed2d477e01496bd8c176376a7840c84d GIT binary patch literal 1988 zcmb7_u};G<5QbA)7!gAy1_nZ5Vn}Ga!G;739q7mgA#qEjgj%)>v^y`sOE4gT!~?MM z6i7_0Fk;t`bB(VJ7bTbFzPp_7zjQm*THRAt-Rqr9UVa{1()R{gLf9!6Y#57Xn52xv zV!%entRG#bW5R+9-Yp1?vuVQWUjiSV=Mim&c+dtOBuGxPNDyv>-|bH4+$j@YfC7cP zfnuI4+9poBB8+EIKS&0#0l2pSjrjnY^q_#!Eo>P*c(aoFSkhL=NR;Fx$P+x}8XnDA zY@~g)(xT03g(^_ZQV>%LnP2_~7^hSY9326mbhp|@*`HQc$DHdaZHIEy#9^8d9tdt6 z@`puO$2z77D!SXLEYv+*Xp$h!pFGRT4v4NkXw-^y`|`Gg3^K$aN{HGNao)Hq+d$1=DA{3}zSyMzk#S_Lh3TDAam2^W9|^MMl?18??`++sW>Yk;_f4MjcU z5JTobvrAG!*QO5m3#Vz>1IQ(O4f3UHE+Eaf*8l)rivWOZHW1bV0PRmzeWsu%Mj1|1~c=ijsO4v literal 0 HcmV?d00001 diff --git a/data/tests/test_zip3struct1.zip b/data/tests/test_zip3struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..315fd52e9e46cda79198cf42847465798587c850 GIT binary patch literal 2258 zcma)+y-ve05P;Jkm^)NrU?BWV3<+(SfC&i}20C_wkhpC`LL(;y+MSo+4VYLENIU>5 zGfOu%#D-wRt|8~RzBH~9cZk2cT<*U1QKecp_^Rx7JJUaZw+*&;2Z2ZENu1E3n>1|C zr^HPLbaYI6&V@fF)au9Wl+ZcN5|(BO@$f8kqK1thTE!0~B#Z(lA+dD)b|)KitH|*b zWO3XzB=e-u)^XY;p>g2!EN|dSfO`e7HXjgqT}YsC37b+4-j$#x7PM(o;&>skLJtqQ zjGvCFD}{Yk!lK1$+Wa8zCCiN>i2SAiG#w}UMF3dioD-`r6J8X;oD{}>#9B5QK?;TI z-I8AXTZxM1-LhG;CWn4NVk?QIHu+QtD_G41fuDw)KlZXMkB-?k%ehpp8~A*)uxy#r zd(mkJqON!XRb$&eyA*%G^y3gd|6*~-6}u2fI>ec`)cHW*Otw81!KL7TJ3z8JKLEX2 zLX*wf0)RSS0PtWwaB2_W<$sbJjK^&P5S>pTsb?G_*#&57O587E@elli(}ZmRr1Omc znQUv6k>acN0ie!50Hpaq$wolb`3Q&)`9!c20M+90Sr>~J?+XKRTLDDpD*-Xtwpl?6 rH)Ag#>ih-7AfG5~1~8q^1dPAq9R}leTUg`|*4S@SHH-;+)G&Sl8(!uQ literal 0 HcmV?d00001 diff --git a/data/tests/test_zip4struct1.zip b/data/tests/test_zip4struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..9acfea0241389ce71f27778ac5980ed9b7272931 GIT binary patch literal 1822 zcma)+F;9a)6o6?_Hxq{@4w^{R#7RioO*aRZ4l=qa!cj&tHBY`y$5EaYX0^<8&7Ct{0`0gnUZp zXLJ}`rwc;eg!z92epx1L{wJ{6)g;Ke9wa>-m6P`Hq#HunI2gLoG`s`AqXyWm2V_wU z`Hj2o6}nz(MIEeY&!Z%WCd8dYc*!k1o6)dP_Ejs34y)(MM0tE}m}OA;O#>JXSsjrA zh_f0IHz^WcG(t@ai!>8oNj8TT8qd35*!Y)L745t4al-g%Iws8Jtnit9YJ?qZ=87P* z{XwhUv2Z&CK`bYKHgyGvra36oh6pF(*6ag!0AjL7K^zfoaY)%E0Gez9;B!4NcMEvc zyZpv@(lvmXY=fo_Tga$N?g342N!q8D9Q+8UDHj1`vQdE?5gw{R;3fb~b^>r&4J^0{ zM3b#RJZPhnlY0es0jLj8{^Qk>!%HxrbQvHfn-#eiOTiWHJPw3 Mt`FO?w#98(KeNV#0RR91 literal 0 HcmV?d00001 From 20f6ad5870f4f28140afdf66e4f9ac912820a832 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 22:28:08 +0100 Subject: [PATCH 08/18] chore: add i18n for errors --- backend/api/views/check_folder_structure.py | 46 +++++++-------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index 742788b3..4faa40d9 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -3,6 +3,7 @@ from api.models.checks import StructureCheck from api.models.project import Project from api.models.extension import FileExtension +from django.utils.translation import gettext data_directory = "../../../data" # ../data\structures\zip_struct1.zip @@ -20,24 +21,10 @@ def parseZipFile(project, dir_path): ) check.obligated_extensions.add(extensie.id) project.structure_checks.add(check) - # print(key, value) def checkZipFile(project, dir_path, restrict_extra_folders=False): project_structure_checks = StructureCheck.objects.filter(project=project.id) - """ - for struct in project_structure_checks: - print(struct.id) - print(struct.name) - print("obligated_extensions") - for ext in struct.obligated_extensions.all(): - print(" ", ext.extension) - print("blocked_extensions") - for ext in struct.blocked_extensions.all(): - print(" ", ext.extension) - print("=========================") - """ - structuur = {} for struct in project_structure_checks: structuur[struct.name] = { @@ -120,26 +107,27 @@ def check_zip_content(root_path, dir_path, obligated_extensions, blocked_extensi False: At least 1 blocked extension is found or 1 obligated extension is not found. """ dir_path = dir_path.replace('\\', '/') - # print(f"looking in the {dir_path} subdirectory") with zipfile.ZipFile(root_path, 'r') as zip_file: zip_contents = set(zip_file.namelist()) - # print(zip_contents) found_obligated = set() # To track found obligated extensions if dir_path == ".": files_in_subdirectory = [file for file in zip_contents if "/" not in file] else: files_in_subdirectory = [file[len(dir_path)+1:] for file in zip_contents if (file.startswith(dir_path) and '/' not in file[len(dir_path)+1:] and file[len(dir_path)+1:] != "")] - # print(files_in_subdirectory) + for file in files_in_subdirectory: _, file_extension = os.path.splitext(file) file_extension = file_extension[1:] # file_extension[1:] to remove the . - # print(file_extension) + if file_extension in blocked_extensions: - print(f"Error: {file_extension} found in '{dir_path}' is not allowed.") - return False + # print(f"Error: {file_extension} found in '{dir_path}' is not allowed.") TODO + return False, gettext('zip.errors.invalid_structure.blocked_extension_found') elif file_extension in obligated_extensions: found_obligated.add(file_extension) - return set(obligated_extensions) <= found_obligated + if set(obligated_extensions) <= found_obligated: + return True, gettext('zip.success') + else: + return False, gettext('zip.errors.invalid_structure.obligated_extension_not_found') def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders=False): @@ -157,8 +145,8 @@ def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders= dirs = list_zip_directories(zip_file_path) for dir in struc: if dir not in dirs: - print(f"Error: Directory '{dir}' not defined.") - return False + # print(f"Error: Directory '{dir}' not defined.") TODO + return False, gettext('zip.errors.invalid_structure.directory_not_defined') with zipfile.ZipFile(zip_file_path, 'r') as zip_file: # zip_contents = set(zip_file.namelist()) @@ -168,15 +156,13 @@ def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders= obligated_extensions = info.get('obligated_extensions', set()) blocked_extensions = info.get('blocked_extensions', set()) - result = check_zip_content(zip_file_path, directory, obligated_extensions, blocked_extensions) + result, message = check_zip_content(zip_file_path, directory, obligated_extensions, blocked_extensions) if not result: - return False + return result, message # Check for any directories not present in the folder structure dictionary - # print(struc) - # print(dirs) if restrict_extra_folders: for actual_directory in dirs: if actual_directory not in struc: - print(f"Error: Directory '{actual_directory}' not defined in the folder structure dictionary.") - return False - return True + # print(f"Error: Directory '{actual_directory}' not defined in the folder structure dictionary.") TODO + return False, gettext('zip.errors.invalid_structure.directory_not_found_in_template') + return True, gettext('zip.success') From ce077ce2f373c2ae927a7a13ee61c97dcef35647 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Thu, 7 Mar 2024 22:41:53 +0100 Subject: [PATCH 09/18] chore: cleanup and fixed linting warnings --- backend/api/views/check_folder_structure.py | 79 ++++++++++++++------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index 4faa40d9..ce116a13 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -1,11 +1,10 @@ import zipfile import os from api.models.checks import StructureCheck -from api.models.project import Project from api.models.extension import FileExtension from django.utils.translation import gettext -data_directory = "../../../data" # ../data\structures\zip_struct1.zip +data_directory = "../data" # ../data\structures\zip_struct1.zip def parseZipFile(project, dir_path): @@ -24,7 +23,8 @@ def parseZipFile(project, dir_path): def checkZipFile(project, dir_path, restrict_extra_folders=False): - project_structure_checks = StructureCheck.objects.filter(project=project.id) + project_structure_checks = StructureCheck.objects.filter( + project=project.id) structuur = {} for struct in project_structure_checks: structuur[struct.name] = { @@ -35,7 +35,8 @@ def checkZipFile(project, dir_path, restrict_extra_folders=False): structuur[struct.name]["obligated_extensions"].add(ext.extension) for ext in struct.blocked_extensions.all(): structuur[struct.name]["blocked_extensions"].add(ext.extension) - return check_zip_structure(structuur, dir_path, restrict_extra_folders=restrict_extra_folders) + return check_zip_structure( + structuur, dir_path, restrict_extra_folders=restrict_extra_folders) def get_parent_directories(dir_path): @@ -88,53 +89,75 @@ def get_zip_structure(root_path): _, file_extension = os.path.splitext(file) file_extension = file_extension[1:] if not file_extension == "": - directory_structure[map]["obligated_extensions"].add(file_extension) + directory_structure[map]["obligated_extensions"].add( + file_extension) else: _, file_extension = os.path.splitext(file_name) file_extension = file_extension[1:] - directory_structure["."]["obligated_extensions"].add(file_extension) + directory_structure["."]["obligated_extensions"].add( + file_extension) return directory_structure -def check_zip_content(root_path, dir_path, obligated_extensions, blocked_extensions): +def check_zip_content( + root_path, + dir_path, + obligated_extensions, + blocked_extensions): """ - Check the content of a directory without traversing subdirectories. - :param dir_path: The path of the zip we need to check. - :param obligated_extensions: The file extensions that are obligated to be present. - :param blocked_extensions: The file extensions that are forbidden to be present. - :return: - True: All checks pass. - False: At least 1 blocked extension is found or 1 obligated extension is not found. + Check the content of a directory without traversing subdirectories. + parameters: + dir_path: The path of the zip we need to check. + obligated_extensions: The file extensions that are obligated to be present. + blocked_extensions: The file extensions that are forbidden to be present. + :return: + True: All checks pass. + False: At least 1 blocked extension is found + or 1 obligated extension is not found. """ dir_path = dir_path.replace('\\', '/') with zipfile.ZipFile(root_path, 'r') as zip_file: zip_contents = set(zip_file.namelist()) found_obligated = set() # To track found obligated extensions if dir_path == ".": - files_in_subdirectory = [file for file in zip_contents if "/" not in file] + files_in_subdirectory = [ + file for file in zip_contents if "/" not in file + ] else: - files_in_subdirectory = [file[len(dir_path)+1:] for file in zip_contents if (file.startswith(dir_path) and '/' not in file[len(dir_path)+1:] and file[len(dir_path)+1:] != "")] + files_in_subdirectory = [ + file[len(dir_path)+1:] for file in zip_contents + if (file.startswith(dir_path) and + '/' not in file[len(dir_path)+1:] and + file[len(dir_path)+1:] != "")] for file in files_in_subdirectory: _, file_extension = os.path.splitext(file) - file_extension = file_extension[1:] # file_extension[1:] to remove the . + # file_extension[1:] to remove the . + file_extension = file_extension[1:] if file_extension in blocked_extensions: # print(f"Error: {file_extension} found in '{dir_path}' is not allowed.") TODO - return False, gettext('zip.errors.invalid_structure.blocked_extension_found') + return False, gettext( + 'zip.errors.invalid_structure.blocked_extension_found') elif file_extension in obligated_extensions: found_obligated.add(file_extension) if set(obligated_extensions) <= found_obligated: return True, gettext('zip.success') else: - return False, gettext('zip.errors.invalid_structure.obligated_extension_not_found') + return False, gettext( + 'zip.errors.invalid_structure.obligated_extension_not_found') -def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders=False): +def check_zip_structure( + folder_structure, + zip_file_path, + restrict_extra_folders=False): """ Check the structure of a zip file. - :param zip_file_path: Path to the zip file. - :param folder_structure: Dictionary representing the expected folder structure. + + parameters: + zip_file_path: Path to the zip file. + folder_structure: Dictionary representing the expected folder structure. :return: True: Zip file structure matches the expected structure. False: Zip file structure does not match the expected structure. @@ -146,7 +169,8 @@ def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders= for dir in struc: if dir not in dirs: # print(f"Error: Directory '{dir}' not defined.") TODO - return False, gettext('zip.errors.invalid_structure.directory_not_defined') + return False, gettext( + 'zip.errors.invalid_structure.directory_not_defined') with zipfile.ZipFile(zip_file_path, 'r') as zip_file: # zip_contents = set(zip_file.namelist()) @@ -156,7 +180,11 @@ def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders= obligated_extensions = info.get('obligated_extensions', set()) blocked_extensions = info.get('blocked_extensions', set()) - result, message = check_zip_content(zip_file_path, directory, obligated_extensions, blocked_extensions) + result, message = check_zip_content( + zip_file_path, + directory, + obligated_extensions, + blocked_extensions) if not result: return result, message # Check for any directories not present in the folder structure dictionary @@ -164,5 +192,6 @@ def check_zip_structure(folder_structure, zip_file_path, restrict_extra_folders= for actual_directory in dirs: if actual_directory not in struc: # print(f"Error: Directory '{actual_directory}' not defined in the folder structure dictionary.") TODO - return False, gettext('zip.errors.invalid_structure.directory_not_found_in_template') + return False, gettext( + 'zip.errors.invalid_structure.directory_not_found_in_template') return True, gettext('zip.success') From 83763ce3afa8e5fb8937b6fcc1b0f8dda36deac2 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Fri, 8 Mar 2024 07:35:13 +0100 Subject: [PATCH 10/18] chore: cleanup the data directory and started tests --- backend/api/tests/test_file_structure.py | 23 ++++++++++++++++++ backend/api/views/check_folder_structure.py | 9 ++++--- backend/api/views/project_view.py | 2 +- backend/ypovoli/settings.py | 2 ++ .../structures/mixed_template.zip | Bin .../structures/only_files_template.zip | Bin .../structures/only_folders_template.zip | Bin data/{ => production}/structures/remake.zip | Bin data/{ => production}/structures/root.zip | Bin .../structures/zip_struct1.zip | Bin data/{ => production}/tests/mixed.zip | Bin data/{ => production}/tests/only_files.zip | Bin data/{ => production}/tests/only_folders.zip | Bin .../tests/test_zip1struct1.zip | Bin .../tests/test_zip2struct1.zip | Bin .../tests/test_zip3struct1.zip | Bin .../tests/test_zip4struct1.zip | Bin data/testing/structures/mixed_template.zip | Bin 0 -> 822 bytes .../structures/only_files_template.zip | Bin 0 -> 474 bytes .../structures/only_folders_template.zip | Bin 0 -> 638 bytes data/testing/structures/remake.zip | Bin 0 -> 3234 bytes data/testing/structures/root.zip | Bin 0 -> 226 bytes data/testing/structures/zip_struct1.zip | Bin 0 -> 2136 bytes data/testing/tests/mixed.zip | Bin 0 -> 1660 bytes data/testing/tests/only_files.zip | Bin 0 -> 618 bytes data/testing/tests/only_folders.zip | Bin 0 -> 990 bytes data/testing/tests/test_zip1struct1.zip | Bin 0 -> 2136 bytes data/testing/tests/test_zip2struct1.zip | Bin 0 -> 1988 bytes data/testing/tests/test_zip3struct1.zip | Bin 0 -> 2258 bytes data/testing/tests/test_zip4struct1.zip | Bin 0 -> 1822 bytes 30 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 backend/api/tests/test_file_structure.py rename data/{ => production}/structures/mixed_template.zip (100%) rename data/{ => production}/structures/only_files_template.zip (100%) rename data/{ => production}/structures/only_folders_template.zip (100%) rename data/{ => production}/structures/remake.zip (100%) rename data/{ => production}/structures/root.zip (100%) rename data/{ => production}/structures/zip_struct1.zip (100%) rename data/{ => production}/tests/mixed.zip (100%) rename data/{ => production}/tests/only_files.zip (100%) rename data/{ => production}/tests/only_folders.zip (100%) rename data/{ => production}/tests/test_zip1struct1.zip (100%) rename data/{ => production}/tests/test_zip2struct1.zip (100%) rename data/{ => production}/tests/test_zip3struct1.zip (100%) rename data/{ => production}/tests/test_zip4struct1.zip (100%) create mode 100644 data/testing/structures/mixed_template.zip create mode 100644 data/testing/structures/only_files_template.zip create mode 100644 data/testing/structures/only_folders_template.zip create mode 100644 data/testing/structures/remake.zip create mode 100644 data/testing/structures/root.zip create mode 100644 data/testing/structures/zip_struct1.zip create mode 100644 data/testing/tests/mixed.zip create mode 100644 data/testing/tests/only_files.zip create mode 100644 data/testing/tests/only_folders.zip create mode 100644 data/testing/tests/test_zip1struct1.zip create mode 100644 data/testing/tests/test_zip2struct1.zip create mode 100644 data/testing/tests/test_zip3struct1.zip create mode 100644 data/testing/tests/test_zip4struct1.zip diff --git a/backend/api/tests/test_file_structure.py b/backend/api/tests/test_file_structure.py new file mode 100644 index 00000000..a4279c7a --- /dev/null +++ b/backend/api/tests/test_file_structure.py @@ -0,0 +1,23 @@ +import os +import tempfile +from django.test import TestCase +from django.core.files.base import ContentFile +from api.views.check_folder_structure import check_zip_content, parseZipFile +from api.models.checks import StructureCheck +from api.models.project import Project +from django.conf import settings + + +class FileTestsTests(TestCase): + def setUp(self): + # Set up a temporary directory for MEDIA_ROOT during tests + self.old_media_root = settings.MEDIA_ROOT + settings.MEDIA_ROOT = os.path.normpath(os.path.join(settings.MEDIA_ROOT, '../testing')) + + def tearDown(self): + # Restore the original MEDIA_ROOT after tests + settings.MEDIA_ROOT = self.old_media_root + + def test_your_function(self): + # Test your function that interacts with the media directory + self.assertEqual(True, True) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index ce116a13..3fec83bd 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -3,11 +3,11 @@ from api.models.checks import StructureCheck from api.models.extension import FileExtension from django.utils.translation import gettext +from django.conf import settings -data_directory = "../data" # ../data\structures\zip_struct1.zip - -def parseZipFile(project, dir_path): +def parseZipFile(project, dir_path): # TODO block paths that start with .. + dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) struct = get_zip_structure(dir_path) for key, value in struct.items(): check = StructureCheck.objects.create( @@ -22,7 +22,8 @@ def parseZipFile(project, dir_path): project.structure_checks.add(check) -def checkZipFile(project, dir_path, restrict_extra_folders=False): +def checkZipFile(project, dir_path, restrict_extra_folders=False): # TODO block paths that start with .. + dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) project_structure_checks = StructureCheck.objects.filter( project=project.id) structuur = {} diff --git a/backend/api/views/project_view.py b/backend/api/views/project_view.py index 99d2eca6..413b5a70 100644 --- a/backend/api/views/project_view.py +++ b/backend/api/views/project_view.py @@ -3,7 +3,7 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound from django.utils.translation import gettext_lazy as _ -from api.views.check_folder_structure import get_zip_structure, check_zip_structure, data_directory, parseZipFile +from api.views.check_folder_structure import get_zip_structure, check_zip_structure, parseZipFile from ..models.project import Project from ..serializers.project_serializer import ProjectSerializer from ..serializers.group_serializer import GroupSerializer diff --git a/backend/ypovoli/settings.py b/backend/ypovoli/settings.py index 32355200..2fd97375 100644 --- a/backend/ypovoli/settings.py +++ b/backend/ypovoli/settings.py @@ -12,9 +12,11 @@ from datetime import timedelta from pathlib import Path +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +MEDIA_ROOT = os.path.normpath(os.path.join(BASE_DIR, "../data/production")) # Quick-start development settings - unsuitable for production diff --git a/data/structures/mixed_template.zip b/data/production/structures/mixed_template.zip similarity index 100% rename from data/structures/mixed_template.zip rename to data/production/structures/mixed_template.zip diff --git a/data/structures/only_files_template.zip b/data/production/structures/only_files_template.zip similarity index 100% rename from data/structures/only_files_template.zip rename to data/production/structures/only_files_template.zip diff --git a/data/structures/only_folders_template.zip b/data/production/structures/only_folders_template.zip similarity index 100% rename from data/structures/only_folders_template.zip rename to data/production/structures/only_folders_template.zip diff --git a/data/structures/remake.zip b/data/production/structures/remake.zip similarity index 100% rename from data/structures/remake.zip rename to data/production/structures/remake.zip diff --git a/data/structures/root.zip b/data/production/structures/root.zip similarity index 100% rename from data/structures/root.zip rename to data/production/structures/root.zip diff --git a/data/structures/zip_struct1.zip b/data/production/structures/zip_struct1.zip similarity index 100% rename from data/structures/zip_struct1.zip rename to data/production/structures/zip_struct1.zip diff --git a/data/tests/mixed.zip b/data/production/tests/mixed.zip similarity index 100% rename from data/tests/mixed.zip rename to data/production/tests/mixed.zip diff --git a/data/tests/only_files.zip b/data/production/tests/only_files.zip similarity index 100% rename from data/tests/only_files.zip rename to data/production/tests/only_files.zip diff --git a/data/tests/only_folders.zip b/data/production/tests/only_folders.zip similarity index 100% rename from data/tests/only_folders.zip rename to data/production/tests/only_folders.zip diff --git a/data/tests/test_zip1struct1.zip b/data/production/tests/test_zip1struct1.zip similarity index 100% rename from data/tests/test_zip1struct1.zip rename to data/production/tests/test_zip1struct1.zip diff --git a/data/tests/test_zip2struct1.zip b/data/production/tests/test_zip2struct1.zip similarity index 100% rename from data/tests/test_zip2struct1.zip rename to data/production/tests/test_zip2struct1.zip diff --git a/data/tests/test_zip3struct1.zip b/data/production/tests/test_zip3struct1.zip similarity index 100% rename from data/tests/test_zip3struct1.zip rename to data/production/tests/test_zip3struct1.zip diff --git a/data/tests/test_zip4struct1.zip b/data/production/tests/test_zip4struct1.zip similarity index 100% rename from data/tests/test_zip4struct1.zip rename to data/production/tests/test_zip4struct1.zip diff --git a/data/testing/structures/mixed_template.zip b/data/testing/structures/mixed_template.zip new file mode 100644 index 0000000000000000000000000000000000000000..456c258b7a948ec1e68ae92e48baa5437412adde GIT binary patch literal 822 zcmWIWW@h1H0D-^BX%S!sl;B{HVMxo*Nl7g-)DI2eWMI|}d6D`$_(f`I1vdjD%L`@( z1~3tT(*$886UtJHigOav^$Jqb;D&(=#A(=LoX#q9(d ztPAxTGtea<^KiNb-7IOWW|b$Fq!#I=XF|LPG8BX{3}s}JW5yNc641~WV0h~YVj^OI z6%qp^m?q2sGmU{^Nuv{zX(+Kl*kH`KfEf&OWw>PZU#n{ z7t9O{U?Ko+jxL%xoIrCja}(23^$POR!A7(JwSzEDBecMSO(WoE hbkk5>c>&2ZQ0|N^X`Y})LL8nLpm^1YecoxR!lv3JKiw)%eF`_An9-tL2X;~K%wohRLW z{xJTzL0TlGN&2)q=!KN6B`F&Q=~{d5@l{fPzcr#~P{X6^q}CYK3E@AxMzvLgS{N}J zqyu)m!w&ox(FtYVVUi;dV?=Bz4Yv%4WcaM_$K%E?LGg`EbyHDYaj3qeEa~|NUN{Jj zc;BBv9n0AFeT8t_K}cyo?)h*O!ul_u{b3sYjmr8t$wQtP2XR=&anxg2cHlC@;;_s8 z!V|$V0s_S^`#G-*m;Qm`g-z90R81FD+@AF|z6$rE9!&Hn^>EMxPJ6oh>>2m~%qFBlVkfI60upxRS~a1zC^G~;7F z1KNaDBGhJX)lGZ;=6K`Jk>8v6sNeccvGu1=UL~0_A#Y!88!uGI8aWrS?eiDUqpHWehToC_HZ=ZPG&e0Rh90xa#yHnTQz@9?O; zR%YeYEHT_@_1Kmt(06C5nab0{eS+oeD0=$Z3d1A;f_4c@C(AjK5zxs$4uX;@fYT<5 z#Hj-GHH%eWs z^EnEKp$?QtA)q)`YdM25%kVB-R-hCT32@pplKz$RsEAu=+Wo_!WI6#&n@p0XTDX#1 bh{K&$pvj~%S=pS${F#IAJ9zp9nuPoZU*!%e literal 0 HcmV?d00001 diff --git a/data/testing/structures/root.zip b/data/testing/structures/root.zip new file mode 100644 index 0000000000000000000000000000000000000000..4e703157f5a0929e00ca8b7dd224e0f3e31150ce GIT binary patch literal 226 zcmWIWW@Zs#0D+R2vO?J3$a1~ hY7?3*2(8%cL1@)Rbs)%ARyL3{6A+dG=`aw70RSa#9%=vp literal 0 HcmV?d00001 diff --git a/data/testing/structures/zip_struct1.zip b/data/testing/structures/zip_struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..bd156cd2a3301c7dd2b28cc7520b8b7447a1ddb4 GIT binary patch literal 2136 zcma)-y-ve05XaLGSYfEdz(Dwz7!ulUAQmK8I?#~~g~V+m5*pbl(C!R80(}D}76cLx zz{<=6u(BZ-v1`b=t}l(F8}k84(18XDOW05}_|S^lSkZ<{i5JAgi37ak zDxS@#uate$%A#U5T#+bd$?=l}D!*$0!^FvXkpMn8=fvr$gjbC)r-f0N@R3c1&_ZFo zo5~OW)~cd?H(frg=}{OF=A=v+lTVGXhG{MdBD>uxm1`Ehs+_=&J$V*gTp*fskf>!I z4*0EE0W2~O5vweZfL!q#fn@v0MFyN{wI$Bx;i(8N1OG2N&`qlj^lAxhc6#vvXj**$ z9?l0&i~xA~pX3JP31a|=$r_-k7aXD(1<>@CgjL8RKJX7tlg0rclXXat%|pG6h_4$7 z08Lf`K$;I!j0K1$YXRb8J`s!tfVz0%-%%bR-WLWG#sd(O^+<@#!*&G`ZpMg!XtE+8 XM)^c(Obd%5VToUNRm-~PDVFsMDkRQV literal 0 HcmV?d00001 diff --git a/data/testing/tests/mixed.zip b/data/testing/tests/mixed.zip new file mode 100644 index 0000000000000000000000000000000000000000..509911007ed853f9249aa3ede21c717e5d25b976 GIT binary patch literal 1660 zcmb8wPfo%>6bA5z1*5nTFvbXB>5dYL9zeIcAV!ICCDMWz3ojTb(&}VVmy9#fj%LY`SHyZlEq*1@T*aW9`J`WCX zJ-uu}COdZk!b{64loVkWOdf}wzB~4N0wd6h{aWLAEY2DJy0MB-5OKWvY~TkY_hEb( zh9QcUHB7D>pG{Gr*o?edG^J~Fj+R-DX6+lTZfEM=kAr@guZM2@^!40TF%BB;m(h>cDSZ#g zGVwjoGj`VaY^YkO8LJ=@>t!g$61Npitr@y0ZP4AaaB0#XMpvnv?E$%UJW`Oy?+E`m Ievkq91EnNEO#lD@ literal 0 HcmV?d00001 diff --git a/data/testing/tests/only_files.zip b/data/testing/tests/only_files.zip new file mode 100644 index 0000000000000000000000000000000000000000..55a7402c58fd5025e0827be9af1f98886c1802d0 GIT binary patch literal 618 zcmWIWW@h1H0D;9xX%S!sl;C5KVaQ0$$;mIzFUm>L%StQ?4dG;9UKscy6@*JGxEUB( zUNAE-fQbOOIcw0&;RKqKnVXoNs#lPg4mM&lP&)|YG-3{#5gb4xit|$o^hzp9z$Q!w z>IPw)CQL&!fg5N-S!z*nPGY)VK}s6fkZzy>AdF!MBa<96t}u~+2hoy75EDIwSRo;V z$4t!dK{nF^XeLr<0gb~NQV1tvh841L-9Y0IAqO-KYp5Yi!wffM)6N1-LkUHofmlP3 Ul?|wpfdvSi7#SFZfS7>+00f?N$p8QV literal 0 HcmV?d00001 diff --git a/data/testing/tests/only_folders.zip b/data/testing/tests/only_folders.zip new file mode 100644 index 0000000000000000000000000000000000000000..012a030475b2e794d5b13dfec96d37dce82581db GIT binary patch literal 990 zcmb8t%?g4*5C`xvHLNb-sYCb%B6;j)bcrqo1<@%YJq1P|(6J}znR=S=(mOPxtGnr% zrtGp8e=|FO{kf`jfu7?oTqg&%88Cz}I_!hf$XP8rpiUQYFvfMg^+3^L6(HICTsRj5 zQxKSByQwuphrGs?CAD(ZW<8QitI~}c^vEm^9a@#vy&-s`pP1{UcXP9w zkwvT2jncBzssslt%Ran4I`TaAG|wMeIZaWU2wI*3W{a+L{=CbgYk}xKq;6gznMQq$ za>`5q5J)@# zD>Dzk%7$RXt|8~TzBI0q>vF%lobUf{w<^_|B~Im_dph~^_uAsIJBR{8J1nI`Kdrk# zNQs{g=;(y@yo+#5sMBZdjIi68Cala7*zhd&lDdm0t>Z~4iId1n2~*PV_NQy^6iLrO zj=)_*GtU}r6Q^Ag$|A4l1Os0IJZON8`G6$oLIZ_I*idHhp%t~Uq79c4FNlc~2T*cP zyR}t3n^9jW`=*seht+UJqSz(JPZFs7t^o`iC-+4H_~M)sr>_!THNu=0#$m!&HW@(+ z`p2Uy+f+XMTdRup-E{e~rpIAKn3FPPO+Gck8a8uD5ZUcksa&&gsd558_T*huxIi@R zokT73aL6Cc3P8v>L`Yd40lDH20?G8qhYUEAv?b2w;i(8N1OE#h=q9NHy;?$>ofbX- zP0|P8(R|><2!NM=B{vvP7z02|)&Nbt;1I|f`4$DG!6intV4oq9_nRe z__~n*&}1b5r1?O_Sb%7<79c+66TxTzs1HwAjXYv_Ul>pr4?s-TBOx{q+ZAMRGe!hN alNA9m$|p)=T38ebOZ<0RwXA!dVp+et6wGG; literal 0 HcmV?d00001 diff --git a/data/testing/tests/test_zip2struct1.zip b/data/testing/tests/test_zip2struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..fbce9306ed2d477e01496bd8c176376a7840c84d GIT binary patch literal 1988 zcmb7_u};G<5QbA)7!gAy1_nZ5Vn}Ga!G;739q7mgA#qEjgj%)>v^y`sOE4gT!~?MM z6i7_0Fk;t`bB(VJ7bTbFzPp_7zjQm*THRAt-Rqr9UVa{1()R{gLf9!6Y#57Xn52xv zV!%entRG#bW5R+9-Yp1?vuVQWUjiSV=Mim&c+dtOBuGxPNDyv>-|bH4+$j@YfC7cP zfnuI4+9poBB8+EIKS&0#0l2pSjrjnY^q_#!Eo>P*c(aoFSkhL=NR;Fx$P+x}8XnDA zY@~g)(xT03g(^_ZQV>%LnP2_~7^hSY9326mbhp|@*`HQc$DHdaZHIEy#9^8d9tdt6 z@`puO$2z77D!SXLEYv+*Xp$h!pFGRT4v4NkXw-^y`|`Gg3^K$aN{HGNao)Hq+d$1=DA{3}zSyMzk#S_Lh3TDAam2^W9|^MMl?18??`++sW>Yk;_f4MjcU z5JTobvrAG!*QO5m3#Vz>1IQ(O4f3UHE+Eaf*8l)rivWOZHW1bV0PRmzeWsu%Mj1|1~c=ijsO4v literal 0 HcmV?d00001 diff --git a/data/testing/tests/test_zip3struct1.zip b/data/testing/tests/test_zip3struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..315fd52e9e46cda79198cf42847465798587c850 GIT binary patch literal 2258 zcma)+y-ve05P;Jkm^)NrU?BWV3<+(SfC&i}20C_wkhpC`LL(;y+MSo+4VYLENIU>5 zGfOu%#D-wRt|8~RzBH~9cZk2cT<*U1QKecp_^Rx7JJUaZw+*&;2Z2ZENu1E3n>1|C zr^HPLbaYI6&V@fF)au9Wl+ZcN5|(BO@$f8kqK1thTE!0~B#Z(lA+dD)b|)KitH|*b zWO3XzB=e-u)^XY;p>g2!EN|dSfO`e7HXjgqT}YsC37b+4-j$#x7PM(o;&>skLJtqQ zjGvCFD}{Yk!lK1$+Wa8zCCiN>i2SAiG#w}UMF3dioD-`r6J8X;oD{}>#9B5QK?;TI z-I8AXTZxM1-LhG;CWn4NVk?QIHu+QtD_G41fuDw)KlZXMkB-?k%ehpp8~A*)uxy#r zd(mkJqON!XRb$&eyA*%G^y3gd|6*~-6}u2fI>ec`)cHW*Otw81!KL7TJ3z8JKLEX2 zLX*wf0)RSS0PtWwaB2_W<$sbJjK^&P5S>pTsb?G_*#&57O587E@elli(}ZmRr1Omc znQUv6k>acN0ie!50Hpaq$wolb`3Q&)`9!c20M+90Sr>~J?+XKRTLDDpD*-Xtwpl?6 rH)Ag#>ih-7AfG5~1~8q^1dPAq9R}leTUg`|*4S@SHH-;+)G&Sl8(!uQ literal 0 HcmV?d00001 diff --git a/data/testing/tests/test_zip4struct1.zip b/data/testing/tests/test_zip4struct1.zip new file mode 100644 index 0000000000000000000000000000000000000000..9acfea0241389ce71f27778ac5980ed9b7272931 GIT binary patch literal 1822 zcma)+F;9a)6o6?_Hxq{@4w^{R#7RioO*aRZ4l=qa!cj&tHBY`y$5EaYX0^<8&7Ct{0`0gnUZp zXLJ}`rwc;eg!z92epx1L{wJ{6)g;Ke9wa>-m6P`Hq#HunI2gLoG`s`AqXyWm2V_wU z`Hj2o6}nz(MIEeY&!Z%WCd8dYc*!k1o6)dP_Ejs34y)(MM0tE}m}OA;O#>JXSsjrA zh_f0IHz^WcG(t@ai!>8oNj8TT8qd35*!Y)L745t4al-g%Iws8Jtnit9YJ?qZ=87P* z{XwhUv2Z&CK`bYKHgyGvra36oh6pF(*6ag!0AjL7K^zfoaY)%E0Gez9;B!4NcMEvc zyZpv@(lvmXY=fo_Tga$N?g342N!q8D9Q+8UDHj1`vQdE?5gw{R;3fb~b^>r&4J^0{ zM3b#RJZPhnlY0es0jLj8{^Qk>!%HxrbQvHfn-#eiOTiWHJPw3 Mt`FO?w#98(KeNV#0RR91 literal 0 HcmV?d00001 From 998c51d3cd2cf2331e2770e8d6fcd9265d5d114c Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Fri, 8 Mar 2024 08:02:27 +0100 Subject: [PATCH 11/18] chore: worked at testing the file structure --- backend/api/tests/test_file_structure.py | 86 ++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/backend/api/tests/test_file_structure.py b/backend/api/tests/test_file_structure.py index a4279c7a..5f83044c 100644 --- a/backend/api/tests/test_file_structure.py +++ b/backend/api/tests/test_file_structure.py @@ -1,15 +1,69 @@ import os -import tempfile -from django.test import TestCase -from django.core.files.base import ContentFile +import json +from django.utils import timezone +from django.urls import reverse +from rest_framework.test import APITestCase from api.views.check_folder_structure import check_zip_content, parseZipFile from api.models.checks import StructureCheck +from api.models.extension import FileExtension +from api.models.course import Course from api.models.project import Project +from authentication.models import User from django.conf import settings -class FileTestsTests(TestCase): + +def create_course(id, name, academic_startyear): + """ + Create a Course with the given arguments. + """ + return Course.objects.create( + id=id, name=name, academic_startyear=academic_startyear + ) + + +def create_fileExtension(id, extension): + """ + Create a FileExtension with the given arguments. + """ + return FileExtension.objects.create(id=id, extension=extension) + + +def create_project(name, description, visible, archived, days, course): + """Create a Project with the given arguments.""" + deadline = timezone.now() + timezone.timedelta(days=days) + + return Project.objects.create( + name=name, + description=description, + visible=visible, + archived=archived, + deadline=deadline, + course=course, + ) + + +def create_structureCheck(name, project, obligated, blocked): + """ + Create a StructureCheck with the given arguments. + """ + structureCheck = StructureCheck.objects.create( + name=name, + project=project, + ) + for ch in obligated: + structureCheck.obligated_extensions.add(ch) + for ch in blocked: + structureCheck.blocked_extensions.add(ch) + + return structureCheck + + +class FileTestsTests(APITestCase): def setUp(self): + self.client.force_authenticate( + User.get_dummy_admin() + ) # Set up a temporary directory for MEDIA_ROOT during tests self.old_media_root = settings.MEDIA_ROOT settings.MEDIA_ROOT = os.path.normpath(os.path.join(settings.MEDIA_ROOT, '../testing')) @@ -19,5 +73,27 @@ def tearDown(self): settings.MEDIA_ROOT = self.old_media_root def test_your_function(self): - # Test your function that interacts with the media directory + course = create_course(id=3, name="test course", academic_startyear=2024) + project = create_project( + name="test", + description="descr", + visible=True, + archived=False, + days=100, + course=course, + ) + parseZipFile(project=project, dir_path="structures/zip_struct1.zip") + + response = self.client.get( + reverse("project-detail", args=[str(project.id)]), follow=True + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.accepted_media_type, "application/json") + + content_json = json.loads(response.content.decode("utf-8")) + + # print("strucCheck:", content_json["structure_checks"]) + # TODO get the structure checks from this now its following + # strucCheck: http://testserver/projects/1/structure_checks/ + self.assertEqual(True, True) From 3da961a05d749299e50d866abccd9ec10f88519c Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Sat, 9 Mar 2024 21:34:57 +0100 Subject: [PATCH 12/18] chore: add some tests for filestructures --- backend/api/models/project.py | 3 +- backend/api/tests/test_checks.py | 3 +- backend/api/tests/test_file_structure.py | 59 +++++++++++++++++++-- backend/api/tests/test_group.py | 11 ++-- backend/api/tests/test_project.py | 13 ++--- backend/api/tests/test_submission.py | 11 ++-- backend/api/views/check_folder_structure.py | 6 ++- backend/ypovoli/settings.py | 2 + 8 files changed, 84 insertions(+), 24 deletions(-) diff --git a/backend/api/models/project.py b/backend/api/models/project.py index 88e587fc..839907a1 100644 --- a/backend/api/models/project.py +++ b/backend/api/models/project.py @@ -1,4 +1,3 @@ -from datetime import datetime from django.db import models from django.utils import timezone from api.models.course import Course @@ -21,7 +20,7 @@ class Project(models.Model): start_date = models.DateTimeField( # The default value is the current date and time - default=datetime.now, + default=timezone.now, blank=True, ) diff --git a/backend/api/tests/test_checks.py b/backend/api/tests/test_checks.py index b66251b8..d1c66580 100644 --- a/backend/api/tests/test_checks.py +++ b/backend/api/tests/test_checks.py @@ -7,6 +7,7 @@ from api.models.extension import FileExtension from api.models.project import Project from api.models.course import Course +from django.conf import settings def create_fileExtension(id, extension): @@ -300,4 +301,4 @@ def test_extra_checks_exists(self): # Assert the details of the retrieved Checks match the created Checks retrieved_checks = content_json[0] self.assertEqual(int(retrieved_checks["id"]), checks.id) - self.assertEqual(retrieved_checks["run_script"], "http://testserver" + checks.run_script.url) + self.assertEqual(retrieved_checks["run_script"], settings.TESTING_BASE_LINK + checks.run_script.url) diff --git a/backend/api/tests/test_file_structure.py b/backend/api/tests/test_file_structure.py index 5f83044c..84b261af 100644 --- a/backend/api/tests/test_file_structure.py +++ b/backend/api/tests/test_file_structure.py @@ -92,8 +92,59 @@ def test_your_function(self): content_json = json.loads(response.content.decode("utf-8")) - # print("strucCheck:", content_json["structure_checks"]) - # TODO get the structure checks from this now its following - # strucCheck: http://testserver/projects/1/structure_checks/ + response = self.client.get( + content_json["structure_checks"], follow=True + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.accepted_media_type, "application/json") + + content_json = json.loads(response.content.decode("utf-8")) + + self.assertEqual(len(content_json), 7) + + expected_project_url = settings.TESTING_BASE_LINK + reverse( + "project-detail", args=[str(project.id)] + ) - self.assertEqual(True, True) + content = content_json[0] + self.assertEqual(content["name"], ".") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 0) + self.assertEqual(len(content["blocked_extensions"]), 0) + + content = content_json[1] + self.assertEqual(content["name"], "folder_struct1") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 1) + self.assertEqual(len(content["blocked_extensions"]), 0) + + content = content_json[2] + self.assertEqual(content["name"], "folder_struct1/submap1") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 2) + self.assertEqual(len(content["blocked_extensions"]), 0) + + content = content_json[3] + self.assertEqual(content["name"], "folder_struct1/submap1/templates") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 1) + self.assertEqual(len(content["blocked_extensions"]), 0) + + content = content_json[4] + self.assertEqual(content["name"], "folder_struct1/submap2") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 1) + self.assertEqual(len(content["blocked_extensions"]), 0) + + content = content_json[5] + self.assertEqual(content["name"], "folder_struct1/submap2/src") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 3) + self.assertEqual(len(content["blocked_extensions"]), 0) + + content = content_json[6] + self.assertEqual(content["name"], "folder_struct1/submap3") + self.assertEqual(content["project"], expected_project_url) + self.assertEqual(len(content["obligated_extensions"]), 2) + self.assertEqual(len(content["blocked_extensions"]), 0) diff --git a/backend/api/tests/test_group.py b/backend/api/tests/test_group.py index f10e87d4..e90e80de 100644 --- a/backend/api/tests/test_group.py +++ b/backend/api/tests/test_group.py @@ -8,6 +8,7 @@ from api.models.student import Student from api.models.group import Group from api.models.course import Course +from django.conf import settings def create_course(name, academic_startyear, description=None, parent_course=None): @@ -83,7 +84,7 @@ def test_group_exists(self): self.assertEqual(len(content_json), 1) retrieved_group = content_json[0] - expected_project_url = "http://testserver" + reverse( + expected_project_url = settings.TESTING_BASE_LINK + reverse( "project-detail", args=[str(project.id)] ) @@ -122,10 +123,10 @@ def test_multiple_groups(self): self.assertEqual(len(content_json), 2) retrieved_group1, retrieved_group2 = content_json - expected_project_url1 = "http://testserver" + reverse( + expected_project_url1 = settings.TESTING_BASE_LINK + reverse( "project-detail", args=[str(project1.id)] ) - expected_project_url2 = "http://testserver" + reverse( + expected_project_url2 = settings.TESTING_BASE_LINK + reverse( "project-detail", args=[str(project2.id)] ) @@ -159,7 +160,7 @@ def test_group_detail_view(self): content_json = json.loads(response.content.decode("utf-8")) - expected_project_url = "http://testserver" + reverse( + expected_project_url = settings.TESTING_BASE_LINK + reverse( "project-detail", args=[str(project.id)] ) @@ -203,7 +204,7 @@ def test_group_project(self): # Parse the JSON content from the response content_json = json.loads(response.content.decode("utf-8")) - expected_course_url = "http://testserver" + reverse( + expected_course_url = settings.TESTING_BASE_LINK + reverse( "course-detail", args=[str(course.id)] ) diff --git a/backend/api/tests/test_project.py b/backend/api/tests/test_project.py index 126e4257..a5885712 100644 --- a/backend/api/tests/test_project.py +++ b/backend/api/tests/test_project.py @@ -7,6 +7,7 @@ from api.models.course import Course from api.models.checks import StructureCheck, ExtraCheck from api.models.extension import FileExtension +from django.conf import settings def create_course(id, name, academic_startyear): @@ -212,7 +213,7 @@ def test_project_exists(self): retrieved_project = content_json[0] - expected_course_url = "http://testserver" + reverse( + expected_course_url = settings.TESTING_BASE_LINK + reverse( "course-detail", args=[str(course.id)] ) @@ -256,7 +257,7 @@ def test_multiple_project(self): retrieved_project = content_json[0] - expected_course_url = "http://testserver" + reverse( + expected_course_url = settings.TESTING_BASE_LINK + reverse( "course-detail", args=[str(course.id)] ) @@ -268,7 +269,7 @@ def test_multiple_project(self): retrieved_project = content_json[1] - expected_course_url = "http://testserver" + reverse( + expected_course_url = settings.TESTING_BASE_LINK + reverse( "course-detail", args=[str(course.id)] ) @@ -361,7 +362,7 @@ def test_project_structure_checks(self): retrieved_project = content_json[0] - expected_course_url = "http://testserver" + reverse( + expected_course_url = settings.TESTING_BASE_LINK + reverse( "course-detail", args=[str(course.id)] ) @@ -449,7 +450,7 @@ def test_project_extra_checks(self): content_json = json.loads(response.content.decode("utf-8"))[0] self.assertEqual(int(content_json["id"]), checks.id) - self.assertEqual(content_json["project"], "http://testserver" + reverse( + self.assertEqual(content_json["project"], settings.TESTING_BASE_LINK + reverse( "project-detail", args=[str(project.id)] )) - self.assertEqual(content_json["run_script"], "http://testserver" + checks.run_script.url) + self.assertEqual(content_json["run_script"], settings.TESTING_BASE_LINK + checks.run_script.url) diff --git a/backend/api/tests/test_submission.py b/backend/api/tests/test_submission.py index ac0b52ab..ec426a03 100644 --- a/backend/api/tests/test_submission.py +++ b/backend/api/tests/test_submission.py @@ -9,6 +9,7 @@ from api.models.group import Group from api.models.course import Course from api.models.checks import ExtraCheck +from django.conf import settings def create_course(name, academic_startyear, description=None, parent_course=None): @@ -98,7 +99,7 @@ def test_submission_exists(self): # Assert the details of the retrieved submission # match the created submission retrieved_submission = content_json[0] - expected_group_url = "http://testserver" + reverse( + expected_group_url = settings.TESTING_BASE_LINK + reverse( "group-detail", args=[str(group.id)] ) self.assertEqual(int(retrieved_submission["id"]), submission.id) @@ -139,7 +140,7 @@ def test_multiple_submission_exists(self): # Assert the details of the retrieved submission # match the created submission retrieved_submission = content_json[0] - expected_group_url = "http://testserver" + reverse( + expected_group_url = settings.TESTING_BASE_LINK + reverse( "group-detail", args=[str(group.id)] ) self.assertEqual(int(retrieved_submission["id"]), submission1.id) @@ -150,7 +151,7 @@ def test_multiple_submission_exists(self): self.assertEqual(retrieved_submission["group"], expected_group_url) retrieved_submission = content_json[1] - expected_group_url = "http://testserver" + reverse( + expected_group_url = settings.TESTING_BASE_LINK + reverse( "group-detail", args=[str(group.id)] ) self.assertEqual(int(retrieved_submission["id"]), submission2.id) @@ -188,7 +189,7 @@ def test_submission_detail_view(self): # Assert the details of the retrieved submission # match the created submission retrieved_submission = content_json - expected_group_url = "http://testserver" + reverse( + expected_group_url = settings.TESTING_BASE_LINK + reverse( "group-detail", args=[str(group.id)] ) self.assertEqual(int(retrieved_submission["id"]), submission.id) @@ -241,7 +242,7 @@ def test_submission_group(self): # Parse the JSON content from the response content_json = json.loads(response.content.decode("utf-8")) - expected_project_url = "http://testserver" + reverse( + expected_project_url = settings.TESTING_BASE_LINK + reverse( "project-detail", args=[str(project.id)] ) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index 3fec83bd..c85d0f63 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -9,7 +9,11 @@ def parseZipFile(project, dir_path): # TODO block paths that start with .. dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) struct = get_zip_structure(dir_path) - for key, value in struct.items(): + + sorted_keys = sorted(struct.keys()) + + for key in sorted_keys: + value = struct[key] check = StructureCheck.objects.create( name=key, project=project diff --git a/backend/ypovoli/settings.py b/backend/ypovoli/settings.py index 2fd97375..f06ed720 100644 --- a/backend/ypovoli/settings.py +++ b/backend/ypovoli/settings.py @@ -18,6 +18,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_ROOT = os.path.normpath(os.path.join(BASE_DIR, "../data/production")) +TESTING_BASE_LINK = "http://testserver" + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ From df77e107ec69be11646cf912b15f7b81c6fc4402 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Sat, 9 Mar 2024 22:03:38 +0100 Subject: [PATCH 13/18] chore: finished file_structure tests --- backend/api/tests/test_file_structure.py | 76 ++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/backend/api/tests/test_file_structure.py b/backend/api/tests/test_file_structure.py index 84b261af..7132d6a2 100644 --- a/backend/api/tests/test_file_structure.py +++ b/backend/api/tests/test_file_structure.py @@ -3,7 +3,7 @@ from django.utils import timezone from django.urls import reverse from rest_framework.test import APITestCase -from api.views.check_folder_structure import check_zip_content, parseZipFile +from api.views.check_folder_structure import checkZipFile, parseZipFile from api.models.checks import StructureCheck from api.models.extension import FileExtension from api.models.course import Course @@ -12,7 +12,6 @@ from django.conf import settings - def create_course(id, name, academic_startyear): """ Create a Course with the given arguments. @@ -22,11 +21,11 @@ def create_course(id, name, academic_startyear): ) -def create_fileExtension(id, extension): +def create_fileExtension(extension): """ Create a FileExtension with the given arguments. """ - return FileExtension.objects.create(id=id, extension=extension) + return FileExtension.objects.create(extension=extension) def create_project(name, description, visible, archived, days, course): @@ -72,7 +71,7 @@ def tearDown(self): # Restore the original MEDIA_ROOT after tests settings.MEDIA_ROOT = self.old_media_root - def test_your_function(self): + def test_your_parsing(self): course = create_course(id=3, name="test course", academic_startyear=2024) project = create_project( name="test", @@ -148,3 +147,70 @@ def test_your_function(self): self.assertEqual(content["project"], expected_project_url) self.assertEqual(len(content["obligated_extensions"]), 2) self.assertEqual(len(content["blocked_extensions"]), 0) + + def test_your_checking(self): + course = create_course(id=3, name="test course", academic_startyear=2024) + project = create_project( + name="test", + description="descr", + visible=True, + archived=False, + days=100, + course=course, + ) + + fileExtensionHS = create_fileExtension(extension="hs") + fileExtensionPDF = create_fileExtension(extension="pdf") + fileExtensionDOCX = create_fileExtension(extension="docx") + fileExtensionLATEX = create_fileExtension(extension="latex") + fileExtensionMD = create_fileExtension(extension="md") + fileExtensionPY = create_fileExtension(extension="py") + fileExtensionHPP = create_fileExtension(extension="hpp") + fileExtensionCPP = create_fileExtension(extension="cpp") + fileExtensionTS = create_fileExtension(extension="ts") + fileExtensionTSX = create_fileExtension(extension="tsx") + + create_structureCheck( + name=".", + project=project, + obligated=[], + blocked=[]) + + create_structureCheck( + name="folder_struct1", + project=project, + obligated=[fileExtensionHS], + blocked=[]) + + create_structureCheck( + name="folder_struct1/submap1", + project=project, + obligated=[fileExtensionPDF, fileExtensionDOCX], + blocked=[]) + + create_structureCheck( + name="folder_struct1/submap1/templates", + project=project, + obligated=[fileExtensionLATEX], + blocked=[]) + + create_structureCheck( + name="folder_struct1/submap2", + project=project, + obligated=[fileExtensionMD], + blocked=[]) + + create_structureCheck( + name="folder_struct1/submap2/src", + project=project, + obligated=[fileExtensionPY, fileExtensionHPP, fileExtensionCPP], + blocked=[]) + + create_structureCheck( + name="folder_struct1/submap3", + project=project, + obligated=[fileExtensionTS, fileExtensionTSX], + blocked=[]) + + succes = (True, 'zip.success') + self.assertEqual(checkZipFile(project=project, dir_path="structures/zip_struct1.zip"), succes) From cc0d8e2af78c5b73173408c7ce5f4e58d546c675 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Sat, 9 Mar 2024 22:09:10 +0100 Subject: [PATCH 14/18] chore: fix doable lintning warnings --- backend/api/views/check_folder_structure.py | 31 +++++++++------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index c85d0f63..e6747f3a 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -87,8 +87,7 @@ def get_zip_structure(root_path): with zipfile.ZipFile(root_path, 'r') as zip_file: file_names = zip_file.namelist() for file_name in file_names: - # print(file_name) - parts = file_name.rsplit('/', 1) # You can also use '\\' if needed + parts = file_name.rsplit('/', 1) if len(parts) == 2: map, file = parts _, file_extension = os.path.splitext(file) @@ -169,7 +168,7 @@ def check_zip_structure( """ base, _ = os.path.splitext(zip_file_path) struc = [f for f in folder_structure.keys() if not f == "."] - # print(struc) + dirs = list_zip_directories(zip_file_path) for dir in struc: if dir not in dirs: @@ -177,21 +176,17 @@ def check_zip_structure( return False, gettext( 'zip.errors.invalid_structure.directory_not_defined') - with zipfile.ZipFile(zip_file_path, 'r') as zip_file: - # zip_contents = set(zip_file.namelist()) - # print(f"all contents in the zip are {zip_contents}") - for directory, info in folder_structure.items(): - # base_name, _ = os.path.splitext(zip_file_path) - obligated_extensions = info.get('obligated_extensions', set()) - blocked_extensions = info.get('blocked_extensions', set()) - - result, message = check_zip_content( - zip_file_path, - directory, - obligated_extensions, - blocked_extensions) - if not result: - return result, message + for directory, info in folder_structure.items(): + obligated_extensions = info.get('obligated_extensions', set()) + blocked_extensions = info.get('blocked_extensions', set()) + + result, message = check_zip_content( + zip_file_path, + directory, + obligated_extensions, + blocked_extensions) + if not result: + return result, message # Check for any directories not present in the folder structure dictionary if restrict_extra_folders: for actual_directory in dirs: From b1b20f2d4f3cfa0957b542f087ec762210d31af6 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Sat, 9 Mar 2024 22:19:34 +0100 Subject: [PATCH 15/18] chore: fixed some more linting errors --- backend/api/views/check_folder_structure.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index e6747f3a..bdf487c6 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -26,7 +26,8 @@ def parseZipFile(project, dir_path): # TODO block paths that start with .. project.structure_checks.add(check) -def checkZipFile(project, dir_path, restrict_extra_folders=False): # TODO block paths that start with .. +# TODO block paths that start with .. +def checkZipFile(project, dir_path, restrict_extra_folders=False): dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) project_structure_checks = StructureCheck.objects.filter( project=project.id) @@ -140,7 +141,9 @@ def check_zip_content( file_extension = file_extension[1:] if file_extension in blocked_extensions: - # print(f"Error: {file_extension} found in '{dir_path}' is not allowed.") TODO + # print( + # f"Error: {file_extension} found in + # '{dir_path}' is not allowed.") TODO return False, gettext( 'zip.errors.invalid_structure.blocked_extension_found') elif file_extension in obligated_extensions: @@ -191,7 +194,8 @@ def check_zip_structure( if restrict_extra_folders: for actual_directory in dirs: if actual_directory not in struc: - # print(f"Error: Directory '{actual_directory}' not defined in the folder structure dictionary.") TODO + # print(f"Error: Directory '{actual_directory}' + # not defined in the folder structure dictionary.") TODO return False, gettext( 'zip.errors.invalid_structure.directory_not_found_in_template') return True, gettext('zip.success') From a5984bafff5c854cb2ff71bf475567c5becd1d10 Mon Sep 17 00:00:00 2001 From: Tybo Verslype Date: Sat, 9 Mar 2024 22:34:43 +0100 Subject: [PATCH 16/18] chore: finaly fixed linter warnings --- backend/.flake8 | 2 +- backend/api/views/check_folder_structure.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/backend/.flake8 b/backend/.flake8 index ee3e436d..9c2d9f85 100644 --- a/backend/.flake8 +++ b/backend/.flake8 @@ -3,7 +3,7 @@ # Ignore unused imports ignore = F401 -max-line-length = 119 +max-line-length = 120 max-complexity = 10 diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index bdf487c6..bf65f5f9 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -29,8 +29,7 @@ def parseZipFile(project, dir_path): # TODO block paths that start with .. # TODO block paths that start with .. def checkZipFile(project, dir_path, restrict_extra_folders=False): dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) - project_structure_checks = StructureCheck.objects.filter( - project=project.id) + project_structure_checks = StructureCheck.objects.filter(project=project.id) structuur = {} for struct in project_structure_checks: structuur[struct.name] = { @@ -94,13 +93,11 @@ def get_zip_structure(root_path): _, file_extension = os.path.splitext(file) file_extension = file_extension[1:] if not file_extension == "": - directory_structure[map]["obligated_extensions"].add( - file_extension) + directory_structure[map]["obligated_extensions"].add(file_extension) else: _, file_extension = os.path.splitext(file_name) file_extension = file_extension[1:] - directory_structure["."]["obligated_extensions"].add( - file_extension) + directory_structure["."]["obligated_extensions"].add(file_extension) return directory_structure @@ -125,15 +122,11 @@ def check_zip_content( zip_contents = set(zip_file.namelist()) found_obligated = set() # To track found obligated extensions if dir_path == ".": - files_in_subdirectory = [ - file for file in zip_contents if "/" not in file - ] + files_in_subdirectory = [file for file in zip_contents if "/" not in file] else: files_in_subdirectory = [ - file[len(dir_path)+1:] for file in zip_contents - if (file.startswith(dir_path) and - '/' not in file[len(dir_path)+1:] and - file[len(dir_path)+1:] != "")] + file[len(dir_path) + 1:] for file in zip_contents + if file.startswith(dir_path) and '/' not in file[len(dir_path) + 1:] and bool(file[len(dir_path) + 1:])] for file in files_in_subdirectory: _, file_extension = os.path.splitext(file) From 0d4c01541e442a928d11d2588b57185c92a69e06 Mon Sep 17 00:00:00 2001 From: EwoutV Date: Sat, 9 Mar 2024 23:19:16 +0100 Subject: [PATCH 17/18] chore: linting --- backend/api/tests/test_file_structure.py | 6 +++--- backend/api/views/check_folder_structure.py | 4 ++-- backend/api/views/project_view.py | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/api/tests/test_file_structure.py b/backend/api/tests/test_file_structure.py index 7132d6a2..dc1a9706 100644 --- a/backend/api/tests/test_file_structure.py +++ b/backend/api/tests/test_file_structure.py @@ -3,7 +3,7 @@ from django.utils import timezone from django.urls import reverse from rest_framework.test import APITestCase -from api.views.check_folder_structure import checkZipFile, parseZipFile +from api.views.check_folder_structure import check_zip_file, parse_zip_file from api.models.checks import StructureCheck from api.models.extension import FileExtension from api.models.course import Course @@ -81,7 +81,7 @@ def test_your_parsing(self): days=100, course=course, ) - parseZipFile(project=project, dir_path="structures/zip_struct1.zip") + parse_zip_file(project=project, dir_path="structures/zip_struct1.zip") response = self.client.get( reverse("project-detail", args=[str(project.id)]), follow=True @@ -213,4 +213,4 @@ def test_your_checking(self): blocked=[]) succes = (True, 'zip.success') - self.assertEqual(checkZipFile(project=project, dir_path="structures/zip_struct1.zip"), succes) + self.assertEqual(check_zip_file(project=project, dir_path="structures/zip_struct1.zip"), succes) diff --git a/backend/api/views/check_folder_structure.py b/backend/api/views/check_folder_structure.py index bf65f5f9..b1bf70a0 100644 --- a/backend/api/views/check_folder_structure.py +++ b/backend/api/views/check_folder_structure.py @@ -6,7 +6,7 @@ from django.conf import settings -def parseZipFile(project, dir_path): # TODO block paths that start with .. +def parse_zip_file(project, dir_path): # TODO block paths that start with .. dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) struct = get_zip_structure(dir_path) @@ -27,7 +27,7 @@ def parseZipFile(project, dir_path): # TODO block paths that start with .. # TODO block paths that start with .. -def checkZipFile(project, dir_path, restrict_extra_folders=False): +def check_zip_file(project, dir_path, restrict_extra_folders=False): dir_path = os.path.normpath(os.path.join(settings.MEDIA_ROOT, dir_path)) project_structure_checks = StructureCheck.objects.filter(project=project.id) structuur = {} diff --git a/backend/api/views/project_view.py b/backend/api/views/project_view.py index 413b5a70..0b041875 100644 --- a/backend/api/views/project_view.py +++ b/backend/api/views/project_view.py @@ -3,7 +3,6 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound from django.utils.translation import gettext_lazy as _ -from api.views.check_folder_structure import get_zip_structure, check_zip_structure, parseZipFile from ..models.project import Project from ..serializers.project_serializer import ProjectSerializer from ..serializers.group_serializer import GroupSerializer From 16e2e10156de1d8943a0b106125324c5605f2605 Mon Sep 17 00:00:00 2001 From: EwoutV Date: Sat, 9 Mar 2024 23:21:37 +0100 Subject: [PATCH 18/18] chore: linting --- backend/api/helpers/__init__.py | 0 .../check_folder_structure.py | 0 backend/api/tests/test_file_structure.py | 50 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 backend/api/helpers/__init__.py rename backend/api/{views => helpers}/check_folder_structure.py (100%) diff --git a/backend/api/helpers/__init__.py b/backend/api/helpers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/views/check_folder_structure.py b/backend/api/helpers/check_folder_structure.py similarity index 100% rename from backend/api/views/check_folder_structure.py rename to backend/api/helpers/check_folder_structure.py diff --git a/backend/api/tests/test_file_structure.py b/backend/api/tests/test_file_structure.py index dc1a9706..d16b2ea2 100644 --- a/backend/api/tests/test_file_structure.py +++ b/backend/api/tests/test_file_structure.py @@ -3,7 +3,7 @@ from django.utils import timezone from django.urls import reverse from rest_framework.test import APITestCase -from api.views.check_folder_structure import check_zip_file, parse_zip_file +from api.helpers.check_folder_structure import check_zip_file, parse_zip_file from api.models.checks import StructureCheck from api.models.extension import FileExtension from api.models.course import Course @@ -21,7 +21,7 @@ def create_course(id, name, academic_startyear): ) -def create_fileExtension(extension): +def create_file_extension(extension): """ Create a FileExtension with the given arguments. """ @@ -42,20 +42,20 @@ def create_project(name, description, visible, archived, days, course): ) -def create_structureCheck(name, project, obligated, blocked): +def create_structure_check(name, project, obligated, blocked): """ Create a StructureCheck with the given arguments. """ - structureCheck = StructureCheck.objects.create( + structure_check = StructureCheck.objects.create( name=name, project=project, ) for ch in obligated: - structureCheck.obligated_extensions.add(ch) + structure_check.obligated_extensions.add(ch) for ch in blocked: - structureCheck.blocked_extensions.add(ch) + structure_check.blocked_extensions.add(ch) - return structureCheck + return structure_check class FileTestsTests(APITestCase): @@ -159,54 +159,54 @@ def test_your_checking(self): course=course, ) - fileExtensionHS = create_fileExtension(extension="hs") - fileExtensionPDF = create_fileExtension(extension="pdf") - fileExtensionDOCX = create_fileExtension(extension="docx") - fileExtensionLATEX = create_fileExtension(extension="latex") - fileExtensionMD = create_fileExtension(extension="md") - fileExtensionPY = create_fileExtension(extension="py") - fileExtensionHPP = create_fileExtension(extension="hpp") - fileExtensionCPP = create_fileExtension(extension="cpp") - fileExtensionTS = create_fileExtension(extension="ts") - fileExtensionTSX = create_fileExtension(extension="tsx") - - create_structureCheck( + fileExtensionHS = create_file_extension(extension="hs") + fileExtensionPDF = create_file_extension(extension="pdf") + fileExtensionDOCX = create_file_extension(extension="docx") + fileExtensionLATEX = create_file_extension(extension="latex") + fileExtensionMD = create_file_extension(extension="md") + fileExtensionPY = create_file_extension(extension="py") + fileExtensionHPP = create_file_extension(extension="hpp") + fileExtensionCPP = create_file_extension(extension="cpp") + fileExtensionTS = create_file_extension(extension="ts") + fileExtensionTSX = create_file_extension(extension="tsx") + + create_structure_check( name=".", project=project, obligated=[], blocked=[]) - create_structureCheck( + create_structure_check( name="folder_struct1", project=project, obligated=[fileExtensionHS], blocked=[]) - create_structureCheck( + create_structure_check( name="folder_struct1/submap1", project=project, obligated=[fileExtensionPDF, fileExtensionDOCX], blocked=[]) - create_structureCheck( + create_structure_check( name="folder_struct1/submap1/templates", project=project, obligated=[fileExtensionLATEX], blocked=[]) - create_structureCheck( + create_structure_check( name="folder_struct1/submap2", project=project, obligated=[fileExtensionMD], blocked=[]) - create_structureCheck( + create_structure_check( name="folder_struct1/submap2/src", project=project, obligated=[fileExtensionPY, fileExtensionHPP, fileExtensionCPP], blocked=[]) - create_structureCheck( + create_structure_check( name="folder_struct1/submap3", project=project, obligated=[fileExtensionTS, fileExtensionTSX],