From 4345f3d64e760bf7a5cb53ae8436046c71bf7ba7 Mon Sep 17 00:00:00 2001 From: Teddy Roncin Date: Thu, 1 May 2025 17:56:33 +0200 Subject: [PATCH 1/3] Changed almost all @RequireUserType to @RequireApiPermission to match with the new permission system, for UE, annal and comment routes --- prisma/schema.prisma | 16 +++--- prisma/seed/modules/ueSubscription.seed.ts | 4 +- src/auth/guard/role.guard.ts | 2 +- src/exceptions.ts | 11 +++- src/ue/annals/annals.controller.ts | 56 +++++++++++-------- src/ue/annals/annals.service.ts | 2 +- src/ue/comments/comments.controller.ts | 23 ++++---- src/ue/ue.controller.ts | 10 ++-- src/ue/ue.service.ts | 20 +++++++ test/e2e/app.e2e-spec.ts | 8 +-- test/e2e/timetable/index.ts | 3 +- test/e2e/ue/annals/delete-annal.e2e-spec.ts | 17 +++--- test/e2e/ue/annals/get-annal-file.e2e-spec.ts | 27 +++++---- .../ue/annals/get-annal-metadata.e2e-spec.ts | 21 +++++-- test/e2e/ue/annals/get-annals.e2e-spec.ts | 33 +++++------ test/e2e/ue/annals/patch-annal.e2e-spec.ts | 23 ++++---- test/e2e/ue/annals/upload-annal.e2e-spec.ts | 23 ++++---- .../ue/comments/delete-comment.e2e-spec.ts | 14 ++++- test/e2e/ue/comments/delete-reply.e2e-spec.ts | 19 ++++++- .../e2e/ue/comments/delete-upvote.e2e-spec.ts | 31 +++++++--- .../comments/get-comment-from-id.e2e-spec.ts | 21 +++++-- test/e2e/ue/comments/get-comment.e2e-spec.ts | 17 +++--- test/e2e/ue/comments/post-comment.e2e-spec.ts | 47 ++++++++++------ test/e2e/ue/comments/post-reply.e2e-spec.ts | 15 ++++- test/e2e/ue/comments/post-upvote.e2e-spec.ts | 27 ++++++--- .../ue/comments/update-comment.e2e-spec.ts | 23 ++++++-- test/e2e/ue/comments/update-reply.e2e-spec.ts | 19 ++++++- test/e2e/ue/delete-rate.e2e-spec.ts | 19 +++++-- test/e2e/ue/get-rate-criteria.e2e-spec.ts | 14 ++++- test/e2e/ue/get-ue-rate.e2e-spec.ts | 24 +++++--- test/e2e/ue/index.ts | 3 +- test/e2e/ue/put-rate.e2e-spec.ts | 32 +++++++---- 32 files changed, 409 insertions(+), 215 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index abceb2c..e1981ac 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -898,11 +898,13 @@ enum AddressPrivacy { } enum Permission { - API_SEE_OPINIONS_UE - API_UPLOAD_ANNAL - API_MODERATE_ANNAL - API_MODERATE_COMMENTS - - USER_SEE_DETAILS - USER_UPDATE_DETAILS + API_SEE_OPINIONS_UE // See the rates of an UE + API_GIVE_OPINIONS_UE // Rate an UE you have done or are doing + API_SEE_ANNALS // See and download annals + API_UPLOAD_ANNALS // Upload an annal + API_MODERATE_ANNALS // Moderate annals + API_MODERATE_COMMENTS // Moderate comments + + USER_SEE_DETAILS // See personal details about someone, even the ones the user decided to hide + USER_UPDATE_DETAILS // Update personal details about someone } diff --git a/prisma/seed/modules/ueSubscription.seed.ts b/prisma/seed/modules/ueSubscription.seed.ts index 43b622d..7a45ea3 100644 --- a/prisma/seed/modules/ueSubscription.seed.ts +++ b/prisma/seed/modules/ueSubscription.seed.ts @@ -11,7 +11,7 @@ export default function ueSubscriptionSeed( ): Promise { console.log('Seeding UE subscriptions...'); const subscriptions: Promise[] = []; - /*for (const user of users) { + for (const user of users) { const subscribedToUes = faker.helpers.arrayElements(ues, faker.datatype.number({ min: 1, max: 10 })); for (const ue of subscribedToUes) { subscriptions.push( @@ -24,7 +24,7 @@ export default function ueSubscriptionSeed( }), ); } - }*/ + } console.log('WARNING: no ue subscriptions seeded !!'); return Promise.all(subscriptions); } diff --git a/src/auth/guard/role.guard.ts b/src/auth/guard/role.guard.ts index 584f5e1..5bd90e2 100644 --- a/src/auth/guard/role.guard.ts +++ b/src/auth/guard/role.guard.ts @@ -18,6 +18,6 @@ export class RoleGuard implements CanActivate { // If the user has one of the needed permissions, serve the request for (const requiredType of requiredTypes) if (user.userType === requiredType) return true; // The user has none of the required permissions, throw an error - throw new AppException(ERROR_CODE.FORBIDDEN_INVALID_ROLE, requiredTypes[0]); + throw new AppException(ERROR_CODE.FORBIDDEN_INVALID_ROLE, requiredTypes.join(',')); } } diff --git a/src/exceptions.ts b/src/exceptions.ts index 68ebb57..edd8058 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -74,6 +74,7 @@ export const enum ERROR_CODE { NO_SUCH_ASSO = 4410, NO_SUCH_UEOF = 4411, NO_SUCH_APPLICATION = 4412, + NO_SUCH_UE_AT_SEMESTER = 4413, ANNAL_ALREADY_UPLOADED = 4901, RESOURCE_UNAVAILABLE = 4902, RESOURCE_INVALID_TYPE = 4903, @@ -205,11 +206,11 @@ export const ErrorData = Object.freeze({ }, [ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS]: { message: 'Missing permission %', - httpCode: HttpStatus.FORBIDDEN, + httpCode: HttpStatus.UNAUTHORIZED, }, [ERROR_CODE.FORBIDDEN_NOT_ENOUGH_USER_PERMISSIONS]: { message: 'Missing permission % on user %', - httpCode: HttpStatus.FORBIDDEN, + httpCode: HttpStatus.UNAUTHORIZED, }, [ERROR_CODE.NO_TOKEN]: { message: 'No token provided', @@ -224,7 +225,7 @@ export const ErrorData = Object.freeze({ httpCode: HttpStatus.UNAUTHORIZED, }, [ERROR_CODE.FORBIDDEN_INVALID_ROLE]: { - message: 'Role % is required to access this resource', + message: 'One of the following roles is required to access this resource: %', httpCode: HttpStatus.UNAUTHORIZED, }, [ERROR_CODE.INVALID_CAS_TICKET]: { @@ -331,6 +332,10 @@ export const ErrorData = Object.freeze({ message: 'The application % does not exist', httpCode: HttpStatus.NOT_FOUND, }, + [ERROR_CODE.NO_SUCH_UE_AT_SEMESTER]: { + message: 'UE % does not exist for semester %', + httpCode: HttpStatus.NOT_FOUND, + }, [ERROR_CODE.ANNAL_ALREADY_UPLOADED]: { message: 'A file has alreay been uploaded for this annal', httpCode: HttpStatus.CONFLICT, diff --git a/src/ue/annals/annals.controller.ts b/src/ue/annals/annals.controller.ts index 9f3668f..dd1dd97 100644 --- a/src/ue/annals/annals.controller.ts +++ b/src/ue/annals/annals.controller.ts @@ -3,7 +3,7 @@ import { AnnalsService } from './annals.service'; import { UeService } from '../ue.service'; import { Response as ExpressResponse } from 'express'; import { UUIDParam } from '../../app.pipe'; -import { GetUser, RequireUserType } from '../../auth/decorator'; +import { GetUser, RequireApiPermission } from '../../auth/decorator'; import { AppException, ERROR_CODE } from '../../exceptions'; import { FileSize, MulterWithMime, UploadRoute, UserFile } from '../../upload.interceptor'; import { CommentStatus } from '../comments/interfaces/comment.interface'; @@ -26,7 +26,7 @@ export class AnnalsController { constructor(readonly annalsService: AnnalsService, readonly ueService: UeService) {} @Get() - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_SEE_ANNALS') @ApiOperation({ description: 'Get the list of annals of a UE.' }) @ApiOkResponse({ type: UeAnnalResDto, isArray: true }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_UE, 'Thrown when there is no UE with code `ueCode`.') @@ -36,14 +36,14 @@ export class AnnalsController { @GetPermissions() permissions: PermissionManager, ): Promise { if (!(await this.ueService.doesUeExist(ueCode))) throw new AppException(ERROR_CODE.NO_SUCH_UE, ueCode); - return this.annalsService.getUeAnnalsList(user, ueCode, permissions.can(Permission.API_MODERATE_ANNAL)); + return this.annalsService.getUeAnnalsList(user, ueCode, permissions.can(Permission.API_MODERATE_ANNALS)); } @Post() - @RequireUserType('STUDENT') + @RequireApiPermission('API_UPLOAD_ANNALS') @ApiOperation({ description: - 'Create an annal. User must have done the UE, or have the permission `annalUploader`. Metadata of the annal will be created, but the file will not actually exist. To upload the file, see `PUT /v1/ue/annals/:annalId`.', + 'Create an annal. User must have done the UE, or have the permission `API_MODERATE_ANNALS`. Metadata of the annal will be created, but the file will not actually exist. To upload the file, see `PUT /v1/ue/annals/:annalId`.', }) @ApiOkResponse({ type: UeAnnalResDto }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_UE, 'Thrown when there is no UE with code `ueCode`.') @@ -53,28 +53,30 @@ export class AnnalsController { ) @ApiAppErrorResponse( ERROR_CODE.NOT_DONE_UE_IN_SEMESTER, - 'User has not done the UE and is not an `annalUploader`, and thus cannot upload an annal for this UE.', + 'User has not done the UE and is not an `API_MODERATE_ANNALS`, and thus cannot upload an annal for this UE.', ) async createUeAnnal( @Body() { ueCode, semester, typeId, ueof }: CreateAnnalReqDto, @GetUser() user: User, @GetPermissions() permissions: PermissionManager, ): Promise { - if (ueof && permissions.can(Permission.API_UPLOAD_ANNAL)) - throw new AppException(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, Permission.API_UPLOAD_ANNAL); + if (ueof && !permissions.can(Permission.API_MODERATE_ANNALS)) + throw new AppException(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, Permission.API_MODERATE_ANNALS); if (!ueof && !(await this.ueService.doesUeExist(ueCode))) throw new AppException(ERROR_CODE.NO_SUCH_UE, ueCode); if (ueof && !(await this.ueService.doesUeofExist(ueof))) throw new AppException(ERROR_CODE.NO_SUCH_UEOF, ueof); if (!(await this.annalsService.doesAnnalTypeExist(typeId))) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL_TYPE); if ( !(await this.ueService.hasUserAttended(ueCode, user.id, semester)) && - !permissions.can(Permission.API_UPLOAD_ANNAL) + !permissions.can(Permission.API_MODERATE_ANNALS) ) throw new AppException(ERROR_CODE.NOT_DONE_UE_IN_SEMESTER, ueCode, semester); + if (!(await this.ueService.didUeHappenAtSemester(ueCode, semester))) + throw new AppException(ERROR_CODE.NO_SUCH_UE_AT_SEMESTER, ueCode, semester); return this.annalsService.createAnnalFile(user, { ueCode, semester, typeId, ueof }); } @Get('metadata') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_SEE_ANNALS') @ApiOperation({ description: 'Get generic information about annals for a particular UE. User must have already done this UE, or be an `annalUploader`.', @@ -92,13 +94,13 @@ export class AnnalsController { @GetPermissions() permissions: PermissionManager, ): Promise { if (!(await this.ueService.doesUeExist(ueCode))) throw new AppException(ERROR_CODE.NO_SUCH_UE, ueCode); - if (!(await this.ueService.hasUserAttended(ueCode, user.id)) && !permissions.can(Permission.API_UPLOAD_ANNAL)) + if (!(await this.ueService.hasUserAttended(ueCode, user.id)) && !permissions.can(Permission.API_UPLOAD_ANNALS)) throw new AppException(ERROR_CODE.NOT_ALREADY_DONE_UE); - return this.annalsService.getUeAnnalMetadata(user, ueCode, permissions.can(Permission.API_UPLOAD_ANNAL)); + return this.annalsService.getUeAnnalMetadata(user, ueCode, permissions.can(Permission.API_UPLOAD_ANNALS)); } @Put(':annalId') - @RequireUserType('STUDENT') + @RequireApiPermission('API_UPLOAD_ANNALS') @UploadRoute('file') @ApiOperation({ description: @@ -125,15 +127,15 @@ export class AnnalsController { if (!(await this.annalsService.isUeAnnalSender(user.id, annalId))) throw new AppException(ERROR_CODE.NOT_ANNAL_SENDER); if ( - (await this.annalsService.getUeAnnal(annalId, user.id, permissions.can(Permission.API_MODERATE_ANNAL))).status !== - CommentStatus.PROCESSING + (await this.annalsService.getUeAnnal(annalId, user.id, permissions.can(Permission.API_MODERATE_ANNALS))) + .status !== CommentStatus.PROCESSING ) throw new AppException(ERROR_CODE.ANNAL_ALREADY_UPLOADED); return this.annalsService.uploadAnnalFile(await file, annalId, rotate); } @Get(':annalId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_SEE_ANNALS') @ApiOperation({ description: 'Get the file linked to a specific annal.' }) @ApiOkResponse({ description: 'The file is sent back.' }) @ApiAppErrorResponse( @@ -146,12 +148,14 @@ export class AnnalsController { @Response() response: ExpressResponse, @GetPermissions() permissions: PermissionManager, ) { - if (!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNAL)))) + if ( + !(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNALS))) + ) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId); const annalFile = await this.annalsService.getUeAnnalFile( annalId, user.id, - permissions.can(Permission.API_MODERATE_ANNAL), + permissions.can(Permission.API_MODERATE_ANNALS), ); if (!annalFile) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId); response.setHeader('Content-Type', 'application/pdf'); @@ -163,7 +167,7 @@ export class AnnalsController { } @Patch(':annalId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_UPLOAD_ANNALS') @ApiOperation({ description: 'Modify the metadata of an annal. User must be the original sender of the annal, or be an `annalModerator`.', @@ -177,18 +181,20 @@ export class AnnalsController { @GetUser() user: User, @GetPermissions() permissions: PermissionManager, ): Promise { - if (!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNAL)))) + if ( + !(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNALS))) + ) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId); if ( !(await this.annalsService.isUeAnnalSender(user.id, annalId)) && - !permissions.can(Permission.API_MODERATE_ANNAL) + !permissions.can(Permission.API_MODERATE_ANNALS) ) throw new AppException(ERROR_CODE.NOT_ANNAL_SENDER); return this.annalsService.updateAnnalMetadata(annalId, body); } @Delete(':annalId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_UPLOAD_ANNALS') @ApiOperation({ description: 'Delete an annal. The file attached to the annal will not actually be deleted. User must be the original sender of the annal, or be an `annalModerator`.', @@ -201,11 +207,13 @@ export class AnnalsController { @GetUser() user: User, @GetPermissions() permissions: PermissionManager, ): Promise { - if (!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNAL)))) + if ( + !(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNALS))) + ) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId); if ( !(await this.annalsService.isUeAnnalSender(user.id, annalId)) && - !permissions.can(Permission.API_MODERATE_ANNAL) + !permissions.can(Permission.API_MODERATE_ANNALS) ) throw new AppException(ERROR_CODE.NOT_ANNAL_SENDER); return this.annalsService.deleteAnnal(annalId); diff --git a/src/ue/annals/annals.service.ts b/src/ue/annals/annals.service.ts index 2cdbe3c..a321ad6 100644 --- a/src/ue/annals/annals.service.ts +++ b/src/ue/annals/annals.service.ts @@ -86,7 +86,7 @@ export class AnnalsService { }, ueof: { connect: { - code: subscription.ueofCode ?? params.ueof, + code: subscription?.ueofCode ?? params.ueof, }, }, }, diff --git a/src/ue/comments/comments.controller.ts b/src/ue/comments/comments.controller.ts index 1a399fd..c77fc41 100644 --- a/src/ue/comments/comments.controller.ts +++ b/src/ue/comments/comments.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Patch, Post, Query } from '@nestjs/common'; import { UUIDParam } from '../../app.pipe'; -import { GetUser, RequireApiPermission, RequireUserType } from '../../auth/decorator'; +import { GetUser, RequireApiPermission } from '../../auth/decorator'; import { AppException, ERROR_CODE } from '../../exceptions'; import UeCommentPostReqDto from './dto/req/ue-comment-post-req.dto'; import CommentReplyReqDto from './dto/req/ue-comment-reply-req.dto'; @@ -24,7 +24,6 @@ export class CommentsController { constructor(readonly commentsService: CommentsService, readonly ueService: UeService) {} @Get() - @RequireUserType('STUDENT', 'FORMER_STUDENT') @RequireApiPermission(Permission.API_SEE_OPINIONS_UE) @ApiOperation({ description: 'Get the comments of a UE. This route is paginated.' }) @ApiOkResponse({ type: paginatedResponseDto(UeCommentResDto) }) @@ -32,7 +31,7 @@ export class CommentsController { ERROR_CODE.NO_SUCH_UE, 'This error is sent back when there is no UE associated with the code provided.', ) - async getUEComments( + async getUeComments( @GetUser() user: User, @Query() dto: GetUeCommentsReqDto, @GetPermissions() permissions: PermissionManager, @@ -42,7 +41,7 @@ export class CommentsController { } @Post() - @RequireUserType('STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Send a comment for a UE.' }) @ApiOkResponse({ type: UeCommentResDto }) @ApiAppErrorResponse( @@ -69,7 +68,7 @@ export class CommentsController { // TODO : en vrai la route GET /ue/comments renvoie les mêmes infos nan ? :sweat_smile: @Get(':commentId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_SEE_OPINIONS_UE') @ApiOperation({ description: 'Fetch a specific comment.' }) @ApiOkResponse({ type: UeCommentResDto }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_COMMENT, 'No comment is associated with the given commentId') @@ -88,7 +87,7 @@ export class CommentsController { } @Patch(':commentId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Edit a comment.' }) @ApiOkResponse({ type: UeCommentResDto }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_COMMENT, 'No comment has the given commentId.') @@ -111,7 +110,7 @@ export class CommentsController { } @Delete(':commentId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Delete a comment. The user must be the author or have the `commentModerator` permission.', }) @@ -135,7 +134,7 @@ export class CommentsController { } @Post(':commentId/upvote') - @RequireUserType('STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @HttpCode(HttpStatus.OK) @ApiOperation({ description: 'Give an upvote for a comment. User cannot be the author. Each user can only upvote a comment once.', @@ -164,7 +163,7 @@ export class CommentsController { } @Delete(':commentId/upvote') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @HttpCode(HttpStatus.OK) @ApiOperation({ description: 'Remove an upvote for a comment. User' }) @ApiOkResponse({ type: UeCommentUpvoteResDto$False }) @@ -191,7 +190,7 @@ export class CommentsController { } @Post(':commentId/reply') - @RequireUserType('STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Reply to a comment.' }) @ApiOkResponse({ type: UeCommentReplyResDto }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_COMMENT, 'There is no comment with the provided commentId.') @@ -208,7 +207,7 @@ export class CommentsController { } @Patch('reply/:replyId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Edit a reply to a comment. The user must be the author of the reply or have the `commentModerator` permission.', @@ -232,7 +231,7 @@ export class CommentsController { } @Delete('reply/:replyId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Delete a reply to a comment. The user must be the author of the reply or have the `commentModerator` permission.', diff --git a/src/ue/ue.controller.ts b/src/ue/ue.controller.ts index 79f55e5..35a3363 100644 --- a/src/ue/ue.controller.ts +++ b/src/ue/ue.controller.ts @@ -3,7 +3,7 @@ import { HttpStatusCode } from 'axios'; import type { Response } from 'express'; import { UeSearchReqDto } from './dto/req/ue-search-req.dto'; import { UeService } from './ue.service'; -import { GetUser, IsPublic, RequireUserType } from '../auth/decorator'; +import { GetUser, IsPublic, RequireApiPermission, RequireUserType } from '../auth/decorator'; import { User } from '../users/interfaces/user.interface'; import { UUIDParam } from '../app.pipe'; import { AppException, ERROR_CODE } from '../exceptions'; @@ -67,7 +67,7 @@ export class UeController { } @Get('/rate/criteria') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_SEE_OPINIONS_UE') @ApiOperation({ description: 'Get the different criteria on which users can rate UEs.' }) @ApiOkResponse({ type: UeRateCriterionResDto, isArray: true }) async getRateCriteria(): Promise { @@ -75,7 +75,7 @@ export class UeController { } @Get('/:ueCode/rate') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Get the rates given by the current user.' }) @ApiOkResponse({ description: 'Keys are criterionId and values are the marks.', @@ -88,7 +88,7 @@ export class UeController { } @Put('/ueof/:ueofCode/rate') - @RequireUserType('STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Rate the UE by some criterion.' }) @ApiOkResponse({ type: UeRateReqDto }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_UE, 'There is no UE with the provided code.') @@ -107,7 +107,7 @@ export class UeController { } @Delete('/ueof/:ueofCode/rate/:criterionId') - @RequireUserType('STUDENT', 'FORMER_STUDENT') + @RequireApiPermission('API_GIVE_OPINIONS_UE') @ApiOperation({ description: 'Remove the rate on the UE about some criterion.' }) @ApiOkResponse({ type: UeRateReqDto }) @ApiAppErrorResponse(ERROR_CODE.NO_SUCH_UE, 'There is no UE with the provided code.') diff --git a/src/ue/ue.service.ts b/src/ue/ue.service.ts index c1a0965..29e6ff4 100644 --- a/src/ue/ue.service.ts +++ b/src/ue/ue.service.ts @@ -360,4 +360,24 @@ export class UeService { }, }); } + + /** + * Returns if the UE was taught at some specific semester. + * @param ueCode Code of the UE to check. + * @param semesterCode Code of the semester to check. + */ + async didUeHappenAtSemester(ueCode: string, semesterCode: string): Promise { + return ( + (await this.prisma.ueof.count({ + where: { + ueId: ueCode, + openSemester: { + some: { + code: semesterCode, + }, + }, + }, + })) > 0 + ); + } } diff --git a/test/e2e/app.e2e-spec.ts b/test/e2e/app.e2e-spec.ts index b9f29d4..4a16e08 100644 --- a/test/e2e/app.e2e-spec.ts +++ b/test/e2e/app.e2e-spec.ts @@ -8,8 +8,8 @@ import * as pactum from 'pactum'; import AuthE2ESpec from './auth'; import ProfileE2ESpec from './profile'; import UsersE2ESpec from './users'; -// import TimetableE2ESpec from './timetable'; -import UEE2ESpec from './ue'; +import TimetableE2ESpec from './timetable'; +import UeE2ESpec from './ue'; import { AppValidationPipe } from '../../src/app.pipe'; import * as cas from '../external_services/cas'; import * as timetableProvider from '../external_services/timetable'; @@ -51,7 +51,7 @@ describe('EtuUTT API e2e testing', () => { AuthE2ESpec(() => app); ProfileE2ESpec(() => app); UsersE2ESpec(() => app); - // TimetableE2ESpec(() => app); - UEE2ESpec(() => app); + TimetableE2ESpec(() => app); // Deactivated, see function + UeE2ESpec(() => app); AssoE2ESpec(() => app); }); diff --git a/test/e2e/timetable/index.ts b/test/e2e/timetable/index.ts index 52fc5f5..8eb45ea 100644 --- a/test/e2e/timetable/index.ts +++ b/test/e2e/timetable/index.ts @@ -8,8 +8,9 @@ import UpdateEntryE2ESpec from './update-entry.e2e-spec'; import DeleteEntryE2ESpec from './delete-occurrences.e2e-spec'; import ImportTimetableE2ESpec from './import-timetable.e2e-spec'; +// These tests are deactivated by describe.skip export default function TimetableE2ESpec(app: E2EAppProvider) { - describe('Timetable', () => { + describe.skip('Timetable', () => { GetDailyTimetableE2ESpec(app); GetTimetableE2ESpec(app); GetGroupsE2ESpec(app); diff --git a/test/e2e/ue/annals/delete-annal.e2e-spec.ts b/test/e2e/ue/annals/delete-annal.e2e-spec.ts index 408d8f9..f30431a 100644 --- a/test/e2e/ue/annals/delete-annal.e2e-spec.ts +++ b/test/e2e/ue/annals/delete-annal.e2e-spec.ts @@ -17,9 +17,9 @@ import { pick } from '../../../../src/utils'; import { PrismaService } from '../../../../src/prisma/prisma.service'; const DeleteAnnal = e2eSuite('DELETE /ue/annals/{annalId}', (app) => { - const senderUser = createUser(app); - const nonUeUser = createUser(app, { login: 'user2', studentId: 2 }); - const nonStudentUser = createUser(app, { login: 'nonStudent', studentId: 4, userType: 'TEACHER' }); + const senderUser = createUser(app, { permissions: ['API_UPLOAD_ANNALS'] }); + const nonUeUser = createUser(app, { login: 'user2', studentId: 2, permissions: ['API_UPLOAD_ANNALS'] }); + const userNoPermission = createUser(app); const annalType = createAnnalType(app); const semester = createSemester(app); const branch = createBranch(app); @@ -41,13 +41,12 @@ const DeleteAnnal = e2eSuite('DELETE /ue/annals/{annalId}', (app) => { .expectAppError(ERROR_CODE.NO_SUCH_ANNAL, Dummies.UUID); }); - it('should return a 403 because user is not a student', () => { - return pactum + it('should fail as the user does not have the required permissions', () => + pactum .spec() - .withBearerToken(nonStudentUser.token) - .delete(`/ue/annals/${annal_validated.id}`) - .expectAppError(ERROR_CODE.FORBIDDEN_INVALID_ROLE, 'STUDENT'); - }); + .withBearerToken(userNoPermission.token) + .delete(`/ue/annals/${Dummies.UUID}`) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_UPLOAD_ANNALS')); it('should return a 403 because user is not the author', () => { return pactum diff --git a/test/e2e/ue/annals/get-annal-file.e2e-spec.ts b/test/e2e/ue/annals/get-annal-file.e2e-spec.ts index ad5bc4c..663fc55 100644 --- a/test/e2e/ue/annals/get-annal-file.e2e-spec.ts +++ b/test/e2e/ue/annals/get-annal-file.e2e-spec.ts @@ -15,10 +15,10 @@ import { ERROR_CODE } from '../../../../src/exceptions'; import { CommentStatus } from '../../../../src/ue/comments/interfaces/comment.interface'; const GetAnnalFile = e2eSuite('GET /ue/annals/{annalId}', (app) => { - const senderUser = createUser(app); - const nonUeUser = createUser(app, { login: 'user2', studentId: 2 }); + const senderUser = createUser(app, { permissions: ['API_SEE_ANNALS'] }); + const nonUeUser = createUser(app, { login: 'user2', studentId: 2, permissions: ['API_SEE_ANNALS'] }); // const moderator = createUser(app, { login: 'user3', studentId: 3, permissions: ['annalModerator'] }); - const nonStudentUser = createUser(app, { login: 'nonStudent', studentId: 4, userType: 'TEACHER' }); + const userNoPermission = createUser(app); const annalType = createAnnalType(app); const semester = createSemester(app); const branch = createBranch(app); @@ -47,22 +47,27 @@ const GetAnnalFile = e2eSuite('GET /ue/annals/{annalId}', (app) => { return pactum.spec().get(`/ue/annals/${annal_validated.id}`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .get(`/ue/annals/${annal_validated.id}`) + .withQueryParams({ + ueCode: ue.code, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_SEE_ANNALS')); + it('should return a 404 because annal does not exist', () => { return pactum .spec() .withBearerToken(senderUser.token) .get(`/ue/annals/${Dummies.UUID}`) + .withQueryParams({ + ueCode: ue.code, + }) .expectAppError(ERROR_CODE.NO_SUCH_ANNAL, Dummies.UUID); }); - it('should return a 403 because user is not a student', () => { - return pactum - .spec() - .withBearerToken(nonStudentUser.token) - .get(`/ue/annals/${annal_validated.id}`) - .expectAppError(ERROR_CODE.FORBIDDEN_INVALID_ROLE, 'STUDENT'); - }); - it('should return a 404 because annal is not validated', () => { return pactum .spec() diff --git a/test/e2e/ue/annals/get-annal-metadata.e2e-spec.ts b/test/e2e/ue/annals/get-annal-metadata.e2e-spec.ts index 0d43ef0..838048f 100644 --- a/test/e2e/ue/annals/get-annal-metadata.e2e-spec.ts +++ b/test/e2e/ue/annals/get-annal-metadata.e2e-spec.ts @@ -14,9 +14,14 @@ import { ERROR_CODE } from '../../../../src/exceptions'; import { Permission } from '@prisma/client'; const GetAnnalMetadata = e2eSuite('GET /ue/annals/metadata', (app) => { - const ueUser = createUser(app); - const nonUeUser = createUser(app, { login: 'user2', studentId: 3 }); - const uploader = createUser(app, { login: 'user3', studentId: 4, permissions: [Permission.API_UPLOAD_ANNAL] }); + const ueUser = createUser(app, { permissions: [Permission.API_SEE_ANNALS] }); + const nonUeUser = createUser(app, { login: 'user2', studentId: 3, permissions: [Permission.API_SEE_ANNALS] }); + const uploader = createUser(app, { + login: 'user3', + studentId: 4, + permissions: [Permission.API_SEE_ANNALS, Permission.API_UPLOAD_ANNALS], + }); + const userNoPermission = createUser(app); const annalType = createAnnalType(app); const semester = createSemester(app); const branch = createBranch(app); @@ -26,14 +31,18 @@ const GetAnnalMetadata = e2eSuite('GET /ue/annals/metadata', (app) => { createUeSubscription(app, { user: ueUser, ueof, semester }); it('should return a 401 as user is not authenticated', () => { - return pactum + return pactum.spec().get(`/ue/annals/metadata`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); + }); + + it('should fail as the user does not have the required permissions', () => + pactum .spec() + .withBearerToken(userNoPermission.token) .get(`/ue/annals/metadata`) .withQueryParams({ ueCode: ue.code, }) - .expectAppError(ERROR_CODE.NOT_LOGGED_IN); - }); + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_SEE_ANNALS')); it('should return a 404 because UE does not exist', () => { return pactum diff --git a/test/e2e/ue/annals/get-annals.e2e-spec.ts b/test/e2e/ue/annals/get-annals.e2e-spec.ts index a1c5c21..35ccb24 100644 --- a/test/e2e/ue/annals/get-annals.e2e-spec.ts +++ b/test/e2e/ue/annals/get-annals.e2e-spec.ts @@ -18,10 +18,14 @@ import { pick } from '../../../../src/utils'; import { CommentStatus } from '../../../../src/ue/comments/interfaces/comment.interface'; const GetAnnal = e2eSuite('GET /ue/annals', (app) => { - const senderUser = createUser(app); - const nonUeUser = createUser(app, { login: 'user2', studentId: 2 }); - const moderator = createUser(app, { login: 'user3', studentId: 3, permissions: ['API_MODERATE_ANNAL'] }); - const nonStudentUser = createUser(app, { login: 'nonStudent', studentId: 4, userType: 'TEACHER' }); + const senderUser = createUser(app, { permissions: ['API_SEE_ANNALS'] }); + const nonUeUser = createUser(app, { login: 'user2', studentId: 2, permissions: ['API_SEE_ANNALS'] }); + const moderator = createUser(app, { + login: 'user3', + studentId: 3, + permissions: ['API_SEE_ANNALS', 'API_MODERATE_ANNALS'], + }); + const userNoPermission = createUser(app); const annalType = createAnnalType(app); const semester = createSemester(app); const branch = createBranch(app); @@ -47,14 +51,18 @@ const GetAnnal = e2eSuite('GET /ue/annals', (app) => { ); it('should return a 401 as user is not authenticated', () => { - return pactum + return pactum.spec().get(`/ue/annals`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); + }); + + it('should fail as the user does not have the required permissions', () => + pactum .spec() + .withBearerToken(userNoPermission.token) .get(`/ue/annals`) .withQueryParams({ ueCode: ue.code, }) - .expectAppError(ERROR_CODE.NOT_LOGGED_IN); - }); + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_SEE_ANNALS')); it('should return a 404 because UE does not exist', () => { return pactum @@ -67,17 +75,6 @@ const GetAnnal = e2eSuite('GET /ue/annals', (app) => { .expectAppError(ERROR_CODE.NO_SUCH_UE, ue.code.slice(0, ue.code.length - 1)); }); - it('should return a 403 because user is not a student', () => { - return pactum - .spec() - .withBearerToken(nonStudentUser.token) - .get(`/ue/annals`) - .withQueryParams({ - ueCode: ue.code, - }) - .expectAppError(ERROR_CODE.FORBIDDEN_INVALID_ROLE, 'STUDENT'); - }); - it('should return the ue annal list', async () => { await pactum .spec() diff --git a/test/e2e/ue/annals/patch-annal.e2e-spec.ts b/test/e2e/ue/annals/patch-annal.e2e-spec.ts index ba4a54e..cc290d0 100644 --- a/test/e2e/ue/annals/patch-annal.e2e-spec.ts +++ b/test/e2e/ue/annals/patch-annal.e2e-spec.ts @@ -16,9 +16,9 @@ import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; import { pick } from '../../../../src/utils'; const EditAnnal = e2eSuite('PATCH /ue/annals/{annalId}', (app) => { - const senderUser = createUser(app); - const nonUeUser = createUser(app, { login: 'user2', studentId: 2 }); - const nonStudentUser = createUser(app, { login: 'nonStudent', studentId: 4, userType: 'TEACHER' }); + const senderUser = createUser(app, { permissions: ['API_UPLOAD_ANNALS'] }); + const nonUeUser = createUser(app, { login: 'user2', studentId: 2, permissions: ['API_UPLOAD_ANNALS'] }); + const userNoPermission = createUser(app); const annalType = createAnnalType(app); const semester = createSemester(app); const branch = createBranch(app); @@ -49,22 +49,21 @@ const EditAnnal = e2eSuite('PATCH /ue/annals/{annalId}', (app) => { return pactum.spec().patch(`/ue/annals/${annal_validated.id}`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); - it('should return a 404 because annal does not exist', () => { - return pactum + it('should fail as the user does not have the required permissions', () => + pactum .spec() - .withBearerToken(senderUser.token) + .withBearerToken(userNoPermission.token) .patch(`/ue/annals/${Dummies.UUID}`) .withBody(generateBody()) - .expectAppError(ERROR_CODE.NO_SUCH_ANNAL, Dummies.UUID); - }); + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_UPLOAD_ANNALS')); - it('should return a 403 because user is not a student', () => { + it('should return a 404 because annal does not exist', () => { return pactum .spec() - .withBearerToken(nonStudentUser.token) - .patch(`/ue/annals/${annal_validated.id}`) + .withBearerToken(senderUser.token) + .patch(`/ue/annals/${Dummies.UUID}`) .withBody(generateBody()) - .expectAppError(ERROR_CODE.FORBIDDEN_INVALID_ROLE, 'STUDENT'); + .expectAppError(ERROR_CODE.NO_SUCH_ANNAL, Dummies.UUID); }); it('should return a 403 because user is not the author', () => { diff --git a/test/e2e/ue/annals/upload-annal.e2e-spec.ts b/test/e2e/ue/annals/upload-annal.e2e-spec.ts index 7991f35..6b0305b 100644 --- a/test/e2e/ue/annals/upload-annal.e2e-spec.ts +++ b/test/e2e/ue/annals/upload-annal.e2e-spec.ts @@ -17,9 +17,9 @@ import { pick } from '../../../../src/utils'; import { mkdirSync, rmSync } from 'fs'; const PostAnnal = e2eSuite('POST-PUT /ue/annals', (app) => { - const senderUser = createUser(app); - const nonUeUser = createUser(app, { login: 'user2', studentId: 2 }); - const nonStudentUser = createUser(app, { login: 'nonStudent', studentId: 4, userType: 'TEACHER' }); + const senderUser = createUser(app, { permissions: ['API_UPLOAD_ANNALS'] }); + const nonUeUser = createUser(app, { login: 'user2', studentId: 2, permissions: ['API_UPLOAD_ANNALS'] }); + const userNoPermission = createUser(app); const annalType = createAnnalType(app); const semester = createSemester(app); const branch = createBranch(app); @@ -32,30 +32,29 @@ const PostAnnal = e2eSuite('POST-PUT /ue/annals', (app) => { return pactum.spec().post(`/ue/annals`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); - it('should return a 404 because UE does not exist', () => { - return pactum + it('should fail as the user does not have the required permissions', () => + pactum .spec() - .withBearerToken(senderUser.token) + .withBearerToken(userNoPermission.token) .post(`/ue/annals`) .withBody({ semester: semester.code, typeId: annalType.id, ueCode: ue.code.slice(0, ue.code.length - 1), }) - .expectAppError(ERROR_CODE.NO_SUCH_UE, ue.code.slice(0, ue.code.length - 1)); - }); + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_UPLOAD_ANNALS')); - it('should return a 403 because user is not a student', () => { + it('should return a 404 because UE does not exist', () => { return pactum .spec() - .withBearerToken(nonStudentUser.token) + .withBearerToken(senderUser.token) .post(`/ue/annals`) .withBody({ semester: semester.code, typeId: annalType.id, - ueCode: ue.code, + ueCode: ue.code.slice(0, ue.code.length - 1), }) - .expectAppError(ERROR_CODE.FORBIDDEN_INVALID_ROLE, 'STUDENT'); + .expectAppError(ERROR_CODE.NO_SUCH_UE, ue.code.slice(0, ue.code.length - 1)); }); it('should return a 403 because user has not done the UE', () => { diff --git a/test/e2e/ue/comments/delete-comment.e2e-spec.ts b/test/e2e/ue/comments/delete-comment.e2e-spec.ts index d893651..35f1b70 100644 --- a/test/e2e/ue/comments/delete-comment.e2e-spec.ts +++ b/test/e2e/ue/comments/delete-comment.e2e-spec.ts @@ -15,8 +15,9 @@ import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; import { PrismaService } from '../../../../src/prisma/prisma.service'; const DeleteComment = e2eSuite('DELETE /ue/comments/:commentId', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotAuthor = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -29,10 +30,17 @@ const DeleteComment = e2eSuite('DELETE /ue/comments/:commentId', (app) => { return pactum.spec().delete(`/ue/comments/${comment1.id}`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .delete(`/ue/comments/${comment1.id}`) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 403 because user is not the author', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .delete(`/ue/comments/${comment1.id}`) .expectAppError(ERROR_CODE.NOT_COMMENT_AUTHOR); }); diff --git a/test/e2e/ue/comments/delete-reply.e2e-spec.ts b/test/e2e/ue/comments/delete-reply.e2e-spec.ts index 9ce55b5..7760e57 100644 --- a/test/e2e/ue/comments/delete-reply.e2e-spec.ts +++ b/test/e2e/ue/comments/delete-reply.e2e-spec.ts @@ -15,8 +15,9 @@ import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; import { PrismaService } from '../../../../src/prisma/prisma.service'; const DeleteCommentReply = e2eSuite('DELETE /ue/comments/reply/{replyId}', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotAuthor = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -29,10 +30,22 @@ const DeleteCommentReply = e2eSuite('DELETE /ue/comments/reply/{replyId}', (app) return pactum.spec().delete(`/ue/comments/reply/${reply.id}`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .delete(`/ue/comments/reply/${reply.id}`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 403 because user is not the author', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .delete(`/ue/comments/reply/${reply.id}`) .expectAppError(ERROR_CODE.NOT_REPLY_AUTHOR); }); diff --git a/test/e2e/ue/comments/delete-upvote.e2e-spec.ts b/test/e2e/ue/comments/delete-upvote.e2e-spec.ts index 80007ca..6f40094 100644 --- a/test/e2e/ue/comments/delete-upvote.e2e-spec.ts +++ b/test/e2e/ue/comments/delete-upvote.e2e-spec.ts @@ -15,20 +15,33 @@ import { Dummies, e2eSuite } from '../../../utils/test_utils'; import { PrismaService } from '../../../../src/prisma/prisma.service'; const DeleteUpvote = e2eSuite('DELETE /ue/comments/{commentId}/upvote', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotAuthor = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); const ue = createUe(app); const ueof = createUeof(app, { branchOptions: [branchOption], semesters: [semester], ue }); const comment1 = createComment(app, { user, ueof, semester }); - const upvote = createCommentUpvote(app, { user: user2, comment: comment1 }); + const upvote = createCommentUpvote(app, { user: userNotAuthor, comment: comment1 }); it('should return a 401 as user is not authenticated', () => { return pactum.spec().delete(`/ue/comments/${comment1.id}/upvote`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .delete(`/ue/comments/${comment1.id}/upvote`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 403 because user is the author', () => { return pactum .spec() @@ -40,7 +53,7 @@ const DeleteUpvote = e2eSuite('DELETE /ue/comments/{commentId}/upvote', (app) => it('should return a 400 because uuid is not an uuid', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .delete(`/ue/comments/${comment1.id.slice(0, 31)}/upvote`) .expectAppError(ERROR_CODE.PARAM_NOT_UUID, 'commentId'); }); @@ -48,7 +61,7 @@ const DeleteUpvote = e2eSuite('DELETE /ue/comments/{commentId}/upvote', (app) => it('should return a 404 because reply does not exist', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .delete(`/ue/comments/${Dummies.UUID}/upvote`) .expectAppError(ERROR_CODE.NO_SUCH_COMMENT); }); @@ -56,11 +69,11 @@ const DeleteUpvote = e2eSuite('DELETE /ue/comments/{commentId}/upvote', (app) => it('should delete the upvote', async () => { await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .delete(`/ue/comments/${comment1.id}/upvote`) .expectStatus(HttpStatus.OK) .expectJsonMatchStrict({ upvoted: false }); - return createCommentUpvote(app, { user: user2, comment: comment1 }, upvote, true); + return createCommentUpvote(app, { user: userNotAuthor, comment: comment1 }, upvote, true); }); it('should not be able to re-de-upvote a comment', async () => { @@ -69,10 +82,10 @@ const DeleteUpvote = e2eSuite('DELETE /ue/comments/{commentId}/upvote', (app) => .ueCommentUpvote.delete({ where: { id: upvote.id } }); await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .delete(`/ue/comments/${comment1.id}/upvote`) .expectAppError(ERROR_CODE.FORBIDDEN_NOT_UPVOTED); - return createCommentUpvote(app, { user: user2, comment: comment1 }, upvote, true); + return createCommentUpvote(app, { user: userNotAuthor, comment: comment1 }, upvote, true); }); }); diff --git a/test/e2e/ue/comments/get-comment-from-id.e2e-spec.ts b/test/e2e/ue/comments/get-comment-from-id.e2e-spec.ts index 5728400..13a4722 100644 --- a/test/e2e/ue/comments/get-comment-from-id.e2e-spec.ts +++ b/test/e2e/ue/comments/get-comment-from-id.e2e-spec.ts @@ -7,21 +7,34 @@ import { omit } from '../../../../src/utils'; import { FakeComment } from '../../../utils/fakedb'; const GetCommentFromIdE2ESpec = e2eSuite('GET /ue/comments/:commentId', (app) => { - const user = fakedb.createUser(app); - const user2 = fakedb.createUser(app, { login: 'user2' }); + const user = fakedb.createUser(app, { permissions: ['API_SEE_OPINIONS_UE'] }); + const userNotAuthor = fakedb.createUser(app, { login: 'user2', permissions: ['API_SEE_OPINIONS_UE'] }); + const userNoPermission = fakedb.createUser(app); const semester = fakedb.createSemester(app); const branch = fakedb.createBranch(app); const branchOption = fakedb.createBranchOption(app, { branch }); const ue = fakedb.createUe(app); const ueof = fakedb.createUeof(app, { branchOptions: [branchOption], semesters: [semester], ue }); const comment = fakedb.createComment(app, { user, ueof, semester }); - fakedb.createCommentUpvote(app, { user: user2, comment }); + fakedb.createCommentUpvote(app, { user: userNotAuthor, comment }); const reply = fakedb.createCommentReply(app, { user, comment }, { body: 'HelloWorld' }); it('should return a 401 as user is not authenticated', () => { return pactum.spec().get(`/ue/comments/${comment.id}`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .get(`/ue/comments/${comment.id}`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_SEE_OPINIONS_UE')); + it('should return a 400 because comment id is not a valid uuid', () => { return pactum .spec() @@ -75,7 +88,7 @@ const GetCommentFromIdE2ESpec = e2eSuite('GET /ue/comments/:commentId', (app) => it('should return the comment without the author field as the user is not the author', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .get(`/ue/comments/${comment.id}`) .expectUeComment({ ueof, diff --git a/test/e2e/ue/comments/get-comment.e2e-spec.ts b/test/e2e/ue/comments/get-comment.e2e-spec.ts index 4753f31..29c849a 100644 --- a/test/e2e/ue/comments/get-comment.e2e-spec.ts +++ b/test/e2e/ue/comments/get-comment.e2e-spec.ts @@ -61,6 +61,15 @@ const GetCommentsE2ESpec = e2eSuite('GET /ue/comments', (app) => { return pactum.spec().get(`/ue/comments`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should return a 403 as user does not have the permissions to see the comments', () => { + return pactum + .spec() + .withBearerToken(userNoPermission.token) + .get('/ue/comments') + .withQueryParams({ ueCode: ue.code }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_SEE_OPINIONS_UE'); + }); + it('should return a 400 as user uses a wrong page', () => { return pactum .spec() @@ -73,14 +82,6 @@ const GetCommentsE2ESpec = e2eSuite('GET /ue/comments', (app) => { .expectAppError(ERROR_CODE.PARAM_NOT_POSITIVE, 'page'); }); - it('should return a 403 as user does not have the permissions to see the comments', () => { - return pactum - .spec() - .withBearerToken(userNoPermission.token) - .get('/ue/comments') - .withQueryParams({ ueCode: ue.code }); - }); - it('should return a 404 because UE does not exist', () => { return pactum .spec() diff --git a/test/e2e/ue/comments/post-comment.e2e-spec.ts b/test/e2e/ue/comments/post-comment.e2e-spec.ts index dac8285..fb022ea 100644 --- a/test/e2e/ue/comments/post-comment.e2e-spec.ts +++ b/test/e2e/ue/comments/post-comment.e2e-spec.ts @@ -15,14 +15,15 @@ import { PrismaService } from '../../../../src/prisma/prisma.service'; import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; const PostCommment = e2eSuite('POST /ue/comments', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const userNotDoneUe = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userDidUe = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); const ue = createUe(app); const ueof = createUeof(app, { branchOptions: [branchOption], semesters: [semester], ue }); - createUeSubscription(app, { user: user2, ueof, semester }); + createUeSubscription(app, { user: userDidUe, ueof, semester }); it('should return a 401 as user is not authenticated', () => { return pactum @@ -35,10 +36,22 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { .expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .post(`/ue/comments`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 400 because body is a not string', () => { return pactum .spec() - .withBearerToken(user.token) + .withBearerToken(userNotDoneUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code, @@ -51,7 +64,7 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { it('should return a 400 because body is too short', () => { return pactum .spec() - .withBearerToken(user.token) + .withBearerToken(userNotDoneUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code, @@ -63,7 +76,7 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { it('should return a 404 because UE does not exist', () => { return pactum .spec() - .withBearerToken(user.token) + .withBearerToken(userNotDoneUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code.slice(0, ue.code.length - 1), @@ -75,7 +88,7 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { it('should return a 403 because user has not done the UE yet', () => { return pactum .spec() - .withBearerToken(user.token) + .withBearerToken(userNotDoneUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code, @@ -88,7 +101,7 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { it('should return a comment as anonymous user', async () => { await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userDidUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code, @@ -100,9 +113,9 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { id: JsonLike.ANY_UUID, ueof, author: { - id: user2.id, - firstName: user2.firstName, - lastName: user2.lastName, + id: userDidUe.id, + firstName: userDidUe.firstName, + lastName: userDidUe.lastName, }, createdAt: JsonLike.ANY_DATE, updatedAt: JsonLike.ANY_DATE, @@ -120,10 +133,10 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { }); it('should return a 403 while trying to post another comment', async () => { - await createComment(app, { ueof, user: user2, semester }, { isAnonymous: true }, true); + await createComment(app, { ueof, user: userDidUe, semester }, { isAnonymous: true }, true); await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userDidUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code, @@ -136,7 +149,7 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { it('should return a comment as a logged in user', async () => { await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userDidUe.token) .post(`/ue/comments`) .withBody({ ueCode: ue.code, @@ -147,9 +160,9 @@ const PostCommment = e2eSuite('POST /ue/comments', (app) => { ueof, id: JsonLike.ANY_UUID, author: { - id: user2.id, - firstName: user2.firstName, - lastName: user2.lastName, + id: userDidUe.id, + firstName: userDidUe.firstName, + lastName: userDidUe.lastName, }, createdAt: JsonLike.ANY_DATE, updatedAt: JsonLike.ANY_DATE, diff --git a/test/e2e/ue/comments/post-reply.e2e-spec.ts b/test/e2e/ue/comments/post-reply.e2e-spec.ts index c4dcc08..06aaf1a 100644 --- a/test/e2e/ue/comments/post-reply.e2e-spec.ts +++ b/test/e2e/ue/comments/post-reply.e2e-spec.ts @@ -15,7 +15,8 @@ import { PrismaService } from '../../../../src/prisma/prisma.service'; import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; const PostCommmentReply = e2eSuite('POST /ue/comments/{commentId}/reply', (app) => { - const user = createUser(app); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -34,6 +35,18 @@ const PostCommmentReply = e2eSuite('POST /ue/comments/{commentId}/reply', (app) .expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .post(`/ue/comments/${comment.id}/reply`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 400 because body is required', () => { return pactum .spec() diff --git a/test/e2e/ue/comments/post-upvote.e2e-spec.ts b/test/e2e/ue/comments/post-upvote.e2e-spec.ts index 3146851..d79d901 100644 --- a/test/e2e/ue/comments/post-upvote.e2e-spec.ts +++ b/test/e2e/ue/comments/post-upvote.e2e-spec.ts @@ -15,8 +15,9 @@ import { Dummies, e2eSuite } from '../../../utils/test_utils'; import { PrismaService } from '../../../../src/prisma/prisma.service'; const PostUpvote = e2eSuite('POST /ue/comments/{commentId}/upvote', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotAuthor = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -28,6 +29,18 @@ const PostUpvote = e2eSuite('POST /ue/comments/{commentId}/upvote', (app) => { return pactum.spec().post(`/ue/comments/${comment.id}/upvote`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .post(`/ue/comments/${comment.id}/upvote`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 403 because user is the author', () => { return pactum .spec() @@ -39,7 +52,7 @@ const PostUpvote = e2eSuite('POST /ue/comments/{commentId}/upvote', (app) => { it('should return a 400 because uuid is not an uuid', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .post(`/ue/comments/${comment.id.slice(0, 31)}/upvote`) .expectAppError(ERROR_CODE.PARAM_NOT_UUID, 'commentId'); }); @@ -47,7 +60,7 @@ const PostUpvote = e2eSuite('POST /ue/comments/{commentId}/upvote', (app) => { it('should return a 404 because reply does not exist', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .post(`/ue/comments/${Dummies.UUID}/upvote`) .expectAppError(ERROR_CODE.NO_SUCH_COMMENT); }); @@ -55,7 +68,7 @@ const PostUpvote = e2eSuite('POST /ue/comments/{commentId}/upvote', (app) => { it('should create the upvote', async () => { await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .post(`/ue/comments/${comment.id}/upvote`) .expectStatus(HttpStatus.OK) .expectJsonMatchStrict({ upvoted: true }); @@ -63,10 +76,10 @@ const PostUpvote = e2eSuite('POST /ue/comments/{commentId}/upvote', (app) => { }); it('should not be able to re-upvote upvote', async () => { - await createCommentUpvote(app, { user: user2, comment }, {}, true); + await createCommentUpvote(app, { user: userNotAuthor, comment }, {}, true); await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .post(`/ue/comments/${comment.id}/upvote`) .expectAppError(ERROR_CODE.FORBIDDEN_ALREADY_UPVOTED); return app().get(PrismaService).ueCommentUpvote.deleteMany(); diff --git a/test/e2e/ue/comments/update-comment.e2e-spec.ts b/test/e2e/ue/comments/update-comment.e2e-spec.ts index b123fdf..b570a7b 100644 --- a/test/e2e/ue/comments/update-comment.e2e-spec.ts +++ b/test/e2e/ue/comments/update-comment.e2e-spec.ts @@ -15,15 +15,16 @@ import { PrismaService } from '../../../../src/prisma/prisma.service'; import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; const UpdateComment = e2eSuite('PATCH /ue/comments/:commentId', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotCommentAuthor = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); const ue = createUe(app); const ueof = createUeof(app, { branchOptions: [branchOption], semesters: [semester], ue }); const comment = createComment(app, { ueof, user, semester }); - createCommentUpvote(app, { user: user2, comment }); + createCommentUpvote(app, { user: userNotCommentAuthor, comment }); it('should return a 401 as user is not authenticated', () => { return pactum @@ -35,6 +36,16 @@ const UpdateComment = e2eSuite('PATCH /ue/comments/:commentId', (app) => { .expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .patch(`/ue/comments/${comment.id}`) + .withBody({ + body: 'Test comment', + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 400 because body is a string', () => { return pactum .spec() @@ -50,7 +61,7 @@ const UpdateComment = e2eSuite('PATCH /ue/comments/:commentId', (app) => { it('should return a 403 because user is not the author', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotCommentAuthor.token) .patch(`/ue/comments/${comment.id}`) .withBody({ body: 'Cette UE est troooop bien', @@ -121,7 +132,7 @@ const UpdateComment = e2eSuite('PATCH /ue/comments/:commentId', (app) => { }); await app().get(PrismaService).ueComment.deleteMany(); await createComment(app, { ueof, user, semester }, comment, true); - return createCommentUpvote(app, { user: user2, comment }, {}, true); + return createCommentUpvote(app, { user: userNotCommentAuthor, comment }, {}, true); }); it('should return the updated comment as a logged in user', async () => { @@ -152,7 +163,7 @@ const UpdateComment = e2eSuite('PATCH /ue/comments/:commentId', (app) => { }); await app().get(PrismaService).ueComment.deleteMany(); await createComment(app, { ueof, user, semester }, comment, true); - return createCommentUpvote(app, { user: user2, comment }, {}, true); + return createCommentUpvote(app, { user: userNotCommentAuthor, comment }, {}, true); }); }); diff --git a/test/e2e/ue/comments/update-reply.e2e-spec.ts b/test/e2e/ue/comments/update-reply.e2e-spec.ts index ff14631..00b5cde 100644 --- a/test/e2e/ue/comments/update-reply.e2e-spec.ts +++ b/test/e2e/ue/comments/update-reply.e2e-spec.ts @@ -15,8 +15,9 @@ import { PrismaService } from '../../../../src/prisma/prisma.service'; import { CommentStatus } from 'src/ue/comments/interfaces/comment.interface'; const UpdateCommentReply = e2eSuite('PATCH /ue/comments/reply/{replyId}', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotAuthor = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -35,6 +36,18 @@ const UpdateCommentReply = e2eSuite('PATCH /ue/comments/reply/{replyId}', (app) .expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .patch(`/ue/comments/reply/${reply.id}`) + .withBody({ + ueCode: ue.code, + body: false, + isAnonymous: true, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 400 because body is a string', () => { return pactum .spec() @@ -49,7 +62,7 @@ const UpdateCommentReply = e2eSuite('PATCH /ue/comments/reply/{replyId}', (app) it('should return a 403 because user is not the author', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotAuthor.token) .patch(`/ue/comments/reply/${reply.id}`) .withBody({ body: "Je m'appelle Alban Ichou et j'approuve ce commentaire", diff --git a/test/e2e/ue/delete-rate.e2e-spec.ts b/test/e2e/ue/delete-rate.e2e-spec.ts index 3ecfc43..46e8df0 100644 --- a/test/e2e/ue/delete-rate.e2e-spec.ts +++ b/test/e2e/ue/delete-rate.e2e-spec.ts @@ -15,8 +15,9 @@ import { Dummies, e2eSuite } from '../../utils/test_utils'; import { faker } from '@faker-js/faker'; const DeleteRate = e2eSuite('DELETE /ue/ueof/{ueofCode}/rate/{critetionId}', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNotRated = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermissions = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -30,10 +31,18 @@ const DeleteRate = e2eSuite('DELETE /ue/ueof/{ueofCode}/rate/{critetionId}', (ap return pactum.spec().delete(`/ue/ueof/${ueof.code}/rate/${criterion.id}`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the required permissions', () => { + return pactum + .spec() + .withBearerToken(userNoPermissions.token) + .delete(`/ue/ueof/${ueof.code}/rate/${criterion.id}`) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE'); + }); + it('should return a 403 as user has not rated the UE', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotRated.token) .delete(`/ue/ueof/${ueof.code}/rate/${criterion.id}`) .expectAppError(ERROR_CODE.NOT_ALREADY_RATED_UEOF, ueof.code, criterion.id); }); @@ -42,7 +51,7 @@ const DeleteRate = e2eSuite('DELETE /ue/ueof/{ueofCode}/rate/{critetionId}', (ap const nonExistentCode = faker.db.ue.code(); return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotRated.token) .delete(`/ue/ueof/${nonExistentCode}/rate/${criterion.id}`) .expectAppError(ERROR_CODE.NO_SUCH_UEOF, nonExistentCode); }); @@ -50,7 +59,7 @@ const DeleteRate = e2eSuite('DELETE /ue/ueof/{ueofCode}/rate/{critetionId}', (ap it('should return a 404 as the criterion does not exist', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNotRated.token) .delete(`/ue/ueof/${ueof.code}/rate/${Dummies.UUID}`) .expectAppError(ERROR_CODE.NO_SUCH_CRITERION); }); diff --git a/test/e2e/ue/get-rate-criteria.e2e-spec.ts b/test/e2e/ue/get-rate-criteria.e2e-spec.ts index 67aae28..6aed69e 100644 --- a/test/e2e/ue/get-rate-criteria.e2e-spec.ts +++ b/test/e2e/ue/get-rate-criteria.e2e-spec.ts @@ -2,12 +2,24 @@ import { omit } from '../../../src/utils'; import { createUser, createCriterion, FakeUeStarCriterion } from '../../utils/fakedb'; import { e2eSuite } from '../../utils/test_utils'; import * as pactum from 'pactum'; +import { ERROR_CODE } from '../../../src/exceptions'; const GetRateCriteria = e2eSuite('GET /ue/rate/criteria', (app) => { - const user = createUser(app); + const userNoPermission = createUser(app); + const user = createUser(app, { permissions: ['API_SEE_OPINIONS_UE'] }); const criteria: FakeUeStarCriterion[] = []; for (let i = 0; i < 30; i++) criteria.push(createCriterion(app)); + it('should fail as the user is not logged in', () => + pactum.spec().get('/ue/rate/criteria').expectAppError(ERROR_CODE.NOT_LOGGED_IN)); + + it('should fail as the user does not have the right permissions', () => + pactum + .spec() + .get('/ue/rate/criteria') + .withBearerToken(userNoPermission.token) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_SEE_OPINIONS_UE')); + it('should return all the criteria', () => { return pactum .spec() diff --git a/test/e2e/ue/get-ue-rate.e2e-spec.ts b/test/e2e/ue/get-ue-rate.e2e-spec.ts index 7fa0393..d0486bc 100644 --- a/test/e2e/ue/get-ue-rate.e2e-spec.ts +++ b/test/e2e/ue/get-ue-rate.e2e-spec.ts @@ -13,8 +13,9 @@ import { ERROR_CODE } from 'src/exceptions'; import { e2eSuite } from '../../utils/test_utils'; const GetRateE2ESpec = e2eSuite('GET /ue/:ueCode/rate', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const userNoPermission = createUser(app); + const userFullRating = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userPartialRating = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -22,19 +23,26 @@ const GetRateE2ESpec = e2eSuite('GET /ue/:ueCode/rate', (app) => { const ueof = createUeof(app, { branchOptions: [branchOption], semesters: [semester], ue }); const c1 = createCriterion(app); const c2 = createCriterion(app); - createUeRating(app, { ueof, criterion: c1, user }, { value: 1 }); - createUeRating(app, { ueof, criterion: c2, user }, { value: 5 }); - createUeRating(app, { ueof, criterion: c1, user: user2 }, { value: 2 }); + createUeRating(app, { ueof, criterion: c1, user: userFullRating }, { value: 1 }); + createUeRating(app, { ueof, criterion: c2, user: userFullRating }, { value: 5 }); + createUeRating(app, { ueof, criterion: c1, user: userPartialRating }, { value: 2 }); it('should return a 401 as user is not authenticated', () => { return pactum.spec().get(`/ue/${ue.code}/rate`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should return an error if the user does not have the permissions', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .get(`/ue/${ue.code}/rate`) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return an error if the ue does not exist', () => { const otherUeCode = ue.code === 'AA01' ? 'AA02' : 'AA01'; return pactum .spec() - .withBearerToken(user.token) + .withBearerToken(userFullRating.token) .get(`/ue/${otherUeCode}/rate`) .expectAppError(ERROR_CODE.NO_SUCH_UE, otherUeCode); }); @@ -42,7 +50,7 @@ const GetRateE2ESpec = e2eSuite('GET /ue/:ueCode/rate', (app) => { it('should return the user rate for the UE', () => { return pactum .spec() - .withBearerToken(user.token) + .withBearerToken(userFullRating.token) .get(`/ue/${ue.code}/rate`) .expectUeRates({ [ueof.code]: [ @@ -66,7 +74,7 @@ const GetRateE2ESpec = e2eSuite('GET /ue/:ueCode/rate', (app) => { it('should return the user rate for the UE (partial rating)', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userPartialRating.token) .get(`/ue/${ue.code}/rate`) .expectUeRates({ [ueof.code]: [ diff --git a/test/e2e/ue/index.ts b/test/e2e/ue/index.ts index 1ea6bbd..cf013e5 100644 --- a/test/e2e/ue/index.ts +++ b/test/e2e/ue/index.ts @@ -7,7 +7,6 @@ import PutRate from './put-rate.e2e-spec'; import DeleteRate from './delete-rate.e2e-spec'; import AnnalsE2ESpec from './annals'; import CommentsE2ESpec from './comments'; -import CreditE2ESpec from './credit'; import GetMyUesE2ESpec from './get-my-ues.e2e-spec'; export default function UeE2ESpec(app: () => INestApplication) { @@ -20,7 +19,7 @@ export default function UeE2ESpec(app: () => INestApplication) { DeleteRate(app); CommentsE2ESpec(app); AnnalsE2ESpec(app); - CreditE2ESpec(app); + // CreditE2ESpec(app); GetMyUesE2ESpec(app); }); } diff --git a/test/e2e/ue/put-rate.e2e-spec.ts b/test/e2e/ue/put-rate.e2e-spec.ts index a1c4c51..d5cf8a9 100644 --- a/test/e2e/ue/put-rate.e2e-spec.ts +++ b/test/e2e/ue/put-rate.e2e-spec.ts @@ -15,8 +15,9 @@ import { PrismaService } from '../../../src/prisma/prisma.service'; import { faker } from '@faker-js/faker'; const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { - const user = createUser(app); - const user2 = createUser(app, { login: 'user2' }); + const user = createUser(app, { permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoUe = createUser(app, { login: 'user2', permissions: ['API_GIVE_OPINIONS_UE'] }); + const userNoPermission = createUser(app); const semester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); @@ -29,10 +30,21 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { return pactum.spec().put(`/ue/ueof/${ueof.code}/rate`).expectAppError(ERROR_CODE.NOT_LOGGED_IN); }); + it('should fail as the user does not have the permissions required', () => + pactum + .spec() + .withBearerToken(userNoPermission.token) + .put(`/ue/ueof/${ueof.code}/rate`) + .withBody({ + criterion: criterion.id, + value: 1, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_GIVE_OPINIONS_UE')); + it('should return a 403 as user has not done the UE', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: criterion.id, @@ -44,7 +56,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { it('should return a 400 as value is not a number', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: criterion.id, @@ -56,7 +68,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { it('should return a 400 as value is not an int', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: criterion.id, @@ -68,7 +80,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { it('should return a 400 as value is too high', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: criterion.id, @@ -80,7 +92,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { it('should return a 400 as value is too low', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: criterion.id, @@ -92,7 +104,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { it('should return a 400 as the criterion is not a string', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: true, @@ -104,7 +116,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { it('should return a 404 as the criterion does not exist', () => { return pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${ueof.code}/rate`) .withBody({ criterion: criterion.id.slice(0, 10), @@ -117,7 +129,7 @@ const PutRate = e2eSuite('PUT /ue/ueof/{ueofCode}/rate', (app) => { const nonExistentCode = faker.db.ue.code(); await pactum .spec() - .withBearerToken(user2.token) + .withBearerToken(userNoUe.token) .put(`/ue/ueof/${nonExistentCode}/rate`) .withBody({ criterion: criterion.id, From d498e1f74a4330f41e58680ec304f8f861dd81fa Mon Sep 17 00:00:00 2001 From: Teddy Roncin Date: Sun, 11 May 2025 23:59:40 +0200 Subject: [PATCH 2/3] Permission errors are back to FORBIDDEN --- src/exceptions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exceptions.ts b/src/exceptions.ts index edd8058..dcd2e53 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -206,11 +206,11 @@ export const ErrorData = Object.freeze({ }, [ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS]: { message: 'Missing permission %', - httpCode: HttpStatus.UNAUTHORIZED, + httpCode: HttpStatus.FORBIDDEN, }, [ERROR_CODE.FORBIDDEN_NOT_ENOUGH_USER_PERMISSIONS]: { message: 'Missing permission % on user %', - httpCode: HttpStatus.UNAUTHORIZED, + httpCode: HttpStatus.FORBIDDEN, }, [ERROR_CODE.NO_TOKEN]: { message: 'No token provided', From b5a6133ab96f940b184a8e6a3888654d32d4e7fe Mon Sep 17 00:00:00 2001 From: Teddy Roncin Date: Mon, 12 May 2025 00:41:32 +0200 Subject: [PATCH 3/3] More testing for codecov --- test/e2e/ue/annals/upload-annal.e2e-spec.ts | 56 +++++++++++++++++---- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/test/e2e/ue/annals/upload-annal.e2e-spec.ts b/test/e2e/ue/annals/upload-annal.e2e-spec.ts index 6b0305b..846b7df 100644 --- a/test/e2e/ue/annals/upload-annal.e2e-spec.ts +++ b/test/e2e/ue/annals/upload-annal.e2e-spec.ts @@ -20,8 +20,10 @@ const PostAnnal = e2eSuite('POST-PUT /ue/annals', (app) => { const senderUser = createUser(app, { permissions: ['API_UPLOAD_ANNALS'] }); const nonUeUser = createUser(app, { login: 'user2', studentId: 2, permissions: ['API_UPLOAD_ANNALS'] }); const userNoPermission = createUser(app); + const userModerator = createUser(app, { permissions: ['API_UPLOAD_ANNALS', 'API_MODERATE_ANNALS'] }); const annalType = createAnnalType(app); const semester = createSemester(app); + const otherRandomSemester = createSemester(app); const branch = createBranch(app); const branchOption = createBranchOption(app, { branch }); const ue = createUe(app); @@ -40,12 +42,25 @@ const PostAnnal = e2eSuite('POST-PUT /ue/annals', (app) => { .withBody({ semester: semester.code, typeId: annalType.id, - ueCode: ue.code.slice(0, ue.code.length - 1), + ueCode: ue.code, }) .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_UPLOAD_ANNALS')); - it('should return a 404 because UE does not exist', () => { - return pactum + it('should fail as user does not have permission API_MODERATE_ANNALS to upload to any ueof', () => + pactum + .spec() + .withBearerToken(senderUser.token) + .post(`/ue/annals`) + .withBody({ + semester: semester.code, + typeId: annalType.id, + ueCode: ue.code, + ueof: ueof.code, + }) + .expectAppError(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, 'API_MODERATE_ANNALS')); + + it('should return a 404 because UE does not exist', () => + pactum .spec() .withBearerToken(senderUser.token) .post(`/ue/annals`) @@ -54,11 +69,23 @@ const PostAnnal = e2eSuite('POST-PUT /ue/annals', (app) => { typeId: annalType.id, ueCode: ue.code.slice(0, ue.code.length - 1), }) - .expectAppError(ERROR_CODE.NO_SUCH_UE, ue.code.slice(0, ue.code.length - 1)); - }); + .expectAppError(ERROR_CODE.NO_SUCH_UE, ue.code.slice(0, ue.code.length - 1))); - it('should return a 403 because user has not done the UE', () => { - return pactum + it('should return a 404 because UEOF does not exist', () => + pactum + .spec() + .withBearerToken(userModerator.token) + .post(`/ue/annals`) + .withBody({ + semester: semester.code, + typeId: annalType.id, + ueCode: ue.code, + ueof: ueof.code.slice(0, ueof.code.length - 1), + }) + .expectAppError(ERROR_CODE.NO_SUCH_UEOF, ueof.code.slice(0, ueof.code.length - 1))); + + it('should return a 403 because user has not done the UE', () => + pactum .spec() .withBearerToken(nonUeUser.token) .post(`/ue/annals`) @@ -67,8 +94,19 @@ const PostAnnal = e2eSuite('POST-PUT /ue/annals', (app) => { typeId: annalType.id, ueCode: ue.code, }) - .expectAppError(ERROR_CODE.NOT_DONE_UE_IN_SEMESTER, ue.code, semester.code); - }); + .expectAppError(ERROR_CODE.NOT_DONE_UE_IN_SEMESTER, ue.code, semester.code)); + + it('should fail as ue did not happen at the given semester', () => + pactum + .spec() + .withBearerToken(userModerator.token) + .post(`/ue/annals`) + .withBody({ + semester: otherRandomSemester.code, + typeId: annalType.id, + ueCode: ue.code, + }) + .expectAppError(ERROR_CODE.NO_SUCH_UE_AT_SEMESTER, ue.code, otherRandomSemester.code)); describe('should create the annal', () => { beforeAll(() => {