diff --git a/CHANGELOG.md b/CHANGELOG.md
index 747536a0..33ebe8ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ Another example [here](https://co-pilot.dev/changelog)
### Added
- Add units tests for the teams controller & services([#189](https://github.com/chingu-x/chingu-dashboard-be/pull/189))
- Add discord oauth and e2e test ([#194](https://github.com/chingu-x/chingu-dashboard-be/pull/194))
+- Add CASL permissions for Team Sprint endpoint ([#193](https://github.com/chingu-x/chingu-dashboard-be/pull/193))
+
### Changed
- updated changelog ([#195](https://github.com/chingu-x/chingu-dashboard-be/pull/195))
diff --git a/src/ability/ability.factory/ability.factory.ts b/src/ability/ability.factory/ability.factory.ts
index 15995336..2391cf6e 100644
--- a/src/ability/ability.factory/ability.factory.ts
+++ b/src/ability/ability.factory/ability.factory.ts
@@ -12,6 +12,7 @@ export enum Action {
Update = "update",
Delete = "delete",
Submit = "submit",
+ ManageSprintForms = "manageSprintForms",
}
type ExtendedSubjects = "all";
@@ -25,33 +26,40 @@ export class AbilityFactory {
createPrismaAbility,
);
+ const userVoyageTeamIds = user.voyageTeams.map((vt) => vt.teamId);
+ const userVoyageTeamMemberIds = user.voyageTeams.map(
+ (vt) => vt.memberId,
+ );
+
if (user.roles.includes("admin")) {
can(Action.Manage, "all");
} else if (user.roles.includes("voyager")) {
can([Action.Submit], "Voyage");
can([Action.Manage], "VoyageTeam", {
- id: { in: user.voyageTeams.map((vt) => vt.teamId) },
+ id: { in: userVoyageTeamIds },
});
// For Ideation and Tech stack, we make the permission team based here
// as there are times we'll need them to be able to manage other team members ideations/tech
// more specific permission checks can be found in `ideations.ability.ts` etc
can([Action.Manage], "Ideation", {
voyageTeamMemberId: {
- in: user.voyageTeams.map((vt) => vt.memberId),
+ in: userVoyageTeamMemberIds,
},
});
can([Action.Submit, Action.Read], "Form");
can([Action.Manage], "TeamTechStackItem");
can([Action.Manage], "Resource", {
teamMemberId: {
- in: user.voyageTeams.map((vt) => vt.memberId),
+ in: userVoyageTeamMemberIds,
},
});
can([Action.Manage], "Feature", {
teamMemberId: {
- in: user.voyageTeams.map((vt) => vt.memberId),
+ in: userVoyageTeamMemberIds,
},
});
+ can([Action.ManageSprintForms, Action.Read], "Sprint");
+ can([Action.Manage], "SprintMeetingOrAgenda");
} else {
// all other users
can([Action.Submit, Action.Read], "Form", {
diff --git a/src/ability/conditions/meetingOrAgenda.ability.ts b/src/ability/conditions/meetingOrAgenda.ability.ts
new file mode 100644
index 00000000..8660e255
--- /dev/null
+++ b/src/ability/conditions/meetingOrAgenda.ability.ts
@@ -0,0 +1,71 @@
+import { ForbiddenException, NotFoundException } from "@nestjs/common";
+import { UserReq } from "../../global/types/CustomRequest";
+import prisma from "../../prisma/client";
+import { Agenda, TeamMeeting } from "@prisma/client";
+
+export const manageOwnTeamMeetingOrAgendaById = async ({
+ user,
+ meetingId,
+ agendaId,
+}: {
+ user: UserReq;
+ meetingId?: number;
+ agendaId?: number;
+ subject?: TeamMeeting | Agenda; // If we want to extend some more permissions for any particular subject
+}) => {
+ let meetingOrAgendaTeamId: number;
+ const voyagerTeamIds = user.voyageTeams.map((vt) => vt.teamId);
+ if (meetingId) {
+ const meeting = await prisma.teamMeeting.findUnique({
+ where: {
+ id: meetingId,
+ },
+ select: {
+ voyageTeamId: true,
+ },
+ });
+ if (!meeting) {
+ throw new NotFoundException(
+ `Team Meeting (id:${meetingId}) not found`,
+ );
+ }
+
+ meetingOrAgendaTeamId = meeting.voyageTeamId;
+ }
+
+ if (agendaId) {
+ const agenda = await prisma.agenda.findUnique({
+ where: {
+ id: agendaId,
+ },
+ select: {
+ teamMeeting: {
+ select: {
+ voyageTeamId: true,
+ },
+ },
+ },
+ });
+ if (!agenda) {
+ throw new NotFoundException(
+ `Team Meeting Agenda (id:${agendaId}) not found`,
+ );
+ }
+
+ meetingOrAgendaTeamId = agenda.teamMeeting.voyageTeamId;
+ }
+
+ if (user.roles.includes("admin")) return;
+
+ if (!user.roles.includes("voyager")) {
+ throw new ForbiddenException(
+ "Invalid user role for Sprint Meeting access control",
+ );
+ }
+
+ if (!voyagerTeamIds.includes(meetingOrAgendaTeamId!)) {
+ throw new ForbiddenException(
+ "Sprint Meeting access control: You can only manage your own project features.",
+ );
+ }
+};
diff --git a/src/ability/prisma-generated-types.ts b/src/ability/prisma-generated-types.ts
index e8519221..b40cdda2 100644
--- a/src/ability/prisma-generated-types.ts
+++ b/src/ability/prisma-generated-types.ts
@@ -9,6 +9,9 @@ import {
TeamTechStackItem,
TeamResource,
ProjectFeature,
+ Sprint,
+ TeamMeeting,
+ Agenda,
} from "@prisma/client";
export type PrismaSubjects = Subjects<{
@@ -20,4 +23,6 @@ export type PrismaSubjects = Subjects<{
TeamTechStackItem: TeamTechStackItem;
Resource: TeamResource;
Feature: ProjectFeature;
+ Sprint: Sprint;
+ SprintMeetingOrAgenda: TeamMeeting | Agenda;
}>;
diff --git a/src/sprints/sprints.controller.ts b/src/sprints/sprints.controller.ts
index 5e7fb30d..59bf026d 100644
--- a/src/sprints/sprints.controller.ts
+++ b/src/sprints/sprints.controller.ts
@@ -39,6 +39,7 @@ import {
import {
BadRequestErrorResponse,
ConflictErrorResponse,
+ ForbiddenErrorResponse,
NotFoundErrorResponse,
UnauthorizedErrorResponse,
} from "../global/responses/errors";
@@ -56,9 +57,11 @@ export class SprintsController {
constructor(private readonly sprintsService: SprintsService) {}
// dev and admin purpose
+ @CheckAbilities({ action: Action.Manage, subject: "all" })
@Get()
@ApiOperation({
- summary: "gets all the voyages and sprints details in the database",
+ summary:
+ "[Roles:Admin] gets all the voyages and sprints details in the database",
})
@ApiResponse({
status: HttpStatus.OK,
@@ -68,16 +71,21 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
getVoyagesAndSprints() {
return this.sprintsService.getVoyagesAndSprints();
}
- @Get("teams/:teamId")
@ApiOperation({
- summary: "gets all the voyages and sprints given a teamId",
+ summary:
+ "[Permissions: Own Team] gets all the voyages and sprints given a teamId",
description: "returns all the sprint dates of a particular team",
})
@ApiResponse({
@@ -92,24 +100,30 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "teamId",
description: "Voyage team Id",
required: true,
example: 1,
})
- getSprintDatesByTeamId(@Param("teamId", ParseIntPipe) teamId: number) {
- return this.sprintsService.getSprintDatesByTeamId(teamId);
+ @CheckAbilities({ action: Action.Read, subject: "Sprint" })
+ @Get("teams/:teamId")
+ getSprintDatesByTeamId(
+ @Request() req: CustomRequest,
+ @Param("teamId", ParseIntPipe) teamId: number,
+ ) {
+ return this.sprintsService.getSprintDatesByTeamId(teamId, req);
}
-
- // TODO: this route and most routes here will only be available to team member
- // To be added with authorization
- @Get("meetings/:meetingId")
@ApiOperation({
- summary: "gets meeting detail given meeting ID",
+ summary: "[Permissions: Own Team] gets meeting detail given meeting ID",
description:
"returns meeting details such as title, meeting time, meeting link, notes, agenda, meeting forms. Everything needed to populate the meeting page.",
})
@@ -125,22 +139,32 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "meetingId",
required: true,
description: "voyage team Meeting ID (TeamMeeting/id)",
example: 1,
})
- getMeetingById(@Param("meetingId", ParseIntPipe) meetingId: number) {
- return this.sprintsService.getMeetingById(meetingId);
+ @CheckAbilities({ action: Action.Read, subject: "SprintMeetingOrAgenda" })
+ @Get("meetings/:meetingId")
+ getMeetingById(
+ @Request() req: CustomRequest,
+ @Param("meetingId", ParseIntPipe) meetingId: number,
+ ) {
+ return this.sprintsService.getMeetingById(meetingId, req);
}
- @Post(":sprintNumber/teams/:teamId/meetings")
@ApiOperation({
- summary: "Creates a sprint meeting given a sprint number and team Id",
+ summary:
+ "[Permissions: Own Team] Creates a sprint meeting given a sprint number and team Id",
description: "Returns meeting details",
})
@ApiResponse({
@@ -167,9 +191,14 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "sprintNumber",
required: true,
@@ -182,7 +211,10 @@ export class SprintsController {
description: "voyage team ID",
example: 1,
})
+ @CheckAbilities({ action: Action.Create, subject: "SprintMeetingOrAgenda" })
+ @Post(":sprintNumber/teams/:teamId/meetings")
createTeamMeeting(
+ @Request() req: CustomRequest,
@Param("sprintNumber", ParseIntPipe) sprintNumber: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Body(ValidationPipe) createTeamMeetingDto: CreateTeamMeetingDto,
@@ -191,12 +223,12 @@ export class SprintsController {
teamId,
sprintNumber,
createTeamMeetingDto,
+ req,
);
}
- @Patch("meetings/:meetingId")
@ApiOperation({
- summary: "Updates a meeting given a meeting ID",
+ summary: "[Permissions: Own Team] Updates a meeting given a meeting ID",
description: "Updates meeting detail, including link, time, notes",
})
@ApiResponse({
@@ -211,28 +243,36 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "meetingId",
required: true,
description: "voyage team meeting ID",
example: 1,
})
+ @CheckAbilities({ action: Action.Update, subject: "SprintMeetingOrAgenda" })
+ @Patch("meetings/:meetingId")
updateTeamMeeting(
+ @Request() req: CustomRequest,
@Param("meetingId", ParseIntPipe) meetingId: number,
@Body(ValidationPipe) updateTeamMeetingDto: UpdateTeamMeetingDto,
) {
return this.sprintsService.updateTeamMeeting(
meetingId,
updateTeamMeetingDto,
+ req,
);
}
- @Post("meetings/:meetingId/agendas")
@ApiOperation({
- summary: "Adds an agenda item given meeting ID",
+ summary: "[Permissions: Own Team] Adds an agenda item given meeting ID",
description: "returns agenda item details.",
})
@ApiResponse({
@@ -247,28 +287,37 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "meetingId",
required: true,
description: "voyage team meeting ID",
example: 1,
})
+ @CheckAbilities({ action: Action.Create, subject: "SprintMeetingOrAgenda" })
+ @Post("meetings/:meetingId/agendas")
addMeetingAgenda(
+ @Request() req: CustomRequest,
@Param("meetingId", ParseIntPipe) meetingId: number,
@Body(ValidationPipe) createAgendaDto: CreateAgendaDto,
) {
return this.sprintsService.createMeetingAgenda(
meetingId,
createAgendaDto,
+ req,
);
}
- @Patch("agendas/:agendaId")
@ApiOperation({
- summary: "Updates an agenda item given an agenda ID",
+ summary:
+ "[Permissions: Own Team] Updates an agenda item given an agenda ID",
description: "returns updated agenda item details.",
})
@ApiResponse({
@@ -283,28 +332,37 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "agendaId",
required: true,
description: "agenda ID",
example: 1,
})
+ @CheckAbilities({ action: Action.Update, subject: "SprintMeetingOrAgenda" })
+ @Patch("agendas/:agendaId")
updateMeetingAgenda(
+ @Request() req: CustomRequest,
@Param("agendaId", ParseIntPipe) agendaId: number,
@Body(ValidationPipe) updateAgendaDto: UpdateAgendaDto,
) {
return this.sprintsService.updateMeetingAgenda(
agendaId,
updateAgendaDto,
+ req,
);
}
- @Delete("agendas/:agendaId")
@ApiOperation({
- summary: "Deletes an agenda item given agenda ID",
+ summary:
+ "[Permissions: Own Team] Deletes an agenda item given agenda ID",
description: "returns deleted agenda item detail.",
})
@ApiResponse({
@@ -319,23 +377,32 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "agendaId",
required: true,
description: "agenda ID",
example: 1,
})
- deleteMeetingAgenda(@Param("agendaId", ParseIntPipe) agendaId: number) {
- return this.sprintsService.deleteMeetingAgenda(agendaId);
+ @CheckAbilities({ action: Action.Delete, subject: "SprintMeetingOrAgenda" })
+ @Delete("agendas/:agendaId")
+ deleteMeetingAgenda(
+ @Request() req: CustomRequest,
+ @Param("agendaId", ParseIntPipe) agendaId: number,
+ ) {
+ return this.sprintsService.deleteMeetingAgenda(agendaId, req);
}
- @Post("meetings/:meetingId/forms/:formId")
@ApiOperation({
summary:
- "Adds sprint reviews or sprint planning section to the meeting",
+ "[Permissions: Own Team] Adds sprint reviews or sprint planning section to the meeting",
description:
"This creates a record which stores all the responses for this particular forms
" +
'This should only work if the form type is "meeting"
' +
@@ -360,9 +427,14 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "meetingId",
required: true,
@@ -375,16 +447,23 @@ export class SprintsController {
description: "form ID",
example: 1,
})
+ @CheckAbilities({ action: Action.ManageSprintForms, subject: "Sprint" })
+ @Post("meetings/:meetingId/forms/:formId")
addMeetingFormResponse(
+ @Request() req: CustomRequest,
@Param("meetingId", ParseIntPipe) meetingId: number,
@Param("formId", ParseIntPipe) formId: number,
) {
- return this.sprintsService.addMeetingFormResponse(meetingId, formId);
+ return this.sprintsService.addMeetingFormResponse(
+ meetingId,
+ formId,
+ req,
+ );
}
- @Get("meetings/:meetingId/forms/:formId")
@ApiOperation({
- summary: "Gets a form given meeting ID and formId",
+ summary:
+ "[Permissions: Own Team] Gets a form given meeting ID and formId",
description: "returns the form, including questions and responses",
})
@ApiResponse({
@@ -404,9 +483,14 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "meetingId",
required: true,
@@ -419,6 +503,8 @@ export class SprintsController {
description: "form ID",
example: 1,
})
+ @CheckAbilities({ action: Action.ManageSprintForms, subject: "Sprint" })
+ @Get("meetings/:meetingId/forms/:formId")
getMeetingFormQuestionsWithResponses(
@Param("meetingId", ParseIntPipe) meetingId: number,
@Param("formId", ParseIntPipe) formId: number,
@@ -431,9 +517,9 @@ export class SprintsController {
);
}
- @Patch("meetings/:meetingId/forms/:formId")
@ApiOperation({
- summary: "Updates a form given meeting ID and formId",
+ summary:
+ "[Permissions: Own Team] Updates a form given meeting ID and formId",
description:
"Returns the updated form, including questions and responses
" +
"A sample body
" +
@@ -466,9 +552,14 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiParam({
name: "meetingId",
required: true,
@@ -481,7 +572,10 @@ export class SprintsController {
description: "form ID",
example: 1,
})
+ @CheckAbilities({ action: Action.ManageSprintForms, subject: "Sprint" })
+ @Patch("meetings/:meetingId/forms/:formId")
updateMeetingFormResponse(
+ @Request() req: CustomRequest,
@Param("meetingId", ParseIntPipe) meetingId: number,
@Param("formId", ParseIntPipe) formId: number,
@Body(new FormInputValidationPipe())
@@ -491,12 +585,14 @@ export class SprintsController {
meetingId,
formId,
updateMeetingFormResponse,
+ req,
);
}
+ @CheckAbilities({ action: Action.ManageSprintForms, subject: "Sprint" })
@Post("check-in")
@ApiOperation({
- summary: "Submit end of sprint check in form",
+ summary: "[Permissions: Own Team] Submit end of sprint check in form",
description:
"Inputs (choiceId, text, boolean, number are all optional),
" +
"depends on the question type, but AT LEAST ONE of them must be present,
" +
@@ -537,9 +633,14 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiResponse({
status: HttpStatus.CONFLICT,
description: "User has already submitted a check in for that sprint.",
@@ -577,9 +678,14 @@ export class SprintsController {
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
- description: "User is not logged in or doesn't have admin access",
+ description: "unauthorized access - user is not logged in",
type: UnauthorizedErrorResponse,
})
+ @ApiResponse({
+ status: HttpStatus.FORBIDDEN,
+ description: "forbidden - user does not have the required permission",
+ type: ForbiddenErrorResponse,
+ })
@ApiQuery({
name: "teamId",
required: false,
diff --git a/src/sprints/sprints.service.ts b/src/sprints/sprints.service.ts
index 9bd9dadf..828bd4a8 100644
--- a/src/sprints/sprints.service.ts
+++ b/src/sprints/sprints.service.ts
@@ -16,6 +16,8 @@ import { GlobalService } from "../global/global.service";
import { FormTitles } from "../global/constants/formTitles";
import { CustomRequest } from "../global/types/CustomRequest";
import { CheckinQueryDto } from "./dto/get-checkin-form-response";
+import { manageOwnVoyageTeamWithIdParam } from "../ability/conditions/voyage-teams.ability";
+import { manageOwnTeamMeetingOrAgendaById } from "../ability/conditions/meetingOrAgenda.ability";
@Injectable()
export class SprintsService {
@@ -26,7 +28,7 @@ export class SprintsService {
) {}
// this checks if the form with the given formId is of formType = "meeting"
- private isMeetingForm = async (formId) => {
+ private isMeetingForm = async (formId: number) => {
const form = await this.prisma.form.findUnique({
where: {
id: formId,
@@ -100,7 +102,8 @@ export class SprintsService {
});
}
- async getSprintDatesByTeamId(teamId: number) {
+ async getSprintDatesByTeamId(teamId: number, req: CustomRequest) {
+ manageOwnVoyageTeamWithIdParam(req.user, teamId);
const teamSprintDates = await this.prisma.voyageTeam.findUnique({
where: {
id: teamId,
@@ -142,13 +145,14 @@ export class SprintsService {
return teamSprintDates.voyage;
}
- async getMeetingById(meetingId: number) {
+ async getMeetingById(meetingId: number, req: CustomRequest) {
const teamMeeting = await this.prisma.teamMeeting.findUnique({
where: {
id: meetingId,
},
select: {
id: true,
+ voyageTeamId: true,
sprint: {
select: {
id: true,
@@ -213,6 +217,7 @@ export class SprintsService {
throw new NotFoundException(
`Meeting with id ${meetingId} not found`,
);
+ manageOwnVoyageTeamWithIdParam(req.user, teamMeeting.voyageTeamId);
return teamMeeting;
}
@@ -226,6 +231,7 @@ export class SprintsService {
dateTime,
notes,
}: CreateTeamMeetingDto,
+ req: CustomRequest,
) {
const sprintId = await this.findSprintIdBySprintNumber(
teamId,
@@ -237,6 +243,8 @@ export class SprintsService {
);
}
+ manageOwnVoyageTeamWithIdParam(req.user, teamId);
+
// check if the sprint already has a meeting.
// This is temporary just remove this block when the app supports multiple meeting per sprint
const isMeetingExist = await this.prisma.teamMeeting.findFirst({
@@ -263,7 +271,6 @@ export class SprintsService {
},
});
}
-
async updateTeamMeeting(
meetingId: number,
{
@@ -273,91 +280,88 @@ export class SprintsService {
dateTime,
notes,
}: UpdateTeamMeetingDto,
+ req: CustomRequest,
) {
- try {
- const updatedMeeting = await this.prisma.teamMeeting.update({
- where: {
- id: meetingId,
- },
- data: {
- title,
- description,
- meetingLink,
- dateTime,
- notes,
- },
- });
- return updatedMeeting;
- } catch (e) {
- if (e.code === "P2025") {
- throw new NotFoundException(`Invalid meetingId: ${meetingId}`);
- }
- }
+ await manageOwnTeamMeetingOrAgendaById({ user: req.user, meetingId });
+
+ return this.prisma.teamMeeting.update({
+ where: {
+ id: meetingId,
+ },
+ data: {
+ title,
+ description,
+ meetingLink,
+ dateTime,
+ notes,
+ },
+ });
}
async createMeetingAgenda(
meetingId: number,
{ title, description, status }: CreateAgendaDto,
+ req: CustomRequest,
) {
- try {
- const newAgenda = await this.prisma.agenda.create({
- data: {
- teamMeetingId: meetingId,
- title,
- description,
- status,
- },
- });
- return newAgenda;
- } catch (e) {
- if (e.code === "P2003") {
- throw new BadRequestException(
- `Invalid meetingId: ${meetingId}`,
- );
- }
- }
+ await manageOwnTeamMeetingOrAgendaById({ user: req.user, meetingId });
+
+ return this.prisma.agenda.create({
+ data: {
+ teamMeetingId: meetingId,
+ title,
+ description,
+ status,
+ },
+ });
}
async updateMeetingAgenda(
agendaId: number,
{ title, description, status }: UpdateAgendaDto,
+ req: CustomRequest,
) {
- try {
- const updatedMeeting = await this.prisma.agenda.update({
- where: {
- id: agendaId,
- },
- data: {
- title,
- description,
- status,
- },
- });
- return updatedMeeting;
- } catch (e) {
- if (e.code === "P2025") {
- throw new NotFoundException(`Invalid agendaId: ${agendaId}`);
- }
- }
+ await manageOwnTeamMeetingOrAgendaById({ user: req.user, agendaId });
+ return this.prisma.agenda.update({
+ where: {
+ id: agendaId,
+ },
+ data: {
+ title,
+ description,
+ status,
+ },
+ });
}
- async deleteMeetingAgenda(agendaId: number) {
- try {
- return await this.prisma.agenda.delete({
- where: {
- id: agendaId,
- },
- });
- } catch (e) {
- if (e.code === "P2025") {
- throw new NotFoundException(
- `${e.meta.cause} agendaId: ${agendaId}`,
- );
- }
- }
+ async deleteMeetingAgenda(agendaId: number, req: CustomRequest) {
+ await manageOwnTeamMeetingOrAgendaById({ user: req.user, agendaId });
+
+ return this.prisma.agenda.delete({
+ where: {
+ id: agendaId,
+ },
+ });
}
- async addMeetingFormResponse(meetingId: number, formId: number) {
+ async addMeetingFormResponse(
+ meetingId: number,
+ formId: number,
+ req: CustomRequest,
+ ) {
+ const meeting = await this.prisma.teamMeeting.findUnique({
+ where: {
+ id: meetingId,
+ },
+ select: {
+ voyageTeamId: true,
+ },
+ });
+ if (!meeting) {
+ throw new NotFoundException(
+ `Meeting with Id ${meetingId} does not exist.`,
+ );
+ }
+ manageOwnVoyageTeamWithIdParam(req.user, meeting.voyageTeamId);
if (await this.isMeetingForm(formId)) {
try {
const formResponseMeeting =
@@ -366,6 +370,14 @@ export class SprintsService {
formId,
meetingId,
},
+ select: {
+ id: true,
+ meeting: {
+ select: {
+ voyageTeamId: true,
+ },
+ },
+ },
});
const updatedFormResponse =
await this.prisma.formResponseMeeting.update({
@@ -391,12 +403,9 @@ export class SprintsService {
`FormId: ${formId} does not exist.`,
);
}
- if (e.meta["field_name"].includes("meetingId")) {
- throw new BadRequestException(
- `MeetingId: ${meetingId} does not exist.`,
- );
- }
}
+
+ throw e;
}
}
}
@@ -410,6 +419,9 @@ export class SprintsService {
where: {
id: meetingId,
},
+ select: {
+ voyageTeamId: true,
+ },
});
if (!meeting)
@@ -417,6 +429,8 @@ export class SprintsService {
`Meeting with Id ${meetingId} does not exist.`,
);
+ manageOwnVoyageTeamWithIdParam(req.user, meeting.voyageTeamId);
+
const formResponseMeeting =
await this.prisma.formResponseMeeting.findUnique({
where: {
@@ -491,7 +505,22 @@ export class SprintsService {
meetingId: number,
formId: number,
responses: UpdateMeetingFormResponseDto,
+ req: CustomRequest,
) {
+ const meeting = await this.prisma.teamMeeting.findUnique({
+ where: {
+ id: meetingId,
+ },
+ select: {
+ voyageTeamId: true,
+ },
+ });
+ if (!meeting) {
+ throw new NotFoundException(
+ `Meeting with Id ${meetingId} does not exist.`,
+ );
+ }
+ manageOwnVoyageTeamWithIdParam(req.user, meeting.voyageTeamId);
// at this stage, it is unclear what id the frontend is able to send,
// if they are able to send the fromResponseMeeting ID, then we won't need this step
const formResponseMeeting =
diff --git a/test/sprints.e2e-spec.ts b/test/sprints.e2e-spec.ts
index 915095af..6506a6f3 100644
--- a/test/sprints.e2e-spec.ts
+++ b/test/sprints.e2e-spec.ts
@@ -9,13 +9,13 @@ import { CreateAgendaDto } from "src/sprints/dto/create-agenda.dto";
import { toBeOneOf } from "jest-extended";
import * as cookieParser from "cookie-parser";
import { FormTitles } from "../src/global/constants/formTitles";
+import { CASLForbiddenExceptionFilter } from "../src/exception-filters/casl-forbidden-exception.filter";
expect.extend({ toBeOneOf });
describe("Sprints Controller (e2e)", () => {
let app: INestApplication;
let prisma: PrismaService;
- let accessToken: any;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
@@ -26,6 +26,7 @@ describe("Sprints Controller (e2e)", () => {
app = moduleFixture.createNestApplication();
prisma = moduleFixture.get(PrismaService);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
+ app.useGlobalFilters(new CASLForbiddenExceptionFilter());
app.use(cookieParser());
await app.init();
});
@@ -35,21 +36,16 @@ describe("Sprints Controller (e2e)", () => {
await app.close();
});
- beforeEach(async () => {
- await loginAndGetTokens(
- "jessica.williamson@gmail.com",
- "password",
- app,
- ).then((tokens) => {
- accessToken = tokens.access_token;
- });
- });
-
describe("GET /voyages/sprints - gets all voyage and sprints data", () => {
it("should return 200 if fetching all voyage and sprints data", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
return request(app.getHttpServer())
.get(`/voyages/sprints`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -90,16 +86,44 @@ describe("Sprints Controller (e2e)", () => {
it("should return 401 if user is not logged in", async () => {
return request(app.getHttpServer())
.get(`/voyages/sprints`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+ it("should return 403 if a non-admin user tries to access it", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "leo.rowe@outlook.com",
+ "password",
+ app,
+ );
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a non-voyager tries to access it", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
});
describe("GET /voyages/sprints/teams/:teamId - gets a team's sprint dates", () => {
it("should return 200 if fetching all the sprint dates of a particular team was successful", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const teamId = 1;
return request(app.getHttpServer())
.get(`/voyages/sprints/teams/${teamId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -129,10 +153,15 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if teamId is invalid", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const teamId = 9999;
return request(app.getHttpServer())
.get(`/voyages/sprints/teams/${teamId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(404);
});
@@ -140,16 +169,46 @@ describe("Sprints Controller (e2e)", () => {
const teamId = 1;
return request(app.getHttpServer())
.get(`/voyages/sprints/teams/${teamId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+ it("should return 403 if a user of other team tries to access", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const teamId = 1;
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints/teams/${teamId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a non-voyager tries to access it", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const teamId = 1;
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints/teams/${teamId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
});
describe("GET /voyages/sprints/meetings/:meetingId - gets details for one meeting", () => {
it("should return 200 if fetching meeting details was successful", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 1;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -228,10 +287,15 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if meetingId is invalid", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 9999;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(404);
});
@@ -239,16 +303,46 @@ describe("Sprints Controller (e2e)", () => {
const meetingId = 1;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+ it("should return 403 if a non-voyager tries to access it", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a user of other team tries to access the meeting", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
});
describe("PATCH /voyages/sprints/meetings/:meetingId - updates details for a meeting", () => {
it("should return 200 if meeting details was successfully updated", async () => {
const meetingId = 1;
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: "Test title",
dateTime: "2024-02-29T17:17:50.100Z",
@@ -288,19 +382,62 @@ describe("Sprints Controller (e2e)", () => {
const meetingId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+ it("should return 403 if a non-voyager tries to access it", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a user of other team tries to update the meeting", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+
+ it("should return 404 if meetingId is invalid", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
+ const meetingId = 9999;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/meetings/${meetingId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(404);
+ });
});
describe("POST /voyages/sprints/:sprintNumber/teams/:teamId/meetings - creates new meeting for a sprint", () => {
it("should return 201 if creating sprint meeting details was successful", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const teamId = 1;
const sprintNumber = 4;
return request(app.getHttpServer())
.post(
`/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: FormTitles.sprintPlanning,
description: "This is a meeting description.",
@@ -338,13 +475,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 409 if trying to create a meeting that already exists for sprint", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const teamId = 1;
const sprintNumber = 4;
return request(app.getHttpServer())
.post(
`/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: FormTitles.sprintPlanning,
description: "This is a meeting description.",
@@ -356,13 +498,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if teamId not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const teamId = 999;
const sprintNumber = 5;
return request(app.getHttpServer())
.post(
`/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: FormTitles.sprintPlanning,
description: "This is a meeting description.",
@@ -374,13 +521,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 400 for bad request (title is Number)", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const teamId = 1;
const sprintNumber = 5;
return request(app.getHttpServer())
.post(
`/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: 1, //bad request - title should be string
dateTime: "2024-03-01T23:11:20.271Z",
@@ -392,17 +544,61 @@ describe("Sprints Controller (e2e)", () => {
it("should return 401 if user is not logged in", async () => {
const teamId = 1;
- const sprintNumber = 5;
+ const sprintNumber = 4;
return request(app.getHttpServer())
.post(
`/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to create a sprint meeting", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const teamId = 1;
+ const sprintNumber = 4;
+ return request(app.getHttpServer())
+ .post(
+ `/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
+ )
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a user of other team tries to create the meetings", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const teamId = 1;
+ const sprintNumber = 4;
+ return request(app.getHttpServer())
+ .post(
+ `/voyages/sprints/${sprintNumber}/teams/${teamId}/meetings`,
+ )
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: FormTitles.sprintPlanning,
+ description: "This is a meeting description.",
+ dateTime: "2024-03-01T23:11:20.271Z",
+ meetingLink: "samplelink.com/meeting1234",
+ notes: "Notes for the meeting",
+ })
+ .expect(403);
+ });
});
describe("POST /voyages/sprints/meetings/:meetingId/agendas - creates a new meeting agenda", () => {
it("should return 201 if create new agenda was successful", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 1;
const createAgendaDto: CreateAgendaDto = {
title: "Test agenda 3",
@@ -411,7 +607,7 @@ describe("Sprints Controller (e2e)", () => {
};
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/agendas`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send(createAgendaDto)
.expect(201)
.expect((res) => {
@@ -440,12 +636,48 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 400 if meetingId is String", async () => {
- const meetingId = " ";
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
+ const meetingId = "a";
+ return request(app.getHttpServer())
+ .post(`/voyages/sprints/meetings/${meetingId}/agendas`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Contribute to the agenda!",
+ description:
+ "To get started, click the Add Topic button...",
+ })
+ .expect(400);
+ });
+ it("should return 400 if description is missing", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/agendas`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: "Contribute to the agenda!",
+ })
+ .expect(400);
+ });
+ it("should return 400 if title is missing", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .post(`/voyages/sprints/meetings/${meetingId}/agendas`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
description:
"To get started, click the Add Topic button...",
})
@@ -456,16 +688,59 @@ describe("Sprints Controller (e2e)", () => {
const meetingId = 1;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/agendas`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to create an agenda", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .post(`/voyages/sprints/meetings/${meetingId}/agendas`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Contribute to the agenda!",
+ description:
+ "To get started, click the Add Topic button...",
+ })
+ .expect(403);
+ });
+
+ it("should return 403 if a user of other team tries to create an agenda", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ return request(app.getHttpServer())
+ .post(`/voyages/sprints/meetings/${meetingId}/agendas`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Contribute to the agenda!",
+ description:
+ "To get started, click the Add Topic button...",
+ })
+ .expect(403);
+ });
});
- describe("PATCH /voyages/sprints/agendas/:agendaId - supdate an agenda", () => {
+ describe("PATCH /voyages/sprints/agendas/:agendaId - update an agenda", () => {
it("should return 200 if updating the agenda was successful with provided values", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const agendaId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/agendas/${agendaId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: "Title updated",
description: "New agenda",
@@ -498,10 +773,15 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if agendaId is not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const agendaId = 9999;
return request(app.getHttpServer())
.patch(`/voyages/sprints/agendas/${agendaId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
title: "Title updated",
description: "New agenda",
@@ -514,15 +794,57 @@ describe("Sprints Controller (e2e)", () => {
const agendaId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/agendas/${agendaId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to update an agenda", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const agendaId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/agendas/${agendaId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Title updated",
+ description: "New agenda",
+ status: true,
+ })
+ .expect(403);
+ });
+
+ it("should return 403 if a user of other team tries to update the agenda", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const agendaId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/agendas/${agendaId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Title updated",
+ description: "New agenda",
+ status: true,
+ })
+ .expect(403);
+ });
});
describe("DELETE /voyages/sprints/agendas/:agendaId - deletes specified agenda", () => {
it("should return 200 and delete agenda from database", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const agendaId = 1;
return request(app.getHttpServer())
.delete(`/voyages/sprints/agendas/${agendaId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect((res) => {
expect(res.body).toEqual(
@@ -548,10 +870,15 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if agendaId is not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const agendaId = 9999;
return request(app.getHttpServer())
.delete(`/voyages/sprints/agendas/${agendaId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(404);
});
@@ -559,17 +886,59 @@ describe("Sprints Controller (e2e)", () => {
const agendaId = 1;
return request(app.getHttpServer())
.delete(`/voyages/sprints/agendas/${agendaId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to delete an agenda", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const agendaId = 1;
+ return request(app.getHttpServer())
+ .delete(`/voyages/sprints/agendas/${agendaId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Title updated",
+ description: "New agenda",
+ status: true,
+ })
+ .expect(403);
+ });
+
+ it("should return 403 if a user of other team tries to delete the agenda", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const agendaId = 2;
+ return request(app.getHttpServer())
+ .delete(`/voyages/sprints/agendas/${agendaId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ title: "Title updated",
+ description: "New agenda",
+ status: true,
+ })
+ .expect(403);
+ });
});
describe("POST /voyages/sprints/meetings/:meetingId/forms/:formId - creates new meeting form", () => {
it("should return 200 and create new meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 2;
const formId = 1;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(201)
.expect((res) => {
expect(res.body).toEqual(
@@ -596,29 +965,44 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 409 if form already exists for this meeting", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 2;
const formId = 1;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(409);
});
- it("should return 400 if meetingId is not found", async () => {
+ it("should return 404 if meetingId is not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 9999;
const formId = 1;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
- .expect(400);
+ .set("Cookie", [access_token, refresh_token])
+ .expect(404);
});
it("should return 400 if formId is not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 1;
const formId = 999;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(400);
});
@@ -627,16 +1011,49 @@ describe("Sprints Controller (e2e)", () => {
const formId = 999;
return request(app.getHttpServer())
.post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to create a meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a user of other team tries to create a meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .post(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
});
describe("GET /voyages/sprints/meetings/:meetingId/forms/:formId - gets meeting form", () => {
it("should return 200 if the meeting form was successfully fetched #with responses", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 2;
const formId = 1;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect((res) => {
expect(res.body).toEqual(
@@ -670,20 +1087,30 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if meetingId is not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 9999;
const formId = 1;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(404);
});
it("should return 400 if formId is is not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 2;
const formId = 9999;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(400);
});
@@ -692,17 +1119,50 @@ describe("Sprints Controller (e2e)", () => {
const formId = 999;
return request(app.getHttpServer())
.get(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to create a meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
+ it("should return 403 if a user of other team tries to create a meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .get(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .expect(403);
+ });
});
describe("PATCH /voyages/sprints/meetings/:meetingId/forms/:formId - updates a meeting form", () => {
it("should return 200 if successfully create a meeting form response", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 1;
const formId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
responses: [
{
@@ -738,11 +1198,16 @@ describe("Sprints Controller (e2e)", () => {
return expect(response[0].questionId).toEqual(1);
});
it("should return 200 if successfully update a meeting form response", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 1;
const formId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
responses: [
{
@@ -779,11 +1244,16 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 400 if formId is a string", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 2;
const formId = "Bad request";
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
responses: [
{
@@ -798,12 +1268,42 @@ describe("Sprints Controller (e2e)", () => {
.expect(400);
});
+ it("should return 404 if meeting id not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
+ const meetingId = 99999;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ responses: [
+ {
+ questionId: 1,
+ optionChoiceId: 1,
+ text: "Team member x landed a job this week.",
+ boolean: true,
+ number: 1,
+ },
+ ],
+ })
+ .expect(404);
+ });
+
it("should return 400 if responses in the body is not an array", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const meetingId = 1;
const formId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
responses: {
questionId: 1,
@@ -821,8 +1321,53 @@ describe("Sprints Controller (e2e)", () => {
const formId = 999;
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Authorization", `${undefined}`)
.expect(401);
});
+
+ it("should return 403 if a non-voyager tries to update a meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "not_in_voyage@example.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ responses: [
+ {
+ questionId: 1,
+ text: "Team member x landed a job this week.",
+ },
+ ],
+ })
+ .expect(403);
+ });
+
+ it("should return 403 if a user of other team tries to update a meeting form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "JosoMadar@dayrep.com",
+ "password",
+ app,
+ );
+ const meetingId = 1;
+ const formId = 1;
+ return request(app.getHttpServer())
+ .patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
+ .set("Cookie", [access_token, refresh_token])
+ .send({
+ responses: [
+ {
+ questionId: 1,
+ text: "Team member x landed a job this week.",
+ },
+ ],
+ })
+ .expect(403);
+ });
});
describe("POST /voyages/sprints/check-in - submit sprint check in form", () => {
@@ -847,13 +1392,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 201 if successfully submitted a check in form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const responsesBefore = await prisma.response.count();
const responseGroupBefore = await prisma.responseGroup.count();
const checkinsBefore = await prisma.formResponseCheckin.count();
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 4, // voyageTeamMemberId 1 is already in the seed
sprintId: 1,
@@ -887,13 +1437,18 @@ describe("Sprints Controller (e2e)", () => {
expect(checkinsAfter).toEqual(checkinsBefore + 1);
});
it("should return 400 for invalid inputs", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const responsesBefore = await prisma.response.count();
const responseGroupBefore = await prisma.responseGroup.count();
const checkinsBefore = await prisma.formResponseCheckin.count();
// missing voyageTeamMemberId
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
sprintId: 1,
responses: [
@@ -908,7 +1463,7 @@ describe("Sprints Controller (e2e)", () => {
// missing sprintId"
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -923,7 +1478,7 @@ describe("Sprints Controller (e2e)", () => {
// missing responses
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
sprintId: 1,
@@ -933,7 +1488,7 @@ describe("Sprints Controller (e2e)", () => {
// missing questionId in responses - response validation pipe
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -947,7 +1502,7 @@ describe("Sprints Controller (e2e)", () => {
// missing input in responses - response validation pipe
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -961,7 +1516,7 @@ describe("Sprints Controller (e2e)", () => {
// wrong response input types
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -975,7 +1530,7 @@ describe("Sprints Controller (e2e)", () => {
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -989,7 +1544,7 @@ describe("Sprints Controller (e2e)", () => {
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -1003,7 +1558,7 @@ describe("Sprints Controller (e2e)", () => {
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 1,
responses: [
@@ -1041,9 +1596,14 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 409 if user has already submitted the check in form for the same sprint", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 4,
sprintId: 1,
@@ -1060,7 +1620,7 @@ describe("Sprints Controller (e2e)", () => {
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 4,
sprintId: 1,
@@ -1082,9 +1642,14 @@ describe("Sprints Controller (e2e)", () => {
expect(checkinsAfter).toEqual(checkinsBefore);
});
it("should return 400 if the user doesnot belong to the voyage team", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
await request(app.getHttpServer())
.post(sprintCheckinUrl)
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.send({
voyageTeamMemberId: 5,
sprintId: 1,
@@ -1169,24 +1734,19 @@ describe("Sprints Controller (e2e)", () => {
responseGroup: expect.objectContaining(responseGroupShape),
};
- beforeEach(async () => {
- await loginAndGetTokens(
+ it("should return 200 if voyageNumber key's value successfully returns a check in form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
"jessica.williamson@gmail.com",
"password",
app,
- ).then((tokens) => {
- accessToken = tokens.access_token;
- });
- });
-
- it("should return 200 if voyageNumber key's value successfully returns a check in form", async () => {
+ );
const key = "voyageNumber";
const val = "46";
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key]: val })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -1199,13 +1759,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 200 if teamId key's value successfully returns a check in form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const key = "teamId";
const val = "1";
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key]: val })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -1218,13 +1783,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 200 if sprintNumber key's value successfully returns a check in form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const key = ["sprintNumber", "voyageNumber"];
const val = [1, "46"];
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key[0]]: val[0], [key[1]]: val[1] })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -1237,6 +1807,11 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 200 if userId key's value successfully returns a check in form", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const key = "userId";
const user = await prisma.voyageTeamMember.findFirst({
where: {
@@ -1253,7 +1828,7 @@ describe("Sprints Controller (e2e)", () => {
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key]: val })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
@@ -1266,12 +1841,17 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 400 if query params are invalid", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const key = "teamsId";
const val = "1";
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key]: val })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(400);
});
@@ -1282,13 +1862,18 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return an empty array if check in form not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
// TODO: create user with no check ins
const key = "teamId";
const val = "5";
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key]: val })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(200)
.expect((res) => {
expect(res.body).toEqual(expect.arrayContaining([]));
@@ -1296,12 +1881,17 @@ describe("Sprints Controller (e2e)", () => {
});
it("should return 404 if query not found", async () => {
+ const { access_token, refresh_token } = await loginAndGetTokens(
+ "jessica.williamson@gmail.com",
+ "password",
+ app,
+ );
const key = "teamId";
const val = "9999";
return request(app.getHttpServer())
.get(sprintCheckinUrl)
.query({ [key]: val })
- .set("Cookie", accessToken)
+ .set("Cookie", [access_token, refresh_token])
.expect(404);
});
});