Skip to content

Commit

Permalink
added test cases for guard
Browse files Browse the repository at this point in the history
  • Loading branch information
cremertim committed Nov 27, 2024
1 parent 85f35cb commit a938c90
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { CourseManagementService } from 'app/course/manage/course-management.ser
import { Course } from 'app/entities/course.model';
import dayjs from 'dayjs/esm';
import { ArtemisServerDateService } from 'app/shared/server-date.service';
import { CourseOverviewRoutePath } from 'app/overview/courses-routing.module';

@Injectable({
providedIn: 'root',
})
export class CourseOverviewGuardService implements CanActivate {
export class CourseOverviewGuard implements CanActivate {
private courseStorageService = inject(CourseStorageService);
private courseManagementService = inject(CourseManagementService);
private router = inject(Router);
Expand Down Expand Up @@ -49,22 +50,28 @@ export class CourseOverviewGuardService implements CanActivate {
handleReturn = (course?: Course, type?: string): Observable<boolean> => {
let hasAccess: boolean;
switch (type) {
case 'exams':
case CourseOverviewRoutePath.EXERCISES:
hasAccess = true; //default route, should always be accessible
break;
case CourseOverviewRoutePath.LECTURES:
hasAccess = !!course?.lectures;
break;
case CourseOverviewRoutePath.EXAMS:
hasAccess = this.hasVisibleExams(course);
break;
case 'competencies':
case CourseOverviewRoutePath.COMPETENCIES:
hasAccess = !!(course?.numberOfCompetencies || course?.numberOfPrerequisites);
break;
case 'tutorial-groups':
case CourseOverviewRoutePath.TUTORIAL_GROUPS:
hasAccess = !!course?.numberOfTutorialGroups;
break;
case 'dashboard':
case CourseOverviewRoutePath.DASHBOARD:
hasAccess = course?.studentCourseAnalyticsDashboardEnabled ?? false;
break;
case 'faq':
case CourseOverviewRoutePath.FAQ:
hasAccess = course?.faqEnabled ?? false;
break;
case 'learning-path':
case CourseOverviewRoutePath.LEARNING_PATH:
hasAccess = course?.learningPathsEnabled ?? false;
break;
default:
Expand Down
55 changes: 35 additions & 20 deletions src/main/webapp/app/overview/courses-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,22 @@ import { CourseTutorialGroupDetailComponent } from './tutorial-group-details/cou
import { ExamParticipationComponent } from 'app/exam/participate/exam-participation.component';
import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard';
import { CourseArchiveComponent } from './course-archive/course-archive.component';
import { CourseOverviewGuardService } from 'app/overview/course-overview-guard.service';
import { CourseOverviewGuard } from 'app/overview/course-overview-guard';

export enum CourseOverviewRoutePath {
DASHBOARD = 'dashboard',
EXERCISES = 'exercises',
EXAMS = 'exams',
COMPETENCIES = 'competencies',
TUTORIAL_GROUPS = 'tutorial-groups',
FAQ = 'faq',
LEARNING_PATH = 'learning-path',
LECTURES = 'lectures',
ENROLL = 'enroll',
ARCHIVE = 'archive',
STATISTICS = 'statistics',
COMMUNICATION = 'communication',
}

const routes: Routes = [
{
Expand All @@ -25,11 +40,11 @@ const routes: Routes = [
canActivate: [UserRouteAccessService],
},
{
path: 'enroll',
path: CourseOverviewRoutePath.ENROLL,
loadChildren: () => import('./course-registration/course-registration.module').then((m) => m.CourseRegistrationModule),
},
{
path: 'archive',
path: CourseOverviewRoutePath.ARCHIVE,
component: CourseArchiveComponent,
data: {
authorities: [Authority.USER],
Expand All @@ -54,7 +69,7 @@ const routes: Routes = [
canActivate: [UserRouteAccessService],
children: [
{
path: 'exercises',
path: CourseOverviewRoutePath.EXERCISES,
component: CourseExercisesComponent,
data: {
authorities: [Authority.USER],
Expand Down Expand Up @@ -128,7 +143,7 @@ const routes: Routes = [
},

{
path: 'lectures',
path: CourseOverviewRoutePath.LECTURES,
component: CourseLecturesComponent,
data: {
authorities: [Authority.USER],
Expand All @@ -152,7 +167,7 @@ const routes: Routes = [
],
},
{
path: 'statistics',
path: CourseOverviewRoutePath.STATISTICS,
loadChildren: () => import('./course-statistics/course-statistics.module').then((m) => m.CourseStatisticsModule),
data: {
authorities: [Authority.USER],
Expand All @@ -161,13 +176,13 @@ const routes: Routes = [
},
},
{
path: 'competencies',
path: CourseOverviewRoutePath.COMPETENCIES,
data: {
authorities: [Authority.USER],
pageTitle: 'overview.competencies',
showRefreshButton: true,
},
canActivate: [CourseOverviewGuardService],
canActivate: [CourseOverviewGuard],
children: [
{
path: '',
Expand All @@ -180,27 +195,27 @@ const routes: Routes = [
],
},
{
path: 'dashboard',
path: CourseOverviewRoutePath.DASHBOARD,
loadChildren: () => import('./course-dashboard/course-dashboard.module').then((m) => m.CourseDashboardModule),
data: {
authorities: [Authority.USER],
pageTitle: 'overview.dashboard',
},
canActivate: [UserRouteAccessService, CourseOverviewGuardService],
canActivate: [UserRouteAccessService, CourseOverviewGuard],
},
{
path: 'learning-path',
path: CourseOverviewRoutePath.LEARNING_PATH,
loadComponent: () =>
import('app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component').then((c) => c.LearningPathStudentPageComponent),
data: {
authorities: [Authority.USER],
pageTitle: 'overview.learningPath',
showRefreshButton: true,
},
canActivate: [CourseOverviewGuardService],
canActivate: [CourseOverviewGuard],
},
{
path: 'communication',
path: CourseOverviewRoutePath.COMMUNICATION,
loadChildren: () => import('./course-conversations/course-conversations.module').then((m) => m.CourseConversationsModule),
data: {
authorities: [Authority.USER],
Expand All @@ -210,15 +225,15 @@ const routes: Routes = [
},
},
{
path: 'tutorial-groups',
path: CourseOverviewRoutePath.TUTORIAL_GROUPS,
component: CourseTutorialGroupsComponent,
data: {
authorities: [Authority.USER],
pageTitle: 'overview.tutorialGroups',
hasSidebar: true,
showRefreshButton: true,
},
canActivate: [UserRouteAccessService, CourseOverviewGuardService],
canActivate: [UserRouteAccessService, CourseOverviewGuard],
children: [
{
path: ':tutorialGroupId',
Expand All @@ -235,15 +250,15 @@ const routes: Routes = [
],
},
{
path: 'exams',
path: CourseOverviewRoutePath.EXAMS,
component: CourseExamsComponent,
data: {
authorities: [Authority.USER],
pageTitle: 'overview.exams',
hasSidebar: true,
showRefreshButton: true,
},
canActivate: [UserRouteAccessService, CourseOverviewGuardService],
canActivate: [UserRouteAccessService, CourseOverviewGuard],
children: [
{
path: ':examId',
Expand All @@ -269,19 +284,19 @@ const routes: Routes = [
},
},
{
path: 'faq',
path: CourseOverviewRoutePath.FAQ,
loadComponent: () => import('../overview/course-faq/course-faq.component').then((m) => m.CourseFaqComponent),
data: {
authorities: [Authority.USER],
pageTitle: 'overview.faq',
hasSidebar: false,
showRefreshButton: true,
},
canActivate: [CourseOverviewGuardService],
canActivate: [CourseOverviewGuard],
},
{
path: '',
redirectTo: 'dashboard', // dashboard will redirect to exercises if not enabled
redirectTo: CourseOverviewRoutePath.DASHBOARD, // dashboard will redirect to exercises if not enabled
pathMatch: 'full',
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { TestBed } from '@angular/core/testing';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { ArtemisTestModule } from '../../test.module';
import { of } from 'rxjs';
import { CourseStorageService } from 'app/course/manage/course-storage.service';
import { CourseManagementService } from 'app/course/manage/course-management.service';
import dayjs from 'dayjs/esm';
import { Course } from 'app/entities/course.model';
import { HttpResponse } from '@angular/common/http';
import { CourseOverviewGuard } from 'app/overview/course-overview-guard';
import { Exam } from 'app/entities/exam/exam.model';
import { Lecture } from 'app/entities/lecture.model';
import { CourseOverviewRoutePath } from 'app/overview/courses-routing.module';

describe('CourseOverviewGuard', () => {
let guard: CourseOverviewGuard;
let courseStorageService: CourseStorageService;
let courseManagementService: CourseManagementService;
let router: Router;

const visibleRealExam = {
id: 1,
visibleDate: dayjs().subtract(1, 'days'),
startDate: dayjs().subtract(30, 'minutes'),
testExam: false,
} as Exam;

const lecture = new Lecture();

const mockCourse: Course = { id: 1, lectures: [lecture], exams: [visibleRealExam], faqEnabled: true } as Course;

const responseFakeCourse = { body: mockCourse } as HttpResponse<Course>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule],
});
guard = TestBed.inject(CourseOverviewGuard);
courseStorageService = TestBed.inject(CourseStorageService);
courseManagementService = TestBed.inject(CourseManagementService);
router = TestBed.inject(Router);
});

describe('canActivate', () => {
it('should return false if courseId is not present', () => {
const route = { parent: { paramMap: { get: () => undefined } }, routeConfig: { path: CourseOverviewRoutePath.EXERCISES } } as unknown as ActivatedRouteSnapshot;
let resultValue = true;
guard.canActivate(route).subscribe((result) => {
resultValue = result;
});
expect(resultValue).toBeFalse();
});

it('should return true if course is in cache', () => {
const route = { parent: { paramMap: { get: () => '1' } }, routeConfig: { path: CourseOverviewRoutePath.EXERCISES } } as unknown as ActivatedRouteSnapshot;
let resultValue = false;
jest.spyOn(courseStorageService, 'getCourse').mockReturnValue(mockCourse);
guard.canActivate(route).subscribe((result) => {
resultValue = result;
});
expect(resultValue).toBeTrue();
});

it('should return true if course is fetched from server', () => {
const route = { parent: { paramMap: { get: () => '1' } }, routeConfig: { path: CourseOverviewRoutePath.EXERCISES } } as unknown as ActivatedRouteSnapshot;
let resultValue = false;
jest.spyOn(courseStorageService, 'getCourse').mockReturnValue(undefined);
jest.spyOn(courseManagementService, 'find').mockReturnValue(of(responseFakeCourse));
guard.canActivate(route).subscribe((result) => {
resultValue = result;
});
expect(resultValue).toBeTrue();
});
});

describe('handleReturn', () => {
it('should return true if type is lectures and course has lectures', () => {
let resultValue = true;
const result = guard.handleReturn(mockCourse, 'lectures');
result.subscribe((value) => {
resultValue = value;
});

expect(resultValue).toBeTrue();
});

it('should return true if type is exams and course has visible exams', () => {
const result = guard.handleReturn(mockCourse, 'exams');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return true if type is competencies and course has competencies', () => {
mockCourse.numberOfCompetencies = 1;
const result = guard.handleReturn(mockCourse, 'competencies');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return true if type is competencies and course has prerequisits', () => {
mockCourse.numberOfPrerequisites = 1;
const result = guard.handleReturn(mockCourse, 'competencies');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return true if type is tutorial-groups and course has tutorial groups', () => {
mockCourse.numberOfTutorialGroups = 1;
const result = guard.handleReturn(mockCourse, 'tutorial-groups');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return true if type is dashboard and course has studentCourseAnalyticsDashboardEnabled', () => {
mockCourse.studentCourseAnalyticsDashboardEnabled = true;
const result = guard.handleReturn(mockCourse, 'dashboard');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return true if type is faq and course has faqEnabled', () => {
const result = guard.handleReturn(mockCourse, 'faq');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return true if type is learning-path and course has learningPathsEnabled', () => {
mockCourse.learningPathsEnabled = true;
const result = guard.handleReturn(mockCourse, 'learning-path');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeTrue();
});

it('should return false if type is unknown', () => {
const result = guard.handleReturn(mockCourse, 'unknown');
let resultValue = true;
result.subscribe((value) => {
resultValue = value;
});
expect(resultValue).toBeFalse();
});

it('should navigate to exercises if type is unknown', () => {
const navigateSpy = jest.spyOn(router, 'navigate');
guard.handleReturn(mockCourse, 'unknown');
expect(navigateSpy).toHaveBeenCalledWith(['/courses/1/exercises']);
});
});
});

0 comments on commit a938c90

Please sign in to comment.