From 82cc34f4dcc39719868a27bab2a3c794afea2eba Mon Sep 17 00:00:00 2001 From: Ruben Vandamme Date: Wed, 13 Mar 2024 10:16:55 +0100 Subject: [PATCH 01/13] documentation added --- backend/documentation.md | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 backend/documentation.md diff --git a/backend/documentation.md b/backend/documentation.md new file mode 100644 index 00000000..8d2b6fc5 --- /dev/null +++ b/backend/documentation.md @@ -0,0 +1,55 @@ +# Documentation Backend + +## 1) Database + +#### I) setup + +- step 1: Install and set up **PostgreSQL**. We refer to the [official documentation](https://www.postgresql.org/docs/16/admin.html). +- step 2: Start a PostgreSQL server. +- step 3: Create a database on this server. + +We use **SQLAlchemy** to access this database in our backend app. SQLAlchemy allows us to start defining tables, performing queries, etc.. The setup of SQLAlchemy happens in [db/extensions.py]. Here, an SQLAlchemy engine is created. This engine is the component that manages connections to the database. A [database URI](https://docs.sqlalchemy.org/en/20/core/engines.html) is needed to create such an engine. Because we host the backend app and the database in the same place, we use localhost in the URI as default. This is not mandatory; the database and backend app are two seperate things. + +For test purposes, mockup data is available in [fill_database_mock.py]. A visual representation of the database is also recommended (eg. [pgAdmin](https://www.pgadmin.org/)). + +#### II) tables + +Using our EER diagram, we now want to create the corresponding tables. We use SQLAlchemy's declarative base pattern. Start by defining a Base class. This class contains the necessary functionality to interact with the database. Next, we create a python class for each table we need, and make it inherit the Base class. We call this a model (see [db/models/models.py]). For specifics on how to define these models, see [this link](https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/basic_use.html). + +An important thing to notice in this file is that other than the Base class, all models also inherit a class named AbstractModel. It makes sure that each model implements *to_domain_model*. We will come back to this function later on. + + +#### III) sessions + +A SQLAlchemy session object provides an extra abstract layer to the engine. We will use these session objects in the logic part of our domain layer (see later). + +> Using SQLAlchemy session objects simplifies database interactions by encapsulating transactions, providing features such as identity management, automatic transaction management, and a unit of work pattern, promoting cleaner, more maintainable code. \ +\- ChatGPT + +In [db/sessions.py], we define a generator for these session objects using our engine. + +## 2) Domain layer + +#### I) operations +in [domain/logic/] we define the actual backend functionality. Examples are *get_subjects_of_teacher* or *create_submission*. Some things to notice is that every function needs a session object, and that we manually commit changes. In [domain/logic/basic_operations.py], we define an abstract *get* and *get_all*, as these type of operations happen a lot. This *get* is of course not the same as a get request to the API. Three main errors that we provide manual coverage for are ItemNotFoundError, ActionAlreadyPerformedError and NoSuchRelationError (see [db/errors/]). + +#### II) dataclasses + +Now is a good time to explain the function *to_domain_model* from earlier. When we call a logic function, we don't want to return an instance of the Base class from SQLAlchemy. Instead we want to return very universal objects with that correspond one-to-one with an entity + attribute from our EER diagram. That's what a dataclass is. They are defined in [domain/models/]. + +> In our code, we use the name dataclass for two seperate things. The first is the @dataclass tag from the standard python library [dataclasses](https://docs.python.org/3/library/dataclasses.html). The other is the domain layer object just explained like SubmissionDataclass and TeacherDataclass. + +These universal dataclasses inherit the **Pydantic** BaseModel. It allows for automatic data validation, JSON schema generation and more. More information on Pydantic can be found [here](https://docs.pydantic.dev/latest/why/). + +## 3) API + +We use **FastAPI** as framework. FastAPI follows the OpenAPI Specification. Its idea is to, in its turn, specify a REST API with a YAML document. This document can be used to generate documentation and methods for API endpoints. In every file in [/routes/], we can see a FastAPI router defined that represents some routes that are logically grouped together. + +> A route is a URL pattern that directs HTTP requests to specific handlers or controllers in a web application, defining how requests are processed and responses are generated. It plays a crucial role in organizing the flow of data within the application. \ +\- ChatGPT + +Every route recieves a session object as a dependency injection, to forward to the corresponding logic operation. Dependencies are components that need to be executed before the route operation function is called. We let FastAPI handle this for us. Other than a database connection through the session object, we sometimes also inject some authentication/authorization logic (see [routes/dependencies/role_dependencies.py]) with corresponding errors in [routes/errors/authentication.py]. + +## 4) Running the app + +We start by defining app = FastAPI() in [app.py]. Next, we add our routers from the previous section. We also add some exception handlers using the corresponding tag. FastAPI calls these handlers for us if needed. this way, we only have to return a corresponding JSONResponse. Finally, we start the app using a **uvicorn** server. This is the standard for FastApi. "app:app" specifies the location of the FastApi object. The first "app" refers to the module (i.e., app.py), and the second "app" refers to the variable (i.e., the FastAPI application object). By default, Uvicorn will run on the localhost port 8000. Another thing to note in this file is that we provide [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) functionality in production. \ No newline at end of file From 73b1c4ee194a3bf99dd9fe5bf3979bd336519fc9 Mon Sep 17 00:00:00 2001 From: EmmaVandewalle Date: Wed, 13 Mar 2024 13:48:55 +0100 Subject: [PATCH 02/13] feat: deadline table made for students --- .../src/assets/styles/students_components.css | 31 +++++++++++++++++ frontend/src/components/ProjectCard.tsx | 7 ---- frontend/src/pages/student/DeadlineTable.tsx | 34 +++++++++++++++++++ frontend/src/pages/student/HomeStudent.tsx | 30 +++++++++------- 4 files changed, 83 insertions(+), 19 deletions(-) delete mode 100644 frontend/src/components/ProjectCard.tsx create mode 100644 frontend/src/pages/student/DeadlineTable.tsx diff --git a/frontend/src/assets/styles/students_components.css b/frontend/src/assets/styles/students_components.css index 4872219b..9473459e 100644 --- a/frontend/src/assets/styles/students_components.css +++ b/frontend/src/assets/styles/students_components.css @@ -1,4 +1,8 @@ .student-main { + width: 96vw; +} + +.student-left { width: 60vw; flex-wrap: wrap; overflow-y: scroll; @@ -16,4 +20,31 @@ .p-positive { color: green; +} + +.student-right { + width: 36vw; +} + +.deadline { + background-color: #e0e3ff; + width: 15vw; + height: 75vh; + border-radius: 0.50rem; +} + +.deadline-head { + background-color: #5f63c8; + border-radius: 0.50rem 0.50rem 0 0; +} + +.deadline-elements { + height: 100%; + overflow-y: scroll; + border-radius: 0 0 0.50rem 0.50rem; +} + +.deadline-card { + background-color: #9c9afd; + border-bottom: thin solid; } \ No newline at end of file diff --git a/frontend/src/components/ProjectCard.tsx b/frontend/src/components/ProjectCard.tsx deleted file mode 100644 index 57a70230..00000000 --- a/frontend/src/components/ProjectCard.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import {JSX} from "react"; - -export function ProjectCard(): JSX.Element { - return ( - <>Project - Subject - ) -} \ No newline at end of file diff --git a/frontend/src/pages/student/DeadlineTable.tsx b/frontend/src/pages/student/DeadlineTable.tsx new file mode 100644 index 00000000..8b736e50 --- /dev/null +++ b/frontend/src/pages/student/DeadlineTable.tsx @@ -0,0 +1,34 @@ +import {JSX} from "react"; +import { FaArrowRightLong } from "react-icons/fa6"; + +function DeadlineElement(): JSX.Element { + return ( +
+

17:00 - 23/02

+
+ +

Markov Decision Diagram

+
+
+ ) +} + +function DeadlineTable(): JSX.Element { + return ( +
+
+

komende deadlines

+
+
+ + + + + + +
+
+ ) +} + +export default DeadlineTable; \ No newline at end of file diff --git a/frontend/src/pages/student/HomeStudent.tsx b/frontend/src/pages/student/HomeStudent.tsx index d53fa306..272b13fb 100644 --- a/frontend/src/pages/student/HomeStudent.tsx +++ b/frontend/src/pages/student/HomeStudent.tsx @@ -5,6 +5,7 @@ import ProjectCardStudent from "./ProjectCardStudent.tsx"; import '../../assets/styles/students_components.css' import {useRouteLoaderData} from "react-router-dom"; import {studentLoaderObject} from "../../dataloaders/StudentLoader.ts"; +import DeadlineTable from "./DeadlineTable.tsx"; export default function HomeStudent(): JSX.Element { @@ -21,18 +22,23 @@ export default function HomeStudent(): JSX.Element {
-
- - - - - - - - - - - +
+
+ + + + + + + + + + + +
+
+ +
From 234110dfe4a7977acd65e5857e8ab6aa1476914c Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:16:46 +0100 Subject: [PATCH 03/13] silenced some unittest specific warnings #58 --- backend/pyproject.toml | 6 +++++- backend/tests/teachers_test.py | 27 --------------------------- backend/tests/test_edge_cases.py | 0 backend/tests/test_main.py | 0 backend/tests/test_project.py | 0 backend/tests/test_stress.py | 0 backend/tests/test_student.py | 0 backend/tests/test_subject.py | 0 backend/tests/test_submission.py | 0 backend/tests/test_teacher.py | 0 10 files changed, 5 insertions(+), 28 deletions(-) delete mode 100644 backend/tests/teachers_test.py create mode 100644 backend/tests/test_edge_cases.py create mode 100644 backend/tests/test_main.py create mode 100644 backend/tests/test_project.py create mode 100644 backend/tests/test_stress.py create mode 100644 backend/tests/test_student.py create mode 100644 backend/tests/test_subject.py create mode 100644 backend/tests/test_submission.py create mode 100644 backend/tests/test_teacher.py diff --git a/backend/pyproject.toml b/backend/pyproject.toml index eaabfa47..ef780704 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -91,7 +91,11 @@ ignore = [ "PLR0913", # Too many arguments in function "FBT001", # Boolean-typed positional argument in function dfinition "FBT002", - "B008" + "B008", + "PT009", + "PT027", + "FBT003", + "DTZ005", ] # Allow fix for all enabled rules (when `--fix`) is provided. diff --git a/backend/tests/teachers_test.py b/backend/tests/teachers_test.py deleted file mode 100644 index af30bf49..00000000 --- a/backend/tests/teachers_test.py +++ /dev/null @@ -1,27 +0,0 @@ -import json -import unittest -from http import HTTPStatus - -from fastapi.testclient import TestClient - -from app import app - - -class LesgeverTestCase(unittest.TestCase): - def setUp(self) -> None: - self.app = TestClient(app) - - - def test_create_teacher_bad_request(self) -> None: - response = self.app.post("/teachers", data={}) - assert response.status_code == HTTPStatus.BAD_REQUEST - assert "error" in json.loads(response.data) - - def test_create_teacher_success(self) -> None: - teacher_data = {"name": "Bart De Bruyn"} - response = self.app.post("/teachers", data=teacher_data) - assert response.status_code == HTTPStatus.CREATED - - -if __name__ == "__main__": - unittest.main() diff --git a/backend/tests/test_edge_cases.py b/backend/tests/test_edge_cases.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_project.py b/backend/tests/test_project.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_stress.py b/backend/tests/test_stress.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_student.py b/backend/tests/test_student.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_subject.py b/backend/tests/test_subject.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_submission.py b/backend/tests/test_submission.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/test_teacher.py b/backend/tests/test_teacher.py new file mode 100644 index 00000000..e69de29b From 6d2215effeeb59356f138c0d6d835cf7b2860dbb Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:17:26 +0100 Subject: [PATCH 04/13] added httpx to requirements for api testing --- backend/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index 66727a6a..2e62ebad 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -6,6 +6,7 @@ email_validator==2.1.1 fastapi==0.110.0 greenlet==3.0.3 h11==0.14.0 +httpx==0.27.0 idna==3.6 nodeenv==1.8.0 pre-commit==3.6.2 From 0a3e0e0c05c436d68a928fbc17a71e15c2ccb459 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:22:04 +0100 Subject: [PATCH 05/13] test entry point #58 --- backend/tests/test_main.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index e69de29b..2d973e4c 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -0,0 +1,14 @@ +# main.py +import unittest + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +DB_TEST_URI = "postgresql://postgres:postgres@localhost:5432/delphi-test" +test_engine = create_engine(DB_TEST_URI) +SessionLocal = sessionmaker(autocommit=False, bind=test_engine) + + +if __name__ == "__main__": + test_suite = unittest.TestLoader().discover(".") + unittest.TextTestRunner().run(test_suite) From c876f943ec99ef11b0dcb4e8b663b5edd91125b2 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:22:41 +0100 Subject: [PATCH 06/13] tests project #58 --- backend/tests/test_project.py | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/backend/tests/test_project.py b/backend/tests/test_project.py index e69de29b..f33af391 100644 --- a/backend/tests/test_project.py +++ b/backend/tests/test_project.py @@ -0,0 +1,84 @@ +# test_project.py +import unittest +from datetime import datetime + +from test_main import SessionLocal, test_engine + +from db.extensions import Base +from domain.logic.project import ( + create_project, + get_all_projects, + get_project, + get_projects_of_student, + get_projects_of_subject, + get_projects_of_teacher, +) +from domain.logic.student import create_student +from domain.logic.subject import add_student_to_subject, add_teacher_to_subject, create_subject +from domain.logic.teacher import create_teacher + + +class TestProject(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_create_and_get_project(self) -> None: + subject = create_subject(self.session, "Test Subject") + project = create_project(self.session, subject.id, "Test Project", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + retrieved_project = get_project(self.session, project.id) + self.assertEqual(project.id, retrieved_project.id) + + def test_get_all_projects(self) -> None: + subject1 = create_subject(self.session, "Test Subject 1") + subject2 = create_subject(self.session, "Test Subject 2") + create_project(self.session, subject1.id, "Test Project 1", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + create_project(self.session, subject2.id, "Test Project 2", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + self.assertEqual(len(get_all_projects(self.session)), 2) + + def test_get_projects_of_subject(self) -> None: + subject = create_subject(self.session, "Test Subject") + create_project(self.session, subject.id, "Test Project 1", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + create_project(self.session, subject.id, "Test Project 2", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + projects_of_subject = get_projects_of_subject(self.session, subject.id) + self.assertEqual(len(projects_of_subject), 2) + + def test_get_projects_of_student(self) -> None: + student = create_student(self.session, "Test Student", "teststudent@gmail.com") + subject1 = create_subject(self.session, "Test Subject 1") + subject2 = create_subject(self.session, "Test Subject 2") + add_student_to_subject(self.session, student.id, subject1.id) + add_student_to_subject(self.session, student.id, subject2.id) + create_project(self.session, subject1.id, "Test Project 1", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + create_project(self.session, subject2.id, "Test Project 2", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + projects_of_student = get_projects_of_student(self.session, student.id) + self.assertEqual(len(projects_of_student), 2) + + def test_get_projects_of_teacher(self) -> None: + teacher = create_teacher(self.session, "Test Teacher", "testteacher@gmail.com") + subject1 = create_subject(self.session, "Test Subject 1") + subject2 = create_subject(self.session, "Test Subject 2") + add_teacher_to_subject(self.session, teacher.id, subject1.id) + add_teacher_to_subject(self.session, teacher.id, subject2.id) + create_project(self.session, subject1.id, "Test Project 1", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + create_project(self.session, subject2.id, "Test Project 2", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + projects_of_teacher = get_projects_of_teacher(self.session, teacher.id) + self.assertEqual(len(projects_of_teacher), 2) + + +if __name__ == "__main__": + unittest.main() From 70760b0b2e94feb49bd6389bbe5aa813856e10a3 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:22:53 +0100 Subject: [PATCH 07/13] tests student #58 --- backend/tests/test_student.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/backend/tests/test_student.py b/backend/tests/test_student.py index e69de29b..95e8e680 100644 --- a/backend/tests/test_student.py +++ b/backend/tests/test_student.py @@ -0,0 +1,40 @@ +# test_student.py +import unittest + +from test_main import SessionLocal, test_engine + +from db.extensions import Base +from domain.logic.student import create_student, get_all_students, get_student +from domain.logic.subject import add_student_to_subject, create_subject, get_subjects_of_student + + +class TestStudent(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_create_and_get_student(self) -> None: + student = create_student(self.session, "Test Student", "teststudent@gmail.com") + retrieved_student = get_student(self.session, student.id) + self.assertEqual(student.id, retrieved_student.id) + + def test_get_all_students(self) -> None: + create_student(self.session, "Test Student 1", "teststudent1@gmail.com") + create_student(self.session, "Test Student 2", "teststudent2@gmail.com") + self.assertEqual(len(get_all_students(self.session)), 2) + + def test_add_student_to_subject(self) -> None: + student = create_student(self.session, "Test Student", "teststudent@gmail.com") + subject = create_subject(self.session, "Test Subject") + add_student_to_subject(self.session, student.id, subject.id) + subjects_of_student = get_subjects_of_student(self.session, student.id) + self.assertIn(subject.id, [subject.id for subject in subjects_of_student]) + + +if __name__ == "__main__": + unittest.main() From 6ee5e884aab130fa284689a0bc3bcb8b03e1d1a1 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:23:04 +0100 Subject: [PATCH 08/13] tests subject #58 --- backend/tests/test_subject.py | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/backend/tests/test_subject.py b/backend/tests/test_subject.py index e69de29b..d675053f 100644 --- a/backend/tests/test_subject.py +++ b/backend/tests/test_subject.py @@ -0,0 +1,56 @@ +# test_subject.py +import unittest + +from test_main import SessionLocal, test_engine + +from db.extensions import Base +from domain.logic.student import create_student +from domain.logic.subject import ( + add_student_to_subject, + add_teacher_to_subject, + create_subject, + get_all_subjects, + get_subject, + get_subjects_of_student, + get_subjects_of_teacher, +) +from domain.logic.teacher import create_teacher + + +class TestSubject(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_create_and_get_subject(self) -> None: + subject = create_subject(self.session, "Test Subject") + retrieved_subject = get_subject(self.session, subject.id) + self.assertEqual(subject.id, retrieved_subject.id) + + def test_get_all_subjects(self) -> None: + create_subject(self.session, "Test Subject 1") + create_subject(self.session, "Test Subject 2") + self.assertEqual(len(get_all_subjects(self.session)), 2) + + def test_add_student_to_subject(self) -> None: + student = create_student(self.session, "Test Student", "teststudent@gmail.com") + subject = create_subject(self.session, "Test Subject") + add_student_to_subject(self.session, student.id, subject.id) + subjects_of_student = get_subjects_of_student(self.session, student.id) + self.assertIn(subject.id, [subject.id for subject in subjects_of_student]) + + def test_add_teacher_to_subject(self) -> None: + teacher = create_teacher(self.session, "Test Teacher", "testteacher@gmail.com") + subject = create_subject(self.session, "Test Subject") + add_teacher_to_subject(self.session, teacher.id, subject.id) + subjects_of_teacher = get_subjects_of_teacher(self.session, teacher.id) + self.assertIn(subject.id, [subject.id for subject in subjects_of_teacher]) + + +if __name__ == "__main__": + unittest.main() From 418d11291d1b395b2f0e28f7cedc4016b634883a Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:23:45 +0100 Subject: [PATCH 09/13] tests submission #58 --- backend/tests/test_submission.py | 83 ++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/backend/tests/test_submission.py b/backend/tests/test_submission.py index e69de29b..9b32797c 100644 --- a/backend/tests/test_submission.py +++ b/backend/tests/test_submission.py @@ -0,0 +1,83 @@ +# test_submission.py +import unittest +from datetime import datetime + +from test_main import SessionLocal, test_engine + +from db.extensions import Base +from domain.logic.group import create_group +from domain.logic.project import create_project +from domain.logic.student import create_student +from domain.logic.subject import create_subject +from domain.logic.submission import ( + create_submission, + get_all_submissions, + get_submission, + get_submissions_of_group, + get_submissions_of_student, +) +from domain.models.SubmissionDataclass import SubmissionState + + +class TestSubmission(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_create_and_get_submission(self) -> None: + student = create_student(self.session, "Test Student", "teststudent@gmail.com") + subject = create_subject(self.session, "Test Subject") + project = create_project(self.session, subject.id, "Test Project", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + group = create_group(self.session, project.id) + submission = create_submission(self.session, student.id, group.id, "Test Message", SubmissionState.Pending, + datetime.now()) + retrieved_submission = get_submission(self.session, submission.id) + self.assertEqual(submission.id, retrieved_submission.id) + + def test_get_all_submissions(self) -> None: + student1 = create_student(self.session, "Test Student 1", "teststudent1@gmail.com") + student2 = create_student(self.session, "Test Student 2", "teststudent2@gmail.com") + subject = create_subject(self.session, "Test Subject") + project = create_project(self.session, subject.id, "Test Project", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + group = create_group(self.session, project.id) + create_submission(self.session, student1.id, group.id, "Test Message 1", SubmissionState.Pending, + datetime.now()) + create_submission(self.session, student2.id, group.id, "Test Message 2", SubmissionState.Pending, + datetime.now()) + self.assertEqual(len(get_all_submissions(self.session)), 2) + + def test_get_submissions_of_student(self) -> None: + student = create_student(self.session, "Test Student", "teststudent@gmail.com") + subject = create_subject(self.session, "Test Subject") + project = create_project(self.session, subject.id, "Test Project", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + group = create_group(self.session, project.id) + create_submission(self.session, student.id, group.id, "Test Message 1", SubmissionState.Pending, datetime.now()) + create_submission(self.session, student.id, group.id, "Test Message 2", SubmissionState.Pending, datetime.now()) + submissions_of_student = get_submissions_of_student(self.session, student.id) + self.assertEqual(len(submissions_of_student), 2) + + def test_get_submissions_of_group(self) -> None: + student1 = create_student(self.session, "Test Student 1", "teststudent1@gmail.com") + student2 = create_student(self.session, "Test Student 2", "teststudent2@gmail.com") + subject = create_subject(self.session, "Test Subject") + project = create_project(self.session, subject.id, "Test Project", datetime.now(), False, "Test Description", + "Test Requirements", True, 2) + group = create_group(self.session, project.id) + create_submission(self.session, student1.id, group.id, "Test Message 1", SubmissionState.Pending, + datetime.now()) + create_submission(self.session, student2.id, group.id, "Test Message 2", SubmissionState.Pending, + datetime.now()) + submissions_of_group = get_submissions_of_group(self.session, group.id) + self.assertEqual(len(submissions_of_group), 2) + + +if __name__ == "__main__": + unittest.main() From 49cdfab0ba5e875a83680d16849e573b20fc0f3a Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:24:04 +0100 Subject: [PATCH 10/13] tests teacher #58 --- backend/tests/test_teacher.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/backend/tests/test_teacher.py b/backend/tests/test_teacher.py index e69de29b..88925e0f 100644 --- a/backend/tests/test_teacher.py +++ b/backend/tests/test_teacher.py @@ -0,0 +1,32 @@ +# test_teacher.py +import unittest + +from test_main import SessionLocal, test_engine + +from db.extensions import Base +from domain.logic.teacher import create_teacher, get_all_teachers, get_teacher + + +class TestTeacher(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_create_and_get_teacher(self) -> None: + teacher = create_teacher(self.session, "Test Teacher", "testteacher@gmail.com") + retrieved_teacher = get_teacher(self.session, teacher.id) + self.assertEqual(teacher.id, retrieved_teacher.id) + + def test_get_all_teachers(self) -> None: + create_teacher(self.session, "Test Teacher 1", "testteacher1@gmail.com") + create_teacher(self.session, "Test Teacher 2", "testteacher2@gmail.com") + self.assertEqual(len(get_all_teachers(self.session)), 2) + + +if __name__ == "__main__": + unittest.main() From 255da1daf907fc3e459da346fd3596ee8fb8ff3b Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:24:17 +0100 Subject: [PATCH 11/13] tests stresss #58 --- backend/tests/test_stress.py | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/backend/tests/test_stress.py b/backend/tests/test_stress.py index e69de29b..167ab0a3 100644 --- a/backend/tests/test_stress.py +++ b/backend/tests/test_stress.py @@ -0,0 +1,57 @@ +# test_stress.py +import unittest +from datetime import datetime + +from test_main import SessionLocal, test_engine + +from db.extensions import Base +from domain.logic import admin, group, project, student, subject, submission, teacher +from domain.models.SubmissionDataclass import SubmissionState + + +class TestStress(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_stress(self) -> None: + # Create multiple instances of each entity + for i in range(100): + stud = student.create_student(self.session, f"Test Student {i}", f"teststudent{i}@gmail.com") + subj = subject.create_subject(self.session, f"Test Subject {i}") + proj = project.create_project(self.session, subj.id, f"Test Project {i}", datetime.now(), False, + "Test Description", + "Test Requirements", True, 2) + grp = group.create_group(self.session, proj.id) + subm = submission.create_submission(self.session, stud.id, grp.id, "Test Message", SubmissionState.Pending, + datetime.now()) + teach = teacher.create_teacher(self.session, f"Test Teacher {i}", f"testteacher{i}@gmail.com") + adm = admin.create_admin(self.session, f"Test Admin {i}", f"testadmin{i}@gmail.com") + + # Perform operations on the entities + subject.add_student_to_subject(self.session, stud.id, subj.id) + group.add_student_to_group(self.session, stud.id, grp.id) + subject.add_teacher_to_subject(self.session, teach.id, subj.id) + + # Assert the expected outcomes + self.assertEqual(student.get_student(self.session, stud.id).id, stud.id) + self.assertEqual(subject.get_subject(self.session, subj.id).id, subj.id) + self.assertEqual(project.get_project(self.session, proj.id).id, proj.id) + self.assertEqual(group.get_group(self.session, grp.id).id, grp.id) + self.assertEqual(submission.get_submission(self.session, subm.id).id, subm.id) + self.assertEqual(teacher.get_teacher(self.session, teach.id).id, teach.id) + self.assertEqual(admin.get_admin(self.session, adm.id).id, adm.id) + + # Checks outside the loop + self.assertEqual(len(student.get_all_students(self.session)), 100) + self.assertEqual(len(subject.get_all_subjects(self.session)), 100) + self.assertEqual(len(project.get_all_projects(self.session)), 100) + self.assertEqual(len(group.get_all_groups(self.session)), 100) + self.assertEqual(len(submission.get_all_submissions(self.session)), 100) + self.assertEqual(len(teacher.get_all_teachers(self.session)), 100) + self.assertEqual(len(admin.get_all_admins(self.session)), 100) From 9300a98d56fb3c157d0992b133c0538c9cef5d45 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 14:24:31 +0100 Subject: [PATCH 12/13] tests edge cases #58 --- backend/tests/test_edge_cases.py | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/tests/test_edge_cases.py b/backend/tests/test_edge_cases.py index e69de29b..1550e287 100644 --- a/backend/tests/test_edge_cases.py +++ b/backend/tests/test_edge_cases.py @@ -0,0 +1,36 @@ +# test_edge_cases.py +import unittest +from datetime import datetime + +from test_main import SessionLocal, test_engine + +from db.errors.database_errors import ItemNotFoundError +from db.extensions import Base +from domain.logic import group, student, submission +from domain.models.SubmissionDataclass import SubmissionState + + +class TestEdgeCases(unittest.TestCase): + def setUp(self) -> None: + Base.metadata.drop_all(test_engine) + Base.metadata.create_all(test_engine) + self.session = SessionLocal() + + def tearDown(self) -> None: + self.session.rollback() + self.session.close() + + def test_add_student_to_non_existent_group(self) -> None: + stud = student.create_student(self.session, "Test Student", "teststudent@gmail.com") + with self.assertRaises(ItemNotFoundError): + group.add_student_to_group(self.session, stud.id, 999) + + def test_create_submission_for_non_existent_project(self) -> None: + stud = student.create_student(self.session, "Test Student", "teststudent@gmail.com") + with self.assertRaises(ItemNotFoundError): + submission.create_submission(self.session, stud.id, 999, "Test Message", SubmissionState.Pending, + datetime.now()) + + +if __name__ == "__main__": + unittest.main() From a24b201675980f4186f35206f962835c082118ee Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Wed, 13 Mar 2024 16:12:57 +0100 Subject: [PATCH 13/13] remove main function #58 --- backend/tests/test_main.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index 2d973e4c..1e58d766 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -1,14 +1,6 @@ -# main.py -import unittest - from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker DB_TEST_URI = "postgresql://postgres:postgres@localhost:5432/delphi-test" test_engine = create_engine(DB_TEST_URI) SessionLocal = sessionmaker(autocommit=False, bind=test_engine) - - -if __name__ == "__main__": - test_suite = unittest.TestLoader().discover(".") - unittest.TextTestRunner().run(test_suite)