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', { @@ -95,7 +103,7 @@ watchImmediate( - +