From 4270777ffc104ffc49ad59d8986350b9598a6576 Mon Sep 17 00:00:00 2001
From: Brent Silkyn <160223373+bsilkyn@users.noreply.github.com>
Date: Thu, 23 May 2024 11:54:26 +0200
Subject: [PATCH] test: Course test (#450)
* chore: add loads of changes to login mechanism for testing (possible options are admin, student, and all roles at the same time) #434
* chore: login users beforehand (might delete this in the future actually) #434
* chore: trying to fix adding data before tests #434
* chore: more trial and error #434
* chore: struggling.. #434
* chore: so close, but yet so far #407
* chore: fix location of preprocessor #407
* chore: remove unnecessary node events #434
* chore: test.sh script now deletes database and fills with realistic fixtures #434
* test: automatically login all users, before every test file, so that they're available, but also automatically logout after every test #434
* test: set path to supportFile correctly cypress #434
* test: removing logging out for cleaning up #434
* test: add professor login endpoint #434
* test: cleanup #434
* test: course make and course enroll test with id's applied where necessary #434
* test: forgot to actually commit the test file itself #434
* style: lint fix #434
* chore: move filling db to outside
---------
Co-authored-by: Topvennie
---
backend/authentication/views.py | 48 ++++++++++++++-----
backend/ypovoli/settings.py | 36 ++++++++++----
frontend/cypress.config.js | 6 +++
frontend/cypress/support/e2e.ts | 42 +++++++++++++++-
frontend/package-lock.json | 14 ++++++
frontend/package.json | 2 +
.../components/courses/CourseDetailCard.vue | 2 +-
.../components/courses/CourseDetailList.vue | 2 +-
.../src/components/courses/CourseForm.vue | 9 +++-
.../students/StudentCourseJoinButton.vue | 1 +
.../src/components/projects/ProjectList.vue | 2 +-
frontend/src/test/e2e/course.cy.ts | 33 +++++++++++++
frontend/src/test/e2e/login.cy.ts | 12 +----
.../dashboard/roles/TeacherDashboardView.vue | 16 +++++--
frontend/src/views/layout/base/BaseHeader.vue | 10 +++-
test.sh | 6 +++
16 files changed, 197 insertions(+), 44 deletions(-)
create mode 100644 frontend/src/test/e2e/course.cy.ts
diff --git a/backend/authentication/views.py b/backend/authentication/views.py
index 85af6b5a..f5745821 100644
--- a/backend/authentication/views.py
+++ b/backend/authentication/views.py
@@ -1,3 +1,5 @@
+from api.models.assistant import Assistant
+from api.models.teacher import Teacher
from django.http import HttpResponseRedirect
from authentication.cas.client import client
@@ -66,20 +68,23 @@ def echo(self, request: Request) -> Response:
raise AuthenticationFailed(token_serializer.errors)
-def create_user(self, request) -> Response:
+def create_user(self, request, data) -> tuple[User, bool]:
"""General function to create a user, log them in and which returns an empty html page"""
# log in user, or retrieve if they already exist
- user, created = User.objects.get_or_create(id=settings.TEST_USER_DATA["id"], defaults=settings.TEST_USER_DATA)
+ user, created = User.objects.get_or_create(id=data["id"], defaults=data)
# if it has just been created, send the signal to user_created Signal(), to also activate it as a student
if created:
- user_created.send(sender=self, attributes=settings.TEST_USER_ATTRIBUTES, user=user)
+ user_created.send(sender=self, attributes={**data, "ugentStudentID": data.id}, user=user)
# login the user
login(request, user)
# return Response with empty html page
- return Response('',
+ return user, created
+
+
+response = Response('',
status=HTTP_200_OK, headers={"Location": "/"}, content_type="text/html")
@@ -88,14 +93,31 @@ class TestUser(ViewSet):
permission_classes = [IsDebug]
- @action(detail=False, methods=['GET'], permission_classes=[IsDebug], url_path='admin')
- def login_admin(self, request, *__) -> Response:
- """This endpoint lets you log in an admin"""
- settings.TEST_USER_DATA["is_staff"] = True
- return create_user(self, request)
-
- @action(detail=False, methods=['GET'], permission_classes=[IsDebug], url_path='student')
+ @action(detail=False, methods=['POST'], permission_classes=[IsDebug], url_path='student')
def login_student(self, request, *__) -> Response:
"""This endpoint lets you log in as a student who's not an admin"""
- settings.TEST_USER_DATA["is_staff"] = False
- return create_user(self, request)
+ create_user(self, request, settings.TEST_STUDENT_DATA)
+ return response
+
+ @action(detail=False, methods=['POST'], permission_classes=[IsDebug], url_path='professor')
+ def login_professor(self, request, *__) -> Response:
+ """This endpoint lets you log in as a professor"""
+ user, created = create_user(self, request, settings.TEST_PROFESSOR_DATA)
+ if created:
+ Teacher.create(user)
+ return response
+
+ @action(detail=False, methods=['POST'], permission_classes=[IsDebug], url_path='multi')
+ def login_multi(self, request, *__) -> Response:
+ """This endpoint lets you log in as a user who's a student, an assistant and a professor at the same time"""
+ user, created = create_user(self, request, settings.TEST_MULTI_DATA)
+ if created:
+ Assistant.create(user)
+ Teacher.create(user)
+ return response
+
+ @action(detail=False, methods=['POST'], permission_classes=[IsDebug], url_path='admin')
+ def login_admin(self, request, *__) -> Response:
+ """This endpoint lets you log in an admin"""
+ create_user(self, request, settings.TEST_ADMIN_DATA)
+ return response
diff --git a/backend/ypovoli/settings.py b/backend/ypovoli/settings.py
index f0e3186e..36527c9f 100644
--- a/backend/ypovoli/settings.py
+++ b/backend/ypovoli/settings.py
@@ -28,16 +28,34 @@
# TESTING
TESTING_BASE_LINK = "http://testserver"
-TEST_USER_DATA = {
- "id": "1234",
- "username": "test",
- "email": "test@test",
- "first_name": "test",
- "last_name": "test",
+TEST_ADMIN_DATA = {
+ "id": "0",
+ "username": "admin",
+ "email": "admin@test",
+ "first_name": "admin",
+ "last_name": "admin",
+ "is_staff": True
}
-TEST_USER_ATTRIBUTES = {
- **TEST_USER_DATA,
- "ugentStudentID": "1234"
+TEST_STUDENT_DATA = {
+ "id": "6",
+ "username": "student",
+ "email": "student@test",
+ "first_name": "student",
+ "last_name": "student",
+}
+TEST_PROFESSOR_DATA = {
+ "id": "1",
+ "username": "professor",
+ "email": "professor@test",
+ "first_name": "professor",
+ "last_name": "professor",
+}
+TEST_MULTI_DATA = {
+ "id": "10",
+ "username": "multi",
+ "email": "multi@test",
+ "first_name": "multi",
+ "last_name": "multi",
}
# SECURITY WARNING: keep the secret key used in production secret!
diff --git a/frontend/cypress.config.js b/frontend/cypress.config.js
index 83abcaba..fbde2fec 100644
--- a/frontend/cypress.config.js
+++ b/frontend/cypress.config.js
@@ -1,7 +1,13 @@
import { defineConfig } from 'cypress';
+import vitePreprocessor from 'cypress-vite';
export default defineConfig({
e2e: {
+ setupNodeEvents(on, config) {
+ on('file:preprocessor',
+ vitePreprocessor('./vite.config.ts')
+ );
+ },
baseUrl: 'https://nginx',
specPattern: 'src/test/e2e/**/*.cy.{js,jsx,ts,tsx}',
},
diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts
index f80f74f8..f553c558 100644
--- a/frontend/cypress/support/e2e.ts
+++ b/frontend/cypress/support/e2e.ts
@@ -14,7 +14,45 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
-import './commands'
+import './commands.ts'
// Alternatively you can use CommonJS syntax:
-// require('./commands')
\ No newline at end of file
+// require('./commands')
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+ // log uncaught error
+ console.error('Uncaught exception:', err.message);
+
+ return !err.message.includes('401');
+});
+
+const logout = () => {
+ cy.getCookie('csrftoken').then((cookie) => {
+ cy.getCookie('sessionid').then((cookie2) => {
+ if (cookie && cookie2) {
+ cy.request({
+ method: 'POST',
+ url: '/api/auth/cas/logout/',
+ headers: {
+ 'Referer': Cypress.config('baseUrl'),
+ 'X-CSRFToken': cookie.value,
+ },
+ });
+ }
+ })
+
+ });
+}
+
+// before(() => {
+// cy.request('POST', '/api/auth/test-user/student/');
+// logout();
+// cy.request('POST', '/api/auth/test-user/multi/');
+// logout();
+// cy.request('POST', '/api/auth/test-user/admin/');
+// logout();
+// })
+
+afterEach(() => {
+ logout();
+})
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 6189c3b4..b38c986e 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -33,6 +33,7 @@
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@vitejs/plugin-vue": "^5.0.4",
"cypress": "^13.7.1",
+ "cypress-vite": "^1.5.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
@@ -2812,6 +2813,19 @@
"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
}
},
+ "node_modules/cypress-vite": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/cypress-vite/-/cypress-vite-1.5.0.tgz",
+ "integrity": "sha512-vvTMqJZgI3sN2ylQTi4OQh8LRRjSrfrIdkQD5fOj+EC/e9oHkxS96lif1SyDF1PwailG1tnpJE+VpN6+AwO/rg==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": "^3.5.3",
+ "debug": "^4.3.4"
+ },
+ "peerDependencies": {
+ "vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
+ }
+ },
"node_modules/cypress/node_modules/proxy-from-env": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 38241a94..a6a82480 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,6 +11,7 @@
"test": "vitest run",
"cypress:open": "cypress open",
"cypress:test": "cypress run",
+ "cypress:install": "cypress install",
"lint": "eslint src --ext .ts,.vue",
"lint-fix": "eslint src --ext .ts,.vue --fix"
},
@@ -40,6 +41,7 @@
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@vitejs/plugin-vue": "^5.0.4",
"cypress": "^13.7.1",
+ "cypress-vite": "^1.5.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
diff --git a/frontend/src/components/courses/CourseDetailCard.vue b/frontend/src/components/courses/CourseDetailCard.vue
index 049ee0f3..ce92fc59 100644
--- a/frontend/src/components/courses/CourseDetailCard.vue
+++ b/frontend/src/components/courses/CourseDetailCard.vue
@@ -19,7 +19,7 @@ const { getImport } = useGlob(import.meta.glob('@/assets/img/faculties/*.png', {
-
+
{{ t('components.list.noCourses.student') }}
-
+
diff --git a/frontend/src/components/courses/CourseForm.vue b/frontend/src/components/courses/CourseForm.vue
index 8cf979c1..47114855 100644
--- a/frontend/src/components/courses/CourseForm.vue
+++ b/frontend/src/components/courses/CourseForm.vue
@@ -122,7 +122,14 @@ watchEffect(() => {
-
+
diff --git a/frontend/src/components/courses/students/StudentCourseJoinButton.vue b/frontend/src/components/courses/students/StudentCourseJoinButton.vue
index 34da3478..43a38921 100644
--- a/frontend/src/components/courses/students/StudentCourseJoinButton.vue
+++ b/frontend/src/components/courses/students/StudentCourseJoinButton.vue
@@ -66,6 +66,7 @@ async function leaveCourse(): Promise {
-
+
@@ -95,7 +103,7 @@ watchImmediate(
-
+
{{ t('components.list.noCourses.teacher') }}
-
+
diff --git a/frontend/src/views/layout/base/BaseHeader.vue b/frontend/src/views/layout/base/BaseHeader.vue
index 519ecbe0..2a9b20e2 100644
--- a/frontend/src/views/layout/base/BaseHeader.vue
+++ b/frontend/src/views/layout/base/BaseHeader.vue
@@ -107,7 +107,13 @@ const items = computed(() => [
-
+
@@ -139,7 +145,7 @@ const items = computed(() => [