Skip to content

Commit

Permalink
test: Course test (#450)
Browse files Browse the repository at this point in the history
* 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 <vincent@vallaeys.com>
  • Loading branch information
bsilkyn and Topvennie authored May 23, 2024
1 parent 4c95b46 commit 4270777
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 44 deletions.
48 changes: 35 additions & 13 deletions backend/authentication/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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('<!DOCTYPE html><html></html>',
return user, created


response = Response('<!DOCTYPE html><html></html>',
status=HTTP_200_OK, headers={"Location": "/"}, content_type="text/html")


Expand All @@ -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
36 changes: 27 additions & 9 deletions backend/ypovoli/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
6 changes: 6 additions & 0 deletions frontend/cypress.config.js
Original file line number Diff line number Diff line change
@@ -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}',
},
Expand Down
42 changes: 40 additions & 2 deletions frontend/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,45 @@
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'
import './commands.ts'

// Alternatively you can use CommonJS syntax:
// require('./commands')
// 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();
})
14 changes: 14 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/courses/CourseDetailCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { getImport } = useGlob(import.meta.glob('@/assets/img/faculties/*.png', {

<template>
<RouterLink :to="{ name: 'course', params: { courseId: course.id } }">
<Card class="border-round course-card max-w-30rem">
<Card id="course" class="border-round course-card max-w-30rem">
<template #header>
<div class="flex align-items-center justify-content-center text-white relative p-3 text-center h-12rem">
<img
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/courses/CourseDetailList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const { t } = useI18n();
<span class="pi pi-exclamation-circle text-6xl text-primary" />
<slot name="empty">
<p>{{ t('components.list.noCourses.student') }}</p>
<RouterLink :to="{ name: 'courses' }">
<RouterLink id="courses" :to="{ name: 'courses' }">
<Button :label="t('components.button.searchCourse')" icon="pi pi-search" />
</RouterLink>
</slot>
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/components/courses/CourseForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,14 @@ watchEffect(() => {
</div>

<!-- Submit button -->
<Button :label="t('views.courses.save')" type="submit" icon="pi pi-check" iconPos="right" rounded />
<Button
id="courseSave"
:label="t('views.courses.save')"
type="submit"
icon="pi pi-check"
iconPos="right"
rounded
/>
</form>
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ async function leaveCourse(): Promise<void> {

<template>
<Button
id="courseEnroll"
class="text-sm p-0"
:label="t('views.courses.enroll')"
icon-pos="right"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/projects/ProjectList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const incomingProjects = computed<Project[] | null>(() => {
<div class="mt-3">
<slot name="empty">
<p>{{ t('components.list.noProjects.student') }}</p>
<RouterLink :to="{ name: 'courses' }">
<RouterLink id="courses" :to="{ name: 'courses' }">
<Button :label="t('components.button.searchCourse')" icon="pi pi-search" />
</RouterLink>
</slot>
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/test/e2e/course.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
describe('course', () => {
context('teacher', () => {
it('creates course', () => {
// login as a professor
cy.request('POST', '/api/auth/test-user/professor/');
// visit the dashboard
cy.visit('/');
// click button to create new coursre
cy.get('#courseCreate').click();
// fill in the required values for a new course
cy.get('#courseName').type('Course');
cy.get('#courseExcerpt').type('Description');
cy.get('#courseFaculty').click();
cy.get('#courseFaculty_1').click();
// click the save button
cy.get('#courseSave').click();
// should redirect to dashboard
cy.url().should('equal', Cypress.config('baseUrl') + '/');
});
});
context('student', () => {
it('able to enroll into course', () => {
// login as a student
cy.request('POST', '/api/auth/test-user/student/');
// visit the dashboard
cy.visit('/');
// navigate to course selector
cy.get('#courses').click();
// enroll into course
cy.get('#courseEnroll').click();
});
});
});
12 changes: 2 additions & 10 deletions frontend/src/test/e2e/login.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,17 @@ describe('login', () => {
});
it('does not redirect to login page when logged in', () => {
// log in as a test student
cy.visit('/api/auth/test-user/student/');
cy.request('POST', '/api/auth/test-user/student/');
// visit dashboard
cy.visit('/');
// should not be redirected to login page
cy.url().should('not.match', /^.*\/auth\/login$/);
// logout for cleaning up
cy.get('#logout').click();
});
it('redirects to login page after logging out', () => {
// intercept the retrieval of student info
cy.intercept('/api/students/*/').as('student');
// log in as a test student
cy.visit('/api/auth/test-user/student/');
cy.request('POST', '/api/auth/test-user/student/');
// visit dashboard
cy.visit('/');
// wait for requests for student info to end
cy.wait('@student');
cy.wait('@student');
cy.wait('@student');
// logout
cy.get('#logout').click();
// should be on login page now
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/views/dashboard/roles/TeacherDashboardView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ watchImmediate(
<Title class="m-0">{{ t('views.dashboard.projects') }}</Title>

<!-- Create project button -->
<ProjectCreateButton :courses="filteredCourses" :label="t('components.button.createProject')" />
<ProjectCreateButton
id="projectCreate"
:courses="filteredCourses"
:label="t('components.button.createProject')"
/>
</div>
<!-- Project list body -->
<ProjectList :projects="projects">
Expand All @@ -79,7 +83,11 @@ watchImmediate(
{{ t('components.list.noCourses.teacher') }}
</p>

<ProjectCreateButton :courses="filteredCourses" :label="t('components.button.createProject')" />
<ProjectCreateButton
id="projectCreate"
:courses="filteredCourses"
:label="t('components.button.createProject')"
/>
</template>
</template>
</ProjectList>
Expand All @@ -95,7 +103,7 @@ watchImmediate(
<YearSelector :years="allYears" v-model="selectedYear" />

<!-- Create course button -->
<RouterLink :to="{ name: 'course-create' }">
<RouterLink id="courseCreate" :to="{ name: 'course-create' }">
<Button
:icon="PrimeIcons.PLUS"
icon-pos="right"
Expand All @@ -109,7 +117,7 @@ watchImmediate(
<CourseList :courses="filteredCourses">
<template #empty>
<p>{{ t('components.list.noCourses.teacher') }}</p>
<RouterLink :to="{ name: 'course-create' }">
<RouterLink id="courseCreate" :to="{ name: 'course-create' }">
<Button :label="t('components.button.createCourse')" icon="pi pi-plus" />
</RouterLink>
</template>
Expand Down
Loading

0 comments on commit 4270777

Please sign in to comment.