Skip to content

Commit

Permalink
Project tests (#416)
Browse files Browse the repository at this point in the history
* Auth tests

* Fixing the legacy warning

* Adding auth decorators to the project submission download enpoints

* Fixing auth tests

* Broken data field and query parameter tests

* Fixing query parameter tests

* More tests but first fixing some issues

* working tests

* remove bad param filter

* stuff
  • Loading branch information
JarneClauw authored May 23, 2024
1 parent 15f0c70 commit 3d4fe01
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 13 deletions.
73 changes: 64 additions & 9 deletions backend/tests/endpoints/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from datetime import datetime
from zoneinfo import ZoneInfo
from typing import Any
from zipfile import ZipFile
import os

import pytest
from pytest import fixture, FixtureRequest
from flask.testing import FlaskClient
Expand Down Expand Up @@ -125,12 +128,13 @@ 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",
title="project",
description="Test project",
deadlines=[{"deadline":"2024-05-23T21:59:59", "description":"Final deadline"}],
course_id=course.course_id,
Expand All @@ -143,6 +147,45 @@ def project(session: Session, course: Course):
session.commit()
return project

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

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

@fixture
def projects(project: Project, project_invisible: Project, project_archived: Project):
"""Return a list of project entries"""
return [project, project_invisible, project_archived]



### SUBMISSIONS ###
Expand All @@ -161,13 +204,6 @@ def submission(session: Session, student: User, project: Project):
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"""
Expand All @@ -176,15 +212,34 @@ def file_no_name():
temp.write("This is a test file.")
with open(name, "rb") as temp:
yield temp, ""
os.remove(name)

@fixture
def files():
"""Return a temporary file"""
name = "/tmp/test.pdf"
name = "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)]
os.remove(name)

@fixture
def file_assignment():
"""Return an assignment file for a project"""
assignment_file = "assignment.md"
assignment_content = "# Assignment"
with open(assignment_file, "w", encoding="UTF-8") as file:
file.write(assignment_content)

zip_file = "project.zip"
with ZipFile(zip_file, "w") as zipf:
zipf.write(assignment_file)

yield (zipf, zip_file)

os.remove(assignment_file)
os.remove(zip_file)



Expand Down
168 changes: 164 additions & 4 deletions backend/tests/endpoints/project_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,173 @@
"""Tests for project endpoints."""

from typing import Any
import json

from pytest import mark
from flask.testing import FlaskClient

from project.models.project import Project
from tests.utils.auth_login import get_csrf_from_login
from tests.endpoints.endpoint import (
TestEndpoint,
authentication_tests,
authorization_tests,
query_parameter_tests
)

class TestProjectsEndpoint(TestEndpoint):
"""Class to test the projects API endpoint"""

### AUTHENTICATION ###
# Where is login required
authentication_tests = \
authentication_tests("/projects", ["get", "post"]) + \
authentication_tests("/projects/@project_id", ["get", "patch", "delete"]) + \
authentication_tests("/projects/@project_id/assignment", ["get"]) + \
authentication_tests("/projects/@project_id/submissions-download", ["get"]) + \
authentication_tests("/projects/@project_id/latest-per-user", ["get"])

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



### AUTHORIZATION ###
# Who can access what
authorization_tests = \
authorization_tests("/projects", "get",
["student", "student_other", "teacher", "teacher_other", "admin", "admin_other"],
[]) + \
authorization_tests("/projects", "post",
["teacher"],
["student", "student_other", "teacher_other", "admin", "admin_other"]) + \
authorization_tests("/projects/@project_id", "get",
["student", "teacher", "admin"],
["student_other", "teacher_other", "admin_other"]) + \
authorization_tests("/projects/@project_id", "patch",
["teacher", "admin"],
["student", "student_other", "teacher_other", "admin_other"]) + \
authorization_tests("/projects/@project_id", "delete",
["teacher"],
["student", "student_other", "teacher_other", "admin", "admin_other"]) + \
authorization_tests("/projects/@project_id/assignment", "get",
["student", "teacher", "admin"],
["student_other", "teacher_other", "admin_other"]) + \
authorization_tests("/projects/@project_id/submissions-download", "get",
["teacher", "admin"],
["student", "student_other", "teacher_other", "admin_other"]) + \
authorization_tests("/projects/@project_id/latest-per-user", "get",
["teacher", "admin"],
["student_other", "teacher_other", "admin_other"])

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



### QUERY PARAMETER ###
# Test a query parameter, should return [] for wrong values
query_parameter_tests = \
query_parameter_tests("/projects", "get", "student", ["project_id", "title", "course_id"])

@mark.parametrize("query_parameter_test", query_parameter_tests, indirect=True)
def test_query_parameters(self, query_parameter_test: tuple[str, Any, str, bool]):
"""Test a query parameter"""
super().query_parameter(query_parameter_test)



### PROJECTS ###
def test_get_projects(self, client: FlaskClient, projects: list[Project]):
"""Test getting all projects"""
response = client.get(
"/projects",
headers = {"X-CSRF-TOKEN":get_csrf_from_login(client, "student")}
)
assert response.status_code == 200
data = response.json["data"]
assert [project["title"] in ["project", "archived project"] for project in data]

def test_get_projects_project_id(
self, client: FlaskClient, api_host: str, project: Project, projects: list[Project]
):
"""Test getting all projects for a given project_id"""
response = client.get(
f"/projects?project_id={project.project_id}",
headers = {"X-CSRF-TOKEN":get_csrf_from_login(client, "teacher")}
)
assert response.status_code == 200
data = response.json["data"]
assert len(data) == 1
assert data[0]["project_id"] == f"{api_host}/projects/{project.project_id}"

def test_get_projects_title(
self, client: FlaskClient, project: Project, projects: list[Project]
):
"""Test getting all projects for a given title"""
response = client.get(
f"/projects?title={project.title}",
headers = {"X-CSRF-TOKEN":get_csrf_from_login(client, "teacher")}
)
assert response.status_code == 200
data = response.json["data"]
assert len(data) == 1
assert data[0]["title"] == project.title

def test_get_projects_course_id(
self, client: FlaskClient, project: Project, projects: list[Project]
):
"""Test getting all projects for a given course_id"""
response = client.get(
f"/projects?course_id={project.course_id}",
headers = {"X-CSRF-TOKEN":get_csrf_from_login(client, "teacher")}
)
assert response.status_code == 200
assert len(response.json["data"]) == len(projects)



### PROJECT ###
def test_patch_project(self, client: FlaskClient, project: Project):
"""Test patching a project"""
csrf = get_csrf_from_login(client, "teacher")
response = client.patch(
f"/projects/{project.project_id}",
headers = {"X-CSRF-TOKEN":csrf},
data = {
"title": "A new title"
}
)
assert response.status_code == 200
response = client.get(
f"/projects/{project.project_id}",
headers = {"X-CSRF-TOKEN":csrf}
)
assert response.status_code == 200
data = response.json["data"]
assert data["title"] == "A new title"

def test_delete_project(self, client: FlaskClient, project: Project):
"""Test deleting a project"""
csrf = get_csrf_from_login(client, "teacher")
response = client.delete(
f"/projects/{project.project_id}",
headers = {"X-CSRF-TOKEN":csrf}
)
assert response.status_code == 200
response = client.get(
f"/projects/{project.project_id}",
headers = {"X-CSRF-TOKEN":csrf}
)
assert response.status_code == 404



### OLD TESTS ###
def test_assignment_download(client, valid_project):
"""
Method for assignment download
Expand All @@ -26,7 +190,6 @@ def test_assignment_download(client, valid_project):
# 404 because the file is not found, no assignment.md in zip file
assert response.status_code == 404


def test_not_found_download(client):
"""
Test a not present project download
Expand All @@ -37,22 +200,19 @@ def test_not_found_download(client):
response = client.get("/projects/-1/assignments", headers = {"X-CSRF-TOKEN":csrf})
assert response.status_code == 404


def test_projects_home(client):
"""Test home project endpoint."""
csrf = get_csrf_from_login(client, "teacher1")
response = client.get("/projects", headers = {"X-CSRF-TOKEN":csrf})
assert response.status_code == 200


def test_getting_all_projects(client):
"""Test getting all projects"""
csrf = get_csrf_from_login(client, "teacher1")
response = client.get("/projects", headers = {"X-CSRF-TOKEN":csrf})
assert response.status_code == 200
assert isinstance(response.json['data'], list)


def test_post_project(client, valid_project):
"""Test posting a project to the database and testing if it's present"""
valid_project["deadlines"] = json.dumps(valid_project["deadlines"])
Expand Down

0 comments on commit 3d4fe01

Please sign in to comment.