Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project tests #416

Merged
merged 20 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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