Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
Merge branch 'development' of https://github.com/SELab-2/UGent-3 into…
Browse files Browse the repository at this point in the history
… frontend/i18n-language-bug
  • Loading branch information
Vucis committed May 18, 2024
2 parents d909e61 + 76f90a3 commit d90e1f9
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 222 deletions.
13 changes: 10 additions & 3 deletions backend/project/endpoints/courses/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from os import getenv
from urllib.parse import urljoin
from dataclasses import fields
from dotenv import load_dotenv

from flask import request
Expand Down Expand Up @@ -38,17 +39,23 @@ def get(self, uid=None):
"""

try:

filter_params = request.args.to_dict()

invalid_params = set(filter_params.keys()) - {f.name for f in fields(Course)}
if invalid_params:
return {
"url": RESPONSE_URL,
"message": f"Invalid query parameters {invalid_params}"
}, 400

# Start with a base query
base_query = select(Course)

# Apply filters dynamically if they are provided
for param, value in filter_params.items():
if value:
attribute = getattr(Course, param, None)
if attribute:
if param in Course.__table__.columns:
attribute = getattr(Course, param)
base_query = base_query.filter(attribute == value)

# Define the role-specific queries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

API_URL = os.getenv('API_HOST')
RESPONSE_URL = urljoin(API_URL, "projects")
UPLOAD_FOLDER = os.getenv('UPLOAD_URL')
UPLOAD_FOLDER = os.getenv('UPLOAD_FOLDER')

ASSIGNMENT_FILE_NAME = "assignment.md"

Expand Down
3 changes: 2 additions & 1 deletion backend/project/endpoints/projects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def get(self, uid=None):
filters = dict(request.args)
conditions = []
for key, value in filters.items():
conditions.append(getattr(Project, key) == value)
if key in Project.__table__.columns:
conditions.append(getattr(Project, key) == value)

# Get the projects
projects = Project.query
Expand Down
12 changes: 4 additions & 8 deletions backend/project/endpoints/submissions/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ def get(self, uid=None) -> dict[str, any]:
}
filters = dict(request.args)
try:
invalid_parameters = set(filters.keys()) - {"uid", "project_id"}
if invalid_parameters:
data["message"] = f"Invalid query parameter(s) {invalid_parameters}"
return data, 400

# Check the uid query parameter
user_id = filters.get("uid")
if user_id and not isinstance(user_id, str):
Expand All @@ -73,7 +68,8 @@ def get(self, uid=None) -> dict[str, any]:
# Filter the courses based on the query parameters
conditions = []
for key, value in filters.items():
conditions.append(getattr(Submission, key) == value)
if key in Submission.__table__.columns:
conditions.append(getattr(Submission, key) == value)

# Get the submissions
submissions = Submission.query
Expand All @@ -87,7 +83,7 @@ def get(self, uid=None) -> dict[str, any]:
# Return the submissions
data["message"] = "Successfully fetched the submissions"
data["data"] = [{
"submission_id": urljoin(BASE_URL, str(s.submission_id)),
"submission_id": urljoin(f"{API_HOST}/", f"/submissions/{s.submission_id}"),
"uid": urljoin(f"{API_HOST}/", f"users/{s.uid}"),
"project_id": urljoin(f"{API_HOST}/", f"projects/{s.project_id}"),
"grading": s.grading,
Expand Down Expand Up @@ -188,7 +184,7 @@ def post(self, uid=None) -> dict[str, any]:
data["message"] = "Successfully fetched the submissions"
data["url"] = urljoin(f"{API_HOST}/", f"/submissions/{submission.submission_id}")
data["data"] = submission_response(submission, API_HOST)
return data, 200
return data, 201

except exc.SQLAlchemyError:
session.rollback()
Expand Down
1 change: 1 addition & 0 deletions backend/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ disable=
W0613, # Unused argument (pytest uses it)
W0621, # Redefining name %r from outer scope (line %s)
R0904, # Too many public methods (too many unit tests essentially)
R0913, # Too many arguments (too many fixtures essentially)

[modules:project/modules/*]
disable=
Expand Down
12 changes: 5 additions & 7 deletions backend/seeder/seeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,18 @@ def into_the_db(my_uid):
subscribed_students = populate_course_students(
session, course_id, students)
populate_course_projects(
session, course_id, subscribed_students, my_uid)
session, course_id, subscribed_students)

for _ in range(5): # 5 courses where my_uid is a student
teacher_uid = teachers[random.randint(0, len(teachers)-1)].uid
course_id = insert_course_into_db_get_id(session, teacher_uid)
subscribed_students = populate_course_students(
session, course_id, students)
session.add(CourseStudent(course_id=course_id, uid=my_uid))
session.commit()
subscribed_students.append(my_uid) # my_uid is also a student
populate_course_projects(
session, course_id, subscribed_students, teacher_uid)
session, course_id, subscribed_students)
except SQLAlchemyError as e:
if session: # possibly error resulted in session being null
session.rollback()
Expand Down Expand Up @@ -209,12 +211,8 @@ def populate_course_students(session, course_id, students):
return [student.uid for student in subscribed_students]


def populate_course_projects(session, course_id, students, teacher_uid):
def populate_course_projects(session, course_id, students):
"""Populates the course with projects and submissions, also creates the files"""
teacher_relation = course_admin_generator(course_id, teacher_uid)
session.add(teacher_relation)
session.commit()

num_projects = random.randint(1, 3)
projects = generate_projects(course_id, num_projects)
session.add_all(projects)
Expand Down
2 changes: 1 addition & 1 deletion backend/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ services:
TEST_AUTHENTICATION_URL: http://auth-server:5001 # Use the service name defined in Docker Compose
AUTH_METHOD: test
JWT_SECRET_KEY: Test123
UPLOAD_URL: /data/assignments
UPLOAD_FOLDER: /data/assignments
DOCS_JSON_PATH: static/OpenAPI_Object.yaml
DOCS_URL: /docs
volumes:
Expand Down
108 changes: 72 additions & 36 deletions backend/tests/endpoints/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,31 @@
from project.models.course_relation import CourseStudent, CourseAdmin
from project.models.course_share_code import CourseShareCode
from project.models.submission import Submission, SubmissionStatus
from project.models.project import Project
from project.models.project import Project, Runner

### AUTHENTICATION & AUTHORIZATION ###
@fixture
def data_map(course: Course) -> dict[str, Any]:
def data_map(course: Course, project: Project, submission: Submission) -> dict[str, Any]:
"""Map an id to data"""
return {
"@course_id": course.course_id
"@course_id": course.course_id,
"@project_id": project.project_id,
"@submission_id": submission.submission_id
}

@fixture
def auth_test(
request: FixtureRequest, client: FlaskClient, data_map: dict[str, Any]
) -> tuple[str, Any, str, bool]:
) -> tuple[str, Any, str, bool, dict[str, Any]]:
"""Add concrete test data to auth"""
endpoint, method, token, allowed = request.param

for k, v in data_map.items():
endpoint = endpoint.replace(k, str(v))
for key, value in data_map.items():
endpoint = endpoint.replace(key, str(value))
csrf = get_csrf_from_login(client, token) if token else None
data = {k.strip("@"):v for k, v in data_map.items()}

return endpoint, getattr(client, method), csrf, allowed
return endpoint, getattr(client, method), csrf, allowed, data



Expand Down Expand Up @@ -122,6 +125,68 @@ def course(session: Session, student: User, teacher: User, admin: User) -> Cours
return course


### PROJECTS ###
@fixture
def project(session: Session, course: Course):
"""Return a project entry"""
project = Project(
title="Test project",
description="Test project",
deadlines=[{"deadline":"2024-05-23T21:59:59", "description":"Final deadline"}],
course_id=course.course_id,
visible_for_students=True,
archived=False,
runner=Runner.GENERAL,
regex_expressions=[".*.pdf"]
)
session.add(project)
session.commit()
return project



### SUBMISSIONS ###
@fixture
def submission(session: Session, student: User, project: Project):
"""Return a submission entry"""
submission = Submission(
uid=student.uid,
project_id=project.project_id,
submission_time=datetime(2024,5,23,22,00,00,tzinfo=ZoneInfo("GMT")),
submission_path="/1",
submission_status= SubmissionStatus.SUCCESS
)
session.add(submission)
session.commit()
return submission

### FILES ###
@fixture
def file_empty():
"""Return an empty file"""
descriptor, name = tempfile.mkstemp()
with open(descriptor, "rb") as temp:
yield temp, name

@fixture
def file_no_name():
"""Return a file with no name"""
descriptor, name = tempfile.mkstemp()
with open(descriptor, "w", encoding="UTF-8") as temp:
temp.write("This is a test file.")
with open(name, "rb") as temp:
yield temp, ""

@fixture
def files():
"""Return a temporary file"""
name = "/tmp/test.pdf"
with open(name, "w", encoding="UTF-8") as file:
file.write("This is a test file.")
with open(name, "rb") as file:
yield [(file, name)]



### OTHER ###
@pytest.fixture
Expand Down Expand Up @@ -193,35 +258,6 @@ def valid_user_entries(session):

return users

@pytest.fixture
def file_empty():
"""Return an empty file"""
descriptor, name = tempfile.mkstemp()
with open(descriptor, "rb") as temp:
yield temp, name

@pytest.fixture
def file_no_name():
"""Return a file with no name"""
descriptor, name = tempfile.mkstemp()
with open(descriptor, "w", encoding="UTF-8") as temp:
temp.write("This is a test file.")
with open(name, "rb") as temp:
yield temp, ""

@pytest.fixture
def files():
"""Return a temporary file"""
descriptor01, name01 = tempfile.mkstemp()
with open(descriptor01, "w", encoding="UTF-8") as temp:
temp.write("This is a test file.")
descriptor02, name02 = tempfile.mkstemp()
with open(descriptor02, "w", encoding="UTF-8") as temp:
temp.write("This is a test file.")
with open(name01, "rb") as temp01:
with open(name02, "rb") as temp02:
yield [(temp01, name01), (temp02, name02)]

@pytest.fixture
def course_teacher_ad():
"""A user that's a teacher for testing"""
Expand Down
4 changes: 2 additions & 2 deletions backend/tests/endpoints/course/courses_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class TestCourseEndpoint(TestEndpoint):
authentication_tests("/courses/@course_id/admins", ["get", "post", "delete"])

@mark.parametrize("auth_test", authentication_tests, indirect=True)
def test_authentication(self, auth_test: tuple[str, Any, str, bool]):
def test_authentication(self, auth_test: tuple[str, Any, str, bool, dict[str, Any]]):
"""Test the authentication"""
super().authentication(auth_test)

Expand Down Expand Up @@ -68,7 +68,7 @@ def test_authentication(self, auth_test: tuple[str, Any, str, bool]):
["student", "student_other", "teacher_other", "admin", "admin_other"])

@mark.parametrize("auth_test", authorization_tests, indirect=True)
def test_authorization(self, auth_test: tuple[str, Any, str, bool]):
def test_authorization(self, auth_test: tuple[str, Any, str, bool, dict[str, Any]]):
"""Test the authorization"""
super().authorization(auth_test)

Expand Down
18 changes: 9 additions & 9 deletions backend/tests/endpoints/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def query_parameter_tests(
new_endpoint = endpoint + "?parameter=0"
tests.append(param(
(new_endpoint, method, token, True),
id = f"{new_endpoint} {method.upper()} {token} (parameter 0 500)"
id = f"{new_endpoint} {method.upper()} {token} (parameter 0 400)"
))

for parameter in parameters:
Expand All @@ -84,23 +84,23 @@ def query_parameter_tests(
class TestEndpoint:
"""Base class for endpoint tests"""

def authentication(self, auth_test: tuple[str, Any, str, bool]):
def authentication(self, auth_test: tuple[str, Any, str, bool, dict[str, Any]]):
"""Test if the authentication for the given endpoint works"""

endpoint, method, csrf, allowed = auth_test
endpoint, method, csrf, allowed, data = auth_test

if csrf:
response = method(endpoint, headers = {"X-CSRF-TOKEN":csrf})
response = method(endpoint, headers = {"X-CSRF-TOKEN":csrf}, data = data)
else:
response = method(endpoint)
response = method(endpoint, json = data)
assert allowed == (response.status_code != 401)

def authorization(self, auth_test: tuple[str, Any, str, bool]):
def authorization(self, auth_test: tuple[str, Any, str, bool, dict[str, Any]]):
"""Test if the authorization for the given endpoint works"""

endpoint, method, csrf, allowed = auth_test
endpoint, method, csrf, allowed, data = auth_test

response = method(endpoint, headers = {"X-CSRF-TOKEN":csrf})
response = method(endpoint, headers = {"X-CSRF-TOKEN":csrf}, data = data)
assert allowed == (response.status_code != 403)

def data_field_type(self, test: tuple[str, Any, str, dict[str, Any]]):
Expand All @@ -118,7 +118,7 @@ def query_parameter(self, test: tuple[str, Any, str, bool]):

response = method(endpoint, headers = {"X-CSRF-TOKEN":csrf})
if wrong_parameter:
assert wrong_parameter == (response.status_code == 200)
assert wrong_parameter == (response.status_code != 200)

if not wrong_parameter:
assert response.json["data"] == []
Loading

0 comments on commit d90e1f9

Please sign in to comment.