From 215406ec70060b464c70bb0f73e82f5758f5e95d Mon Sep 17 00:00:00 2001 From: Alberto Baroso <35893959+AlbertoBaroso@users.noreply.github.com> Date: Fri, 12 Apr 2024 19:02:31 +0200 Subject: [PATCH 01/57] Availability: controller, service, entity, tests (#21) * Feature/rectuitment session (#20) * feat: session service, controller, entity * feat: update recruitment-session: service, controller, entity * test: mock recruitment session service, insert data mock * fix: relative import of recruitment-session from shared folder * fix: recruitment-session service Delete test * fix: removed lastModified from UpdateRecruitmentSessionDto * fix: ability check on recruitment session creation * feat: check if recruitment session has pending interviews before deleting it * feat: check for conflicts and consistency when updating a recruitment session state * fix: check ability for update recruitment session * refactor: removed unused imports in recruitment-session.controller.ts * fix: use const for unchanged variable in createRecruitmentSession service method * refactor: removed unused code in creatre-recruitment-session.dto.ts * fix: updated Date[] in create and update Recruitment session DTOs * test: Recruitment Session Controller tests * refactor: removed unused imports * feat: Recruitment session module * fix: import of RecruitmentSessionState in recruitment-session.service.ts * fix: find function recreuitment session * fix: set findBy functions * fix: adjustments about array of recruitment session * test: create recruitment session * add: test create RS on service.spec * fix: changed array into scalar value in findBy and findActive --------- Co-authored-by: Alberto Baroso * feat: created availability module * fix: mock data timestamp for midnight * fix: added http exceptions and removed unnecessary request fields * test: Initial tests for availability controller * test: Initial tests for availability service * fix: relationship between timeslot and availability entities * fix: removed relationship fields in entities * test: CRUD unit tests for availability * RecruitmentSession: controller, service, entity, tests (#15) RecruitmentSessionController: - findActive: Retrieve the active recruitment session if it exists. - createRecruitmentSession - updateRecruitmentSession - deleteRecruitmentSession RecruitmentSessionService: - createRecruitmentSession - findAllRecruitmentSessions - findRecruitmentSessionById - findActiveRecruitmentSession - deletRecruitmentSession - updateRecruitmentSession - sessionHasPendingInterviews: Check if a recruitment session has pending interviews (to be implemented). DTOs: - CreateRecruitmentSessionDTO - UpdateRecruitmentSessionDTO - RecruitmentSessionResponseDTO Tests: - Controller Unit tests: recruitment-session.controller.spec.ts - Service Unit tests: recruitment-session.service.spec.ts Commits: * fix: missing dependencies and imports (#9) * fix: added @joi/date library * fix: added missing useState import fix: removed loading screen when auth token is empty * docs: updated project description, useful links, and contributors in README.md (#10) * feat session: service, controller, entity * feat: update recruitment-session: service, controller, entity * feat: update recruitment-session: service, controller, entity * fix: dependencies in shared/abilities * fix: mock shared -> required/optional fields * fix: mock recruitment session service, insert data mock * fix: relative import of recruitment-session from shared folder * fix: recruitment-session service Delete test * fix: removed lastModified from UpdateRecruitmentSessionDto * fix: ability check on recruitment session creation * feat: check if recruitment session has pending interviews before deleting it * feat: check for conflicts and consistency when updating a recruitment session state * fix: check ability for update recruitment session * refactor: removed unused imports in recruitment-session.controller.ts * fix: use const for unchanged variable in createRecruitmentSession service method * refactor: removed unused code in creatre-recruitment-session.dto.ts * fix: updated Date[] in create and update Recruitment session DTOs * fix: added 'state' to recruitmentSession response DTO * test: Recruitment Session Controller tests * refactor: removed unused imports --------- Co-authored-by: Alberto Baroso * fix: updated imports from shared/recruitment-session * Feature: Rectuitment session module (#17) * SonarCloud Analysis (#18) * feat: setup coverageDirectory and coveragePathIgnorePatterns * ci: added SonarCloud Analysis job in GitHub actions * ci: sonar-project.properties configuration * Simplified workflow, single task, maximum gain --------- Co-authored-by: Vincenzo Pellegrini * fix: removed unused avaiability endpoints * feat: added existance checks and conflict check upon availability creation * feat: return 404 when attempting to delete non-existing availabilities refactor: availability.controller.ts using prettier * fix: Availability authorizations and creation schema * fix!: updated AvailabilityState enum values * test: role abilities on Availability test: validate insert Availability schema * feat: additional checks before deleting availability * test: availability controller unit tests * feat: added findByUserAndTimeSlot in Availability service fix: used Relation as type of fields in Availability entity * fix!: removed unnecessary fields in CreateAvailabilityDto * test: Availability service unit tests * fix!: removed create/delete timeslot endpoint * feat: TimeSlot service generateTimeslots() * test: TimeSlot service generateTimeslots() * feat: create recruitment session's timeslots atomically using a transaction * feat: added jest-mock-extended library to auto mock classes * fix: added DbAwareColumn to overcome sqlite column type limitation * fix: apply abilities on TimeSlots * fix: imported missing modules * fix: import Joi in availability controller * test: mock recruitment session for timeslot generation * fix: added coverage exclusions in sonar-project.properties * refactor: format according to prettier rules, reduced code duplication in timeslots.service.spec.ts * feat: User is_board and is_expert flags --------- Co-authored-by: whiitex <89868763+whiitex@users.noreply.github.com> Co-authored-by: Marco De Luca <31864038+markdeluk@users.noreply.github.com> Co-authored-by: whiteOFF <89868763+whiteOFF@users.noreply.github.com> Co-authored-by: Vincenzo Pellegrini Co-authored-by: Mugna0990 <150722467+Mugna0990@users.noreply.github.com> --- api/package.json | 1 + api/src/app.module.ts | 2 + .../applications.controller.spec.ts | 2 +- .../application/applications.service.spec.ts | 8 +- .../availability.controller.spec.ts | 209 +++++++++++++++ .../availability/availability.controller.ts | 135 ++++++++++ api/src/availability/availability.entity.ts | 36 +++ api/src/availability/availability.module.ts | 19 ++ .../availability/availability.service.spec.ts | 178 +++++++++++++ api/src/availability/availability.service.ts | 81 ++++++ .../availability/create-availability.dto.ts | 6 + api/src/mocks/data-sources.ts | 59 +++++ api/src/mocks/data.ts | 56 +++- api/src/mocks/services.ts | 16 +- .../recruitment-session.controller.spec.ts | 21 +- .../recruitment-session.controller.ts | 19 +- .../recruitment-session.module.ts | 7 +- .../recruitment-session.service.spec.ts | 78 +++++- .../recruitment-session.service.ts | 42 ++- api/src/timeslots/create-timeslot.dto.ts | 2 +- api/src/timeslots/timeslot.entity.ts | 12 +- .../timeslots/timeslots.controller.spec.ts | 83 ------ api/src/timeslots/timeslots.controller.ts | 85 +----- api/src/timeslots/timeslots.service.spec.ts | 148 ++++++++++- api/src/timeslots/timeslots.service.ts | 56 +++- api/src/users/user.entity.ts | 12 +- api/src/users/users.controller.spec.ts | 4 + api/src/users/users.controller.ts | 2 + api/src/users/users.e2e-spec.ts | 10 + api/src/utils/database.ts | 33 +++ api/src/utils/db-aware-column.ts | 18 ++ api/test/app.e2e-spec.ts | 13 +- pnpm-lock.yaml | 241 +----------------- shared/src/abilities.ts | 3 +- shared/src/availability.spec.ts | 98 +++++-- shared/src/availability.ts | 30 +-- shared/src/person.ts | 2 + shared/src/recruitment-session.spec.ts | 24 +- shared/src/recruitment-session.ts | 4 +- shared/src/timeslot.ts | 45 +--- sonar-project.properties | 2 +- 41 files changed, 1374 insertions(+), 528 deletions(-) create mode 100644 api/src/availability/availability.controller.spec.ts create mode 100644 api/src/availability/availability.controller.ts create mode 100644 api/src/availability/availability.entity.ts create mode 100644 api/src/availability/availability.module.ts create mode 100644 api/src/availability/availability.service.spec.ts create mode 100644 api/src/availability/availability.service.ts create mode 100644 api/src/availability/create-availability.dto.ts create mode 100644 api/src/mocks/data-sources.ts create mode 100644 api/src/utils/database.ts create mode 100644 api/src/utils/db-aware-column.ts diff --git a/api/package.json b/api/package.json index b4a347b..9dcef79 100644 --- a/api/package.json +++ b/api/package.json @@ -39,6 +39,7 @@ "dotenv": "^16.0.3", "google-auth-library": "^8.7.0", "googleapis": "^118.0.0", + "jest-mock-extended": "^3.0.5", "joi": "^17.7.0", "js-yaml": "^4.1.0", "jwks-rsa": "^3.0.0", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 271ffd0..607aeb4 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -10,6 +10,7 @@ import { APP_GUARD } from '@nestjs/core'; import { JwtGuard } from './authentication/jwt-guard.guard'; import { AuthorizationModule } from './authorization/authorization.module'; import { AuthorizationGuard } from './authorization/authorization.guard'; +import { AvailabilityModule } from './availability/availability.module'; @Module({ imports: [ @@ -34,6 +35,7 @@ import { AuthorizationGuard } from './authorization/authorization.guard'; ApplicationsModule, AuthenticationModule, AuthorizationModule, + AvailabilityModule, RecruitmentSessionModule, TimeSlotsModule, UsersModule, diff --git a/api/src/application/applications.controller.spec.ts b/api/src/application/applications.controller.spec.ts index 1c04510..e445537 100644 --- a/api/src/application/applications.controller.spec.ts +++ b/api/src/application/applications.controller.spec.ts @@ -16,7 +16,7 @@ import { mockMscApplication, updateApplicationDTO, testDate, -} from '@mocks/data'; +} from 'src/mocks/data'; import { BadRequestException, ConflictException, diff --git a/api/src/application/applications.service.spec.ts b/api/src/application/applications.service.spec.ts index db7ab56..976cfeb 100644 --- a/api/src/application/applications.service.spec.ts +++ b/api/src/application/applications.service.spec.ts @@ -4,8 +4,8 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { Application } from './application.entity'; import { ApplicationState, ApplicationType } from '@hkrecruitment/shared'; import { UsersService } from '../users/users.service'; -import { mockedRepository } from '@mocks/repositories'; -import { mockedUsersService } from '@mocks/services'; +import { mockedRepository } from 'src/mocks/repositories'; +import { mockedUsersService } from 'src/mocks/services'; import { applicant, applicationFiles, @@ -20,7 +20,7 @@ import { folderId, today, testDate, -} from '@mocks/data'; +} from 'src/mocks/data'; import { flattenApplication } from './create-application.dto'; import { InternalServerErrorException } from '@nestjs/common'; @@ -204,7 +204,7 @@ describe('ApplicationsService', () => { const applicantId = 'abc123'; const folderId = 'folder_abc123'; const fileId = 'file_abc123'; - const today = '1/1/2023, 24:00:00'; + const today = '1/1/2023, 10:00:00'; let mockApplication, mockCreateApplicationDTO; switch (applicationType) { case ApplicationType.BSC: diff --git a/api/src/availability/availability.controller.spec.ts b/api/src/availability/availability.controller.spec.ts new file mode 100644 index 0000000..f87083d --- /dev/null +++ b/api/src/availability/availability.controller.spec.ts @@ -0,0 +1,209 @@ +import { + mockAvailability, + mockCreateAvailabilityDto, + mockClerk, + mockTimeSlot, + testDate, +} from 'src/mocks/data'; +import { createMock } from '@golevelup/ts-jest'; +import { AvailabilityController } from './availability.controller'; +import { AvailabilityService } from './availability.service'; +import { TestBed } from '@automock/jest'; +import { AuthenticatedRequest } from 'src/authorization/authenticated-request.types'; +import { UsersService } from 'src/users/users.service'; +import { TimeSlotsService } from 'src/timeslots/timeslots.service'; +import { Action } from '@hkrecruitment/shared'; +import { createMockAbility } from '@hkrecruitment/shared/abilities.spec'; + +describe('AvailabilityController', () => { + let controller: AvailabilityController; + let availabilityService: AvailabilityService; + let userService: UsersService; + let timeslotService: TimeSlotsService; + + /************* Test setup ************/ + + beforeAll(() => { + jest + .spyOn(global, 'Date') + .mockImplementation(() => testDate as unknown as string); + }); + + beforeEach(async () => { + const { unit, unitRef } = TestBed.create(AvailabilityController).compile(); + + controller = unit; + availabilityService = unitRef.get(AvailabilityService); + userService = unitRef.get(UsersService); + timeslotService = unitRef.get(TimeSlotsService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + expect(availabilityService).toBeDefined(); + expect(userService).toBeDefined(); + expect(timeslotService).toBeDefined(); + }); + + // CRUD OPERATIONS + + describe('createAvailability', () => { + it('should allow creating a valid availability', async () => { + const mockRequest = getMockRequest(); + + jest.spyOn(userService, 'findByOauthId').mockResolvedValue(mockClerk); + jest.spyOn(timeslotService, 'findById').mockResolvedValue(mockTimeSlot); + jest + .spyOn(availabilityService, 'createAvailability') + .mockResolvedValue(mockAvailability); + + const result = await controller.createAvailability( + mockCreateAvailabilityDto, + mockRequest, + ); + + expect(result).toEqual(mockAvailability); + }); + + it('should throw an error if the availability is invalid', async () => { + const mockRequest = getMockRequest(); + + jest.spyOn(userService, 'findByOauthId').mockResolvedValue(mockClerk); + jest.spyOn(timeslotService, 'findById').mockResolvedValue(mockTimeSlot); + jest + .spyOn(availabilityService, 'createAvailability') + .mockResolvedValue(undefined); + + await expect( + controller.createAvailability(mockCreateAvailabilityDto, mockRequest), + ).rejects.toThrowError(); + }); + + it('should throw an error if the timeslot does not exist', async () => { + const mockRequest = getMockRequest(); + + jest.spyOn(timeslotService, 'findById').mockResolvedValue(null); + + await expect( + controller.createAvailability(mockCreateAvailabilityDto, mockRequest), + ).rejects.toThrowError('Timeslot not found'); + }); + + it('should throw an error if the user does not exist', async () => { + const mockRequest = getMockRequest(); + + jest.spyOn(timeslotService, 'findById').mockResolvedValue(mockTimeSlot); + jest.spyOn(userService, 'findByOauthId').mockResolvedValue(null); + + await expect( + controller.createAvailability(mockCreateAvailabilityDto, mockRequest), + ).rejects.toThrowError('User not found'); + }); + + it('should throw a conflict error if an availability already exists for the user in the same timeslot', async () => { + const mockRequest = getMockRequest(); + + jest.spyOn(userService, 'findByOauthId').mockResolvedValue(mockClerk); + jest.spyOn(timeslotService, 'findById').mockResolvedValue(mockTimeSlot); + jest + .spyOn(availabilityService, 'findByUserAndTimeSlot') + .mockResolvedValue(mockAvailability); + + await expect( + controller.createAvailability(mockCreateAvailabilityDto, mockRequest), + ).rejects.toThrowError('Availability already exists for this timeslot'); + }); + }); + + // Delete an availability + describe('deleteAvailability', () => { + it('should allow deleting an availability', async () => { + const mockAbility = createMockAbility(({ can }) => { + can(Action.Delete, 'Availability'); + }); + const mockReq = createMock(); + mockReq.user.sub = mockAvailability.user.oauthId; + + jest + .spyOn(availabilityService, 'findById') + .mockResolvedValue(mockAvailability); + jest + .spyOn(availabilityService, 'deleteAvailability') + .mockResolvedValue(mockAvailability); + + await expect( + controller.deleteAvailability( + mockAbility, + mockAvailability.id, + mockReq, + ), + ).resolves.toEqual(mockAvailability); + }); + + it('should throw an error if the user does not have the ability to delete the availability', async () => { + const mockAbility = createMockAbility(({ cannot }) => { + cannot(Action.Delete, 'Availability'); + }); + const mockReq = createMock(); + mockReq.user.sub = '123'; + + jest + .spyOn(availabilityService, 'findById') + .mockResolvedValue(mockAvailability); + await expect( + controller.deleteAvailability( + mockAbility, + mockAvailability.id, + mockReq, + ), + ).rejects.toThrowError('Forbidden'); + }); + + it("should throw an error if the user tries to delete someone else's availability", async () => { + const mockAbility = createMockAbility(({ can }) => { + can(Action.Delete, 'Availability'); + }); + const mockReq = createMock(); + mockReq.user.sub = '345'; + + jest + .spyOn(availabilityService, 'findById') + .mockResolvedValue(mockAvailability); + await expect( + controller.deleteAvailability( + mockAbility, + mockAvailability.id, + mockReq, + ), + ).rejects.toThrowError('Forbidden'); + }); + + it('should throw an error if the availability does not exist', async () => { + const mockAbility = createMockAbility(({ can }) => { + can(Action.Create, 'Availability'); + }); + const mockReq = createMock(); + mockReq.user.sub = '123'; + + jest.spyOn(availabilityService, 'findById').mockResolvedValue(undefined); + + await expect( + controller.deleteAvailability( + mockAbility, + mockAvailability.id, + mockReq, + ), + ).rejects.toThrowError('Not Found'); + }); + + // TODO: Delete an availability that is in use, where the user is optional + // TODO: Delete an availability that is in use, with an existing replacement + // TODO: Delete an availability that is in use, with no existing replacement + }); +}); + +function getMockRequest() { + const mockRequest = createMock(); + mockRequest.user.sub = '123'; + return mockRequest; +} diff --git a/api/src/availability/availability.controller.ts b/api/src/availability/availability.controller.ts new file mode 100644 index 0000000..80bd194 --- /dev/null +++ b/api/src/availability/availability.controller.ts @@ -0,0 +1,135 @@ +import { + Body, + ConflictException, + Controller, + Delete, + ForbiddenException, + NotFoundException, + Param, + Post, + Req, +} from '@nestjs/common'; +import { AvailabilityService } from './availability.service'; +import { TimeSlotsService } from '../timeslots/timeslots.service'; +import { UsersService } from '../users/users.service'; +import { + Action, + AppAbility, + AvailabilityState, + insertAvailabilitySchema, +} from '@hkrecruitment/shared'; +import { JoiValidate } from '../joi-validation/joi-validate.decorator'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiForbiddenResponse, + ApiNotFoundResponse, + ApiCreatedResponse, + ApiTags, + ApiNoContentResponse, + ApiBadGatewayResponse, + ApiConflictResponse, + ApiUnprocessableEntityResponse, +} from '@nestjs/swagger'; +import { CheckPolicies } from 'src/authorization/check-policies.decorator'; +import { Availability } from './availability.entity'; +import * as Joi from 'joi'; +import { CreateAvailabilityDto } from './create-availability.dto'; +import { AuthenticatedRequest } from 'src/authorization/authenticated-request.types'; +import { Ability } from 'src/authorization/ability.decorator'; + +@ApiBearerAuth() +@ApiTags('availability') +@Controller('availability') +export class AvailabilityController { + constructor( + private readonly availabilityService: AvailabilityService, + private readonly timeSlotsService: TimeSlotsService, + private readonly userService: UsersService, + ) {} + + @ApiCreatedResponse() + @ApiBadRequestResponse() + @ApiNotFoundResponse() + @ApiForbiddenResponse() + @ApiBadGatewayResponse() + @ApiConflictResponse() + @CheckPolicies((ability) => ability.can(Action.Create, 'Availability')) + @Post() + @JoiValidate({ + param: Joi.number() + .positive() + .integer() + .required() + .label('availability_id'), + body: insertAvailabilitySchema, + }) + async createAvailability( + @Body() availabilityDto: CreateAvailabilityDto, + @Req() req: AuthenticatedRequest, + ): Promise { + /* Verify timeslot exists */ + const timeSlot = await this.timeSlotsService.findById( + availabilityDto.timeSlotId, + ); + if (!timeSlot) throw new NotFoundException('Timeslot not found'); + + /* Verify user exists */ + const user = await this.userService.findByOauthId(req.user.sub); + if (!user) throw new NotFoundException('User not found'); + + /* Verify availability for timeslot does not already exist */ + const existing = await this.availabilityService.findByUserAndTimeSlot( + user, + timeSlot, + ); + if (existing) + throw new ConflictException( + 'Availability already exists for this timeslot', + ); + + const availability = { + timeSlot: timeSlot, + state: AvailabilityState.Free, + } as Availability; + const result = await this.availabilityService.createAvailability( + availability, + ); + if (!result) throw new ForbiddenException(); + return result; + } + + @ApiNotFoundResponse() + @ApiForbiddenResponse() + @ApiNoContentResponse() + @ApiUnprocessableEntityResponse() + @CheckPolicies((ability) => ability.can(Action.Delete, 'Availability')) + @Delete(':availability_id') + @JoiValidate({ + param: Joi.number() + .positive() + .integer() + .required() + .label('availability_id'), + }) + async deleteAvailability( + @Ability() ability: AppAbility, + @Param('availability_id') availabilityId: number, + @Req() req: AuthenticatedRequest, + ): Promise { + // Check if availability exists + const availability = await this.availabilityService.findById( + availabilityId, + ); + if (!availability) throw new NotFoundException(); + + // Check if user has permission to delete availability + if ( + ability.cannot(Action.Delete, 'Availability') || + availability.user.oauthId !== req.user.sub + ) + throw new ForbiddenException(); + + return await this.availabilityService.deleteAvailability(availabilityId); + } +} diff --git a/api/src/availability/availability.entity.ts b/api/src/availability/availability.entity.ts new file mode 100644 index 0000000..384669e --- /dev/null +++ b/api/src/availability/availability.entity.ts @@ -0,0 +1,36 @@ +import { + Column, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + Relation, +} from 'typeorm'; +import { + Availability as AvailabilityInterface, + AvailabilityState, +} from '../../../shared/src/availability'; +import { User } from 'src/users/user.entity'; +import { TimeSlot } from 'src/timeslots/timeslot.entity'; +import { DbAwareColumn } from 'src/utils/db-aware-column'; + +@Entity() +export class Availability implements AvailabilityInterface { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column() + state: AvailabilityState; + + @Column({ name: 'last_modified' }) + lastModified: Date; + + @DbAwareColumn(() => TimeSlot, { name: 'time_slot' }) + @ManyToOne(() => TimeSlot, (timeSlot) => timeSlot.availabilities) + timeSlot: Relation; + + @ManyToOne(() => User, (user) => user.availabilities) + user: Relation; + + // @OneToOne(() => Interview) + // interview: Interview; +} diff --git a/api/src/availability/availability.module.ts b/api/src/availability/availability.module.ts new file mode 100644 index 0000000..a120215 --- /dev/null +++ b/api/src/availability/availability.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { AvailabilityService } from './availability.service'; +import { AvailabilityController } from './availability.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Availability } from './availability.entity'; +import { UsersModule } from 'src/users/users.module'; +import { TimeSlotsModule } from 'src/timeslots/timeslots.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Availability]), + UsersModule, + TimeSlotsModule, + ], + providers: [AvailabilityService], + controllers: [AvailabilityController], + exports: [AvailabilityService], +}) +export class AvailabilityModule {} diff --git a/api/src/availability/availability.service.spec.ts b/api/src/availability/availability.service.spec.ts new file mode 100644 index 0000000..9f62ba2 --- /dev/null +++ b/api/src/availability/availability.service.spec.ts @@ -0,0 +1,178 @@ +import { mockAvailability, testDate } from 'src/mocks/data'; +import { Test, TestingModule } from '@nestjs/testing'; +import { AvailabilityService } from './availability.service'; +import { Availability } from './availability.entity'; +import { getRepositoryToken, getDataSourceToken } from '@nestjs/typeorm'; +import { mockedRepository } from 'src/mocks/repositories'; +import { ConflictException } from '@nestjs/common'; +import { mockDataSource } from 'src/mocks/data-sources'; +import { AvailabilityState } from '@hkrecruitment/shared'; + +describe('AvailabilityService', () => { + let service: AvailabilityService; + + /************* Test setup ************/ + + beforeAll(() => { + jest + .spyOn(global, 'Date') + .mockImplementation(() => testDate as unknown as string); + }); + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AvailabilityService, + { + provide: getRepositoryToken(Availability), + useValue: mockedRepository, + }, + { + provide: getDataSourceToken(), + useValue: mockDataSource, + }, + ], + }).compile(); + + service = module.get(AvailabilityService); + }); + + afterEach(() => jest.clearAllMocks()); + + /*************** Tests ***************/ + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('listAvailabilities', () => { + it('should return all availabilities', async () => { + jest + .spyOn(mockedRepository, 'find') + .mockResolvedValue([mockAvailability]); + const result = await service.listAvailabilities(); + expect(result).toEqual([mockAvailability]); + expect(mockedRepository.find).toHaveBeenCalledTimes(1); + }); + }); + + // CRUD OPERATIONS + + describe('findById', () => { + it('should return the availability with the specified id', async () => { + jest + .spyOn(mockedRepository, 'findBy') + .mockResolvedValue([mockAvailability]); + const result = await service.findById(mockAvailability.id); + expect(result).toEqual(mockAvailability); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + }); + }); + + describe('createAvailability', () => { + it('should create a new availability', async () => { + jest.spyOn(mockedRepository, 'save').mockResolvedValue(mockAvailability); + const result = await service.createAvailability(mockAvailability); + expect(result).toEqual(mockAvailability); + expect(mockedRepository.save).toHaveBeenCalledTimes(1); + }); + }); + + describe('deleteAvailability', () => { + it('should remove the specified availability from the database', async () => { + const mockAvailabilityRepository = { + findOneBy: mockAvailability, + remove: mockAvailability, + }; + const mockedRepositories = mockDataSource.setMockResults({ + Availability: mockAvailabilityRepository, + }); + const result = await service.deleteAvailability(mockAvailability.id); + expect(result).toEqual(mockAvailability); + expect(mockDataSource.createQueryRunner).toHaveBeenCalledTimes(1); + expect( + mockedRepositories['Availability'].findOneBy, + ).toHaveBeenCalledTimes(1); + expect(mockedRepositories['Availability'].findOneBy).toHaveBeenCalledWith( + { id: mockAvailability.id }, + ); + expect(mockedRepositories['Availability'].remove).toHaveBeenCalledTimes( + 1, + ); + expect(mockedRepositories['Availability'].remove).toHaveBeenCalledWith( + mockAvailability, + ); + }); + + it('should throw a conflict error if the availability is in use', async () => { + const mockAvailabilityInUse = { + ...mockAvailability, + state: AvailabilityState.Interviewing, + }; + const mockAvailabilityRepository = { + findOneBy: mockAvailabilityInUse, + remove: mockAvailabilityInUse, + }; + const mockedRepositories = mockDataSource.setMockResults({ + Availability: mockAvailabilityRepository, + }); + jest + .spyOn(mockedRepositories['Availability'], 'findOneBy') + .mockResolvedValue(mockAvailabilityInUse); + jest + .spyOn(mockedRepositories['Availability'], 'remove') + .mockResolvedValue(mockAvailabilityInUse); + const result = service.deleteAvailability(mockAvailabilityInUse.id); + await expect(result).rejects.toThrow(ConflictException); + expect(mockedRepositories['Availability'].findOneBy).toHaveBeenCalledWith( + { id: mockAvailability.id }, + ); + expect( + mockedRepositories['Availability'].findOneBy, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe('findAvailabilityById', () => { + it('should return the availability with the specified id', async () => { + jest + .spyOn(mockedRepository, 'findBy') + .mockResolvedValue([mockAvailability]); + const result = await service.findById(mockAvailability.id); + expect(result).toEqual(mockAvailability); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + }); + }); + + describe('findByUserAndTimeSlot', () => { + it('should return the availability with the specified user and time slot', async () => { + jest + .spyOn(mockedRepository, 'findBy') + .mockResolvedValue([mockAvailability]); + const result = await service.findByUserAndTimeSlot( + mockAvailability.user, + mockAvailability.timeSlot, + ); + expect(result).toEqual(mockAvailability); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + expect(mockedRepository.findBy).toHaveBeenCalledWith({ + user: mockAvailability.user, + timeSlot: mockAvailability.timeSlot, + }); + }); + + it('should return null if no availability is found', async () => { + jest.spyOn(mockedRepository, 'findBy').mockResolvedValue([]); + const result = await service.findByUserAndTimeSlot( + mockAvailability.user, + mockAvailability.timeSlot, + ); + expect(result).toBeNull(); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + expect(mockedRepository.findBy).toHaveBeenCalledWith({ + user: mockAvailability.user, + timeSlot: mockAvailability.timeSlot, + }); + }); + }); +}); diff --git a/api/src/availability/availability.service.ts b/api/src/availability/availability.service.ts new file mode 100644 index 0000000..6f9904d --- /dev/null +++ b/api/src/availability/availability.service.ts @@ -0,0 +1,81 @@ +import { ConflictException, Injectable } from '@nestjs/common'; +import { InjectRepository, InjectDataSource } from '@nestjs/typeorm'; +import { Repository, DataSource, QueryRunner } from 'typeorm'; +import { Availability } from './availability.entity'; +import { TimeSlot } from 'src/timeslots/timeslot.entity'; +import { User } from 'src/users/user.entity'; +import { AvailabilityState } from '@hkrecruitment/shared'; +import { transaction } from 'src/utils/database'; + +@Injectable() +export class AvailabilityService { + constructor( + @InjectRepository(Availability) + private readonly availabilityRepository: Repository, + @InjectDataSource() + private dataSource: DataSource, + ) {} + + async listAvailabilities(): Promise { + return await this.availabilityRepository.find(); + } + + async findById(id: number): Promise { + const matches = await this.availabilityRepository.findBy({ + id: id, + }); + return matches.length > 0 ? matches[0] : null; + } + + async findByUserAndTimeSlot(user: User, timeSlot: TimeSlot) { + const matches = await this.availabilityRepository.findBy({ + user: user, + timeSlot: timeSlot, + }); + return matches.length > 0 ? matches[0] : null; + } + + async createAvailability(availability: Availability): Promise { + return await this.availabilityRepository.save(availability); + } + + async updateAvailability( + oldAvailabilityId: number, + newAvailability: Availability, + ): Promise { + return await this.availabilityRepository.save({ + ...newAvailability, + id: oldAvailabilityId, + }); + } + + async deleteAvailability(availabilityId: number): Promise { + return await transaction( + this.dataSource, + async (queryRunner: QueryRunner) => { + const availability = await queryRunner.manager + .getRepository(Availability) + .findOneBy({ id: availabilityId }); + + // Check if availability is in use + if (availability.state === AvailabilityState.Interviewing) { + const rescheduled = false; + // TODO: If user was optional, just delete it, otherwise: + // Try to assign a different person to the interview + // TODO: Retrieve availability status + id of interviewer of timeslots in range [timeslot-1, timeslot+1] + // TODO: Search for someone with state = AvailabilityState.Available in timesolt, and state != AvailabilityState.interviewing in t-1 and t+1 + // TODO: If it doesn't exist: rescheduled = False + // If no other availability is found, availability cannot be deleted + if (!rescheduled) + throw new ConflictException( + 'Availability is in use and cannot be deleted.', + ); + } + + return await queryRunner.manager + .getRepository(Availability) + .remove(availability); + }, + ); + } +} diff --git a/api/src/availability/create-availability.dto.ts b/api/src/availability/create-availability.dto.ts new file mode 100644 index 0000000..cf90fab --- /dev/null +++ b/api/src/availability/create-availability.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateAvailabilityDto { + @ApiProperty() + timeSlotId: number; +} diff --git a/api/src/mocks/data-sources.ts b/api/src/mocks/data-sources.ts new file mode 100644 index 0000000..4371477 --- /dev/null +++ b/api/src/mocks/data-sources.ts @@ -0,0 +1,59 @@ +import { QueryRunner } from 'typeorm'; +import { mock } from 'jest-mock-extended'; + +export interface QueryResults { + findOneBy?: any; + findOne?: any; + findBy?: any; + find?: any; + save?: any; + query?: any; + remove?: any; +} + +export class MockedDataSource { + results = {}; + + /** + * Sets the mock results for the data source. + * + * @param results - An object containing the query results. + * @returns The mocked results object. + */ + setMockResults(results: { [key: string]: QueryResults }) { + const mockedResults = {}; + for (const key in results) + mockedResults[key] = this.mockResults(results[key]); + this.results = mockedResults; + return mockedResults; + } + + /** + * Creates a mock object with jest.Mock functions for each property of the input object. + * The mock functions will return the corresponding values from the input object. + * + * @param results - The object containing the values to be mocked. + * @returns An object with jest.Mock functions for each property of the input object. + */ + mockResults(results: T): { [K in keyof T]: jest.Mock } { + const mockedResults = {} as { [K in keyof T]: jest.Mock }; + for (const key in results) mockedResults[key] = jest.fn(() => results[key]); + return mockedResults; + } + + /** + * Creates a mock query runner. + * @returns A mock query runner object. + */ + createQueryRunner = jest.fn(() => { + const queryRunner = mock(); + queryRunner.manager.getRepository = jest.fn((entity: any) => { + if (!this.results.hasOwnProperty(entity.name)) + throw new Error(`No results found for entity ${entity.name}`); + return this.results[entity.name]; + }); + return queryRunner; + }); +} + +export const mockDataSource = new MockedDataSource(); diff --git a/api/src/mocks/data.ts b/api/src/mocks/data.ts index be63dcf..64b1c36 100644 --- a/api/src/mocks/data.ts +++ b/api/src/mocks/data.ts @@ -1,23 +1,30 @@ import { CreateApplicationDto } from 'src/application/create-application.dto'; -import { - ApplicationType, - ApplicationState, - LangLevel, - Role, -} from '@hkrecruitment/shared'; import { BscApplication, MscApplication, PhdApplication, } from 'src/application/application.entity'; import { UpdateApplicationDto } from 'src/application/update-application.dto'; -import { RecruitmentSessionState } from '@hkrecruitment/shared/recruitment-session'; +import { + RecruitmentSession, + RecruitmentSessionState, +} from '@hkrecruitment/shared/recruitment-session'; import { CreateRecruitmentSessionDto } from 'src/recruitment-session/create-recruitment-session.dto'; import { UpdateRecruitmentSessionDto } from 'src/recruitment-session/update-recruitment-session.dto'; +import { CreateAvailabilityDto } from 'src/availability/create-availability.dto'; +import { + ApplicationType, + ApplicationState, + LangLevel, + Role, + AvailabilityState, + TimeSlot, +} from '@hkrecruitment/shared'; -export const testDate = new Date(2023, 0, 1); +export const testDate = new Date(2023, 0, 1, 10, 0, 0); export const testDateTimeStart = new Date(2023, 0, 1, 10, 30, 0); export const testDateTime10Minutes = new Date(2023, 0, 1, 10, 40, 0); +export const testDateTime1Hour = new Date(2023, 0, 1, 11, 30, 0); export const testDateTime3Hours = new Date(2023, 0, 1, 13, 30, 0); export const testDateTimeEnd = new Date(2023, 0, 1, 11, 30, 0); @@ -25,7 +32,8 @@ export const mockTimeSlot = { start: testDateTimeStart, end: testDateTimeEnd, id: 1, -}; + availabilities: [], +} as TimeSlot & { availabilities: any[] }; export const testInterviewStart = '11:55' as unknown as Date; export const testInterviewEnd = '20:35' as unknown as Date; @@ -60,6 +68,13 @@ export const mockUpdateRecruitmentSessionDto = { days: [testDay1, testDay2, testDay3], } as UpdateRecruitmentSessionDto; +export const mockGenerateTimeSlots = { + slotDuration: 30, + interviewStart: testDateTimeStart, + interviewEnd: testDateTime1Hour, + days: [testDay1, testDay3], +} as RecruitmentSession; + export const baseFile = { encoding: '7bit', mimetype: 'application/pdf', @@ -169,6 +184,15 @@ export const applicant = { role: Role.Applicant, }; +export const mockClerk = { + firstName: 'Violet', + lastName: 'Red', + oauthId: '321', + sex: 'female', + email: 'email2@example.com', + role: Role.Clerk, +}; + export const applicationFiles = { cv: [ { @@ -191,4 +215,16 @@ export const applicationFiles = { export const applicantId = 'abc123'; export const folderId = 'folder_abc123'; export const fileId = 'file_abc123'; -export const today = '1/1/2023, 24:00:00'; +export const today = '1/1/2023, 10:00:00'; + +export const mockAvailability = { + id: 1, + state: AvailabilityState.Free, + lastModified: new Date(), + timeSlot: mockTimeSlot, + user: applicant, +}; + +export const mockCreateAvailabilityDto = { + timeSlotId: mockTimeSlot.id, +} as CreateAvailabilityDto; diff --git a/api/src/mocks/services.ts b/api/src/mocks/services.ts index 6d7d17c..ae0611f 100644 --- a/api/src/mocks/services.ts +++ b/api/src/mocks/services.ts @@ -1,3 +1,13 @@ -export const mockedUsersService = { - findByOauthId: jest.fn(), -}; +import { TimeSlotsService } from 'src/timeslots/timeslots.service'; +import { UsersService } from 'src/users/users.service'; + +function classToMock(classToMock: any): Object { + const mockedService = {}; + Object.getOwnPropertyNames(classToMock.prototype).forEach((methodName) => { + mockedService[methodName] = jest.fn(); + }); + return mockedService; +} + +export const mockedUsersService = classToMock(UsersService); +export const mockedTimeSlotsService = classToMock(TimeSlotsService); diff --git a/api/src/recruitment-session/recruitment-session.controller.spec.ts b/api/src/recruitment-session/recruitment-session.controller.spec.ts index 719202d..e3fc458 100644 --- a/api/src/recruitment-session/recruitment-session.controller.spec.ts +++ b/api/src/recruitment-session/recruitment-session.controller.spec.ts @@ -67,7 +67,7 @@ describe('RecruitmentSessionController', () => { }); jest .spyOn(service, 'findActiveRecruitmentSession') - .mockResolvedValue({ ...mockRecruitmentSession }); + .mockResolvedValue(mockRecruitmentSession); const result = controller.findActive(mockAbility); await expect(result).rejects.toThrow(ForbiddenException); expect(service.findActiveRecruitmentSession).toHaveBeenCalledTimes(1); @@ -83,6 +83,9 @@ describe('RecruitmentSessionController', () => { jest .spyOn(service, 'createRecruitmentSession') .mockResolvedValue(mockRecruitmentSession); + jest + .spyOn(service, 'findActiveRecruitmentSession') + .mockResolvedValue(null); const result = await controller.createRecruitmentSession( mockCreateRecruitmentSessionDto, ); @@ -137,8 +140,11 @@ describe('RecruitmentSessionController', () => { ); expect(service.updateRecruitmentSession).toHaveBeenCalledTimes(1); expect(service.updateRecruitmentSession).toHaveBeenCalledWith({ - ...mockRecruitmentSession, + ...mockUpdateRecruitmentSessionDto, + createdAt: mockRecruitmentSession.createdAt, lastModified: testDate, + id: mockRecruitmentSession.id, + state: mockRecruitmentSession.state, }); }); @@ -214,7 +220,7 @@ describe('RecruitmentSessionController', () => { expect(service.findActiveRecruitmentSession).toHaveBeenCalledTimes(1); }); - it("shouldn't throw a ConflictException when updating the currentyl active RecruitmentSection state to 'Active'", async () => { + it("shouldn't throw a ConflictException when updating the currently active RecruitmentSection state to 'Active'", async () => { const mockAbility = createMockAbility(({ can }) => { can(Action.Update, 'RecruitmentSession'); }); @@ -294,11 +300,14 @@ describe('RecruitmentSessionController', () => { state: mockDeletedRecruitmentSession.state, createdAt: mockDeletedRecruitmentSession.createdAt, } as RecruitmentSessionResponseDto; + jest + .spyOn(global, 'Date') + .mockImplementation(() => testDate as unknown as string); jest .spyOn(service, 'findRecruitmentSessionById') .mockResolvedValue(mockRecruitmentSession); jest - .spyOn(service, 'deletRecruitmentSession') + .spyOn(service, 'deleteRecruitmentSession') .mockResolvedValue(mockDeletedRecruitmentSession); const result = await controller.deleteRecruitmentSession( mockRecruitmentSession.id, @@ -308,8 +317,8 @@ describe('RecruitmentSessionController', () => { expect(service.findRecruitmentSessionById).toHaveBeenCalledWith( mockRecruitmentSession.id, ); - expect(service.deletRecruitmentSession).toHaveBeenCalledTimes(1); - expect(service.deletRecruitmentSession).toHaveBeenCalledWith( + expect(service.deleteRecruitmentSession).toHaveBeenCalledTimes(1); + expect(service.deleteRecruitmentSession).toHaveBeenCalledWith( mockRecruitmentSession, ); }); diff --git a/api/src/recruitment-session/recruitment-session.controller.ts b/api/src/recruitment-session/recruitment-session.controller.ts index bc85757..72324fd 100644 --- a/api/src/recruitment-session/recruitment-session.controller.ts +++ b/api/src/recruitment-session/recruitment-session.controller.ts @@ -56,7 +56,7 @@ export class RecruitmentSessionController { @CheckPolicies((ability) => ability.can(Action.Read, 'RecruitmentSession')) async findActive( @Ability() ability: AppAbility, - ): Promise { + ): Promise { const recruitmentSession = await this.recruitmentSessionService.findActiveRecruitmentSession(); if (recruitmentSession === null) { @@ -81,7 +81,7 @@ export class RecruitmentSessionController { @ApiBadRequestResponse() @ApiForbiddenResponse() @ApiConflictResponse({ - description: 'The recruitment session cannot be created', // + description: 'The recruitment session cannot be created', }) @ApiCreatedResponse() @JoiValidate({ @@ -95,14 +95,14 @@ export class RecruitmentSessionController { // there should be only one active recruitment session at a time const hasActiveRecruitmentSession = await this.recruitmentSessionService.findActiveRecruitmentSession(); - if (hasActiveRecruitmentSession) + if (hasActiveRecruitmentSession != null) throw new ConflictException( 'There is already an active recruitment session', ); - return this.recruitmentSessionService.createRecruitmentSession({ - ...recruitmentSession, - }); + return this.recruitmentSessionService.createRecruitmentSession( + recruitmentSession, + ); } // UPDATE A RECRUITMENT SESSION @@ -149,7 +149,7 @@ export class RecruitmentSessionController { const currentlyActiveRecruitmentSession = await this.recruitmentSessionService.findActiveRecruitmentSession(); if ( - currentlyActiveRecruitmentSession && + currentlyActiveRecruitmentSession != null && currentlyActiveRecruitmentSession.id !== recruitmentSession.id // It's ok to set 'Active' to the (already) active recruitment session ) throw new ConflictException( @@ -207,7 +207,8 @@ export class RecruitmentSessionController { await this.recruitmentSessionService.findRecruitmentSessionById( recruitmentSessionId, ); - if (!toRemove) throw new NotFoundException('Recruitment session not found'); + if (toRemove === null) + throw new NotFoundException('Recruitment session not found'); // Check if recruitment session has pending interviews if (toRemove.state !== RecruitmentSessionState.Concluded) { @@ -223,7 +224,7 @@ export class RecruitmentSessionController { // Delete recruitment session const deletedRecruitmentSession = - await this.recruitmentSessionService.deletRecruitmentSession(toRemove); + await this.recruitmentSessionService.deleteRecruitmentSession(toRemove); return plainToClass( RecruitmentSessionResponseDto, diff --git a/api/src/recruitment-session/recruitment-session.module.ts b/api/src/recruitment-session/recruitment-session.module.ts index b53e481..48200b0 100644 --- a/api/src/recruitment-session/recruitment-session.module.ts +++ b/api/src/recruitment-session/recruitment-session.module.ts @@ -4,9 +4,14 @@ import { RecruitmentSessionController } from './recruitment-session.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { RecruitmentSession } from './recruitment-session.entity'; import { UsersModule } from 'src/users/users.module'; +import { TimeSlotsModule } from 'src/timeslots/timeslots.module'; @Module({ - imports: [TypeOrmModule.forFeature([RecruitmentSession]), UsersModule], + imports: [ + TypeOrmModule.forFeature([RecruitmentSession]), + UsersModule, + TimeSlotsModule, + ], providers: [RecruitmentSessionService], controllers: [RecruitmentSessionController], exports: [RecruitmentSessionService], diff --git a/api/src/recruitment-session/recruitment-session.service.spec.ts b/api/src/recruitment-session/recruitment-session.service.spec.ts index d6dfc9e..6df16dc 100644 --- a/api/src/recruitment-session/recruitment-session.service.spec.ts +++ b/api/src/recruitment-session/recruitment-session.service.spec.ts @@ -1,12 +1,17 @@ -import { mockRecruitmentSession, testDate } from '@mocks/data'; -import { mockedRepository } from '@mocks/repositories'; +import { mockRecruitmentSession, testDate } from 'src/mocks/data'; +import { mockedRepository } from 'src/mocks/repositories'; import { TestingModule, Test } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; +import { getRepositoryToken, getDataSourceToken } from '@nestjs/typeorm'; import { RecruitmentSession } from './recruitment-session.entity'; import { RecruitmentSessionService } from './recruitment-session.service'; +import { mockedTimeSlotsService as mockedTimeSlotsServiceClass } from '@mocks/services'; +import { mockDataSource } from 'src/mocks/data-sources'; +import { TimeSlotsService } from 'src/timeslots/timeslots.service'; +import { RecruitmentSessionState } from '@hkrecruitment/shared'; describe('Recruitment Session Service', () => { let recruitmentSessionService: RecruitmentSessionService; + let mockedTimeSlotsService: TimeSlotsService; beforeAll(() => { jest @@ -22,9 +27,18 @@ describe('Recruitment Session Service', () => { provide: getRepositoryToken(RecruitmentSession), useValue: mockedRepository, }, + { + provide: TimeSlotsService, + useValue: mockedTimeSlotsServiceClass, + }, + { + provide: getDataSourceToken(), + useValue: mockDataSource, + }, ], }).compile(); + mockedTimeSlotsService = module.get(TimeSlotsService); recruitmentSessionService = module.get( RecruitmentSessionService, ); @@ -36,16 +50,72 @@ describe('Recruitment Session Service', () => { expect(recruitmentSessionService).toBeDefined(); }); + describe('createRecruitmentSession', () => { + it('should create a new recruitment session', async () => { + const mockRecruitmentSessionRepository = { + save: mockRecruitmentSession, + }; + const mockedRepositories = mockDataSource.setMockResults({ + RecruitmentSession: mockRecruitmentSessionRepository, + }); + jest + .spyOn(mockedTimeSlotsService, 'createRecruitmentSessionTimeSlots') + .mockResolvedValue([]); + const result = await recruitmentSessionService.createRecruitmentSession( + mockRecruitmentSession, + ); + const expectedRecruitmentSession = { + ...mockRecruitmentSession, + createdAt: testDate, + lastModified: testDate, + }; + expect(result).toEqual(mockRecruitmentSession); + expect( + mockedRepositories['RecruitmentSession'].save, + ).toHaveBeenCalledTimes(1); + expect( + mockedRepositories['RecruitmentSession'].save, + ).toHaveBeenCalledWith(expectedRecruitmentSession); + expect( + mockedTimeSlotsService.createRecruitmentSessionTimeSlots, + ).toHaveBeenCalledTimes(1); + }); + }); + describe('deleteRecruitmentSession', () => { it('should remove the specified recruitment session from the database', async () => { jest .spyOn(mockedRepository, 'remove') .mockResolvedValue(mockRecruitmentSession); - const result = await recruitmentSessionService.deletRecruitmentSession( + const result = await recruitmentSessionService.deleteRecruitmentSession( mockRecruitmentSession, ); expect(result).toEqual(mockRecruitmentSession); expect(mockedRepository.remove).toHaveBeenCalledTimes(1); + expect(mockedRepository.remove).toHaveBeenCalledWith( + mockRecruitmentSession, + ); + }); + }); + + describe('updateRecruitmentSession', () => { + it('should update and return an existing recruitment session', async () => { + const updatedRecruitmentSession: RecruitmentSession = { + ...mockRecruitmentSession, + state: RecruitmentSessionState.Concluded, + }; + jest + .spyOn(mockedRepository, 'save') + .mockResolvedValue(updatedRecruitmentSession); + const result = await recruitmentSessionService.updateRecruitmentSession( + mockRecruitmentSession, + ); + + expect(result).toEqual(updatedRecruitmentSession); + expect(mockedRepository.save).toHaveBeenCalledTimes(1); + expect(mockedRepository.save).toHaveBeenCalledWith( + mockRecruitmentSession, + ); }); }); }); diff --git a/api/src/recruitment-session/recruitment-session.service.ts b/api/src/recruitment-session/recruitment-session.service.ts index 2b77ee5..670a9e6 100644 --- a/api/src/recruitment-session/recruitment-session.service.ts +++ b/api/src/recruitment-session/recruitment-session.service.ts @@ -1,15 +1,20 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { InjectRepository, InjectDataSource } from '@nestjs/typeorm'; +import { Repository, DataSource } from 'typeorm'; import { RecruitmentSession } from './recruitment-session.entity'; import { CreateRecruitmentSessionDto } from './create-recruitment-session.dto'; import { RecruitmentSessionState } from '@hkrecruitment/shared'; +import { transaction } from 'src/utils/database'; +import { TimeSlotsService } from 'src/timeslots/timeslots.service'; @Injectable() export class RecruitmentSessionService { constructor( @InjectRepository(RecruitmentSession) private readonly recruitmentSessionRepository: Repository, + private readonly timeslotService: TimeSlotsService, + @InjectDataSource() + private dataSource: DataSource, ) {} async createRecruitmentSession( @@ -21,26 +26,41 @@ export class RecruitmentSessionService { state: RecruitmentSessionState.Active, createdAt: now, lastModified: now, - } as unknown as RecruitmentSession; - await this.recruitmentSessionRepository.save(rs); - return rs; + } as RecruitmentSession; + + return transaction(this.dataSource, async (queryRunner) => { + const recruitmentSession = await queryRunner.manager + .getRepository(RecruitmentSession) + .save(rs); + await this.timeslotService.createRecruitmentSessionTimeSlots( + queryRunner, + recruitmentSession, + ); + return recruitmentSession; + }); } async findAllRecruitmentSessions(): Promise { return await this.recruitmentSessionRepository.find(); } - async findRecruitmentSessionById(id: number): Promise { - return await this.recruitmentSessionRepository.findOne({ where: { id } }); + async findRecruitmentSessionById( + RSid: number, + ): Promise { + const matches = await this.recruitmentSessionRepository.findBy({ + id: RSid, + }); + return matches.length > 0 ? matches[0] : null; } - async findActiveRecruitmentSession(): Promise { - return await this.recruitmentSessionRepository.findOne({ - where: { state: RecruitmentSessionState.Active }, + async findActiveRecruitmentSession(): Promise { + const matches = await this.recruitmentSessionRepository.findBy({ + state: RecruitmentSessionState.Active, }); + return matches.length > 0 ? matches[0] : null; } - async deletRecruitmentSession( + async deleteRecruitmentSession( recruitmentSession: RecruitmentSession, ): Promise { return await this.recruitmentSessionRepository.remove(recruitmentSession); diff --git a/api/src/timeslots/create-timeslot.dto.ts b/api/src/timeslots/create-timeslot.dto.ts index 2b9aaea..2e7836d 100644 --- a/api/src/timeslots/create-timeslot.dto.ts +++ b/api/src/timeslots/create-timeslot.dto.ts @@ -1,7 +1,7 @@ import { TimeSlot } from '@hkrecruitment/shared'; import { ApiProperty } from '@nestjs/swagger'; -export class CreateTimeSlotDto implements TimeSlot { +export class CreateTimeSlotDto implements Partial { @ApiProperty() start: Date; diff --git a/api/src/timeslots/timeslot.entity.ts b/api/src/timeslots/timeslot.entity.ts index 959f85f..54911ca 100644 --- a/api/src/timeslots/timeslot.entity.ts +++ b/api/src/timeslots/timeslot.entity.ts @@ -1,5 +1,12 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + Entity, + OneToMany, + PrimaryGeneratedColumn, + Relation, +} from 'typeorm'; import { TimeSlot as TimeSlotInterface } from '@hkrecruitment/shared'; +import { Availability } from 'src/availability/availability.entity'; @Entity() export class TimeSlot implements TimeSlotInterface { @@ -11,4 +18,7 @@ export class TimeSlot implements TimeSlotInterface { @Column() end: Date; + + @OneToMany(() => Availability, (availability) => availability.timeSlot) + availabilities: Relation; } diff --git a/api/src/timeslots/timeslots.controller.spec.ts b/api/src/timeslots/timeslots.controller.spec.ts index 0c2d34b..0c1c16d 100644 --- a/api/src/timeslots/timeslots.controller.spec.ts +++ b/api/src/timeslots/timeslots.controller.spec.ts @@ -1,14 +1,6 @@ import { TestBed } from '@automock/jest'; -import { - mockTimeSlot, - testDate, - testDateTimeEnd, - testDateTimeStart, -} from '@mocks/data'; import { TimeSlotsController } from './timeslots.controller'; import { TimeSlotsService } from './timeslots.service'; -import { testDateTime10Minutes } from '@mocks/data'; -import { testDateTime3Hours } from '@mocks/data'; describe('TimeSlotController', () => { let controller: TimeSlotsController; @@ -26,79 +18,4 @@ describe('TimeSlotController', () => { expect(controller).toBeDefined(); expect(service).toBeDefined(); }); - - // Create a time slot - describe('createTimeSlot', () => { - it('should allow creating a valid time slot', async () => { - const timeSlot = { - start: mockTimeSlot.start, - end: mockTimeSlot.end, - }; - - jest.spyOn(service, 'countOverlappingTimeSlots').mockResolvedValue(0); - jest.spyOn(service, 'createTimeSlot').mockResolvedValue(mockTimeSlot); - - const result = await controller.createTimeSlot(timeSlot); - - expect(result).toEqual(mockTimeSlot); - }); - - it('should throw an error if the duration is less than 30 minutes', async () => { - const timeSlot = { - start: testDateTimeStart, - end: testDateTime10Minutes, - }; - - await expect(controller.createTimeSlot(timeSlot)).rejects.toThrow( - 'The duration of the time slot must be at least 30 minutes', - ); - }); - - it('should throw an error if the duration is more than 60 minutes', async () => { - const timeSlot = { - start: testDateTimeStart, - end: testDateTime3Hours, - }; - - await expect(controller.createTimeSlot(timeSlot)).rejects.toThrow( - 'The duration of the time slot must be at most 60 minutes', - ); - }); - - it('should throw an error if the time slot overlaps with an existing time slot', async () => { - const timeSlot = { - start: testDateTimeStart, - end: testDateTimeEnd, - }; - - jest.spyOn(service, 'countOverlappingTimeSlots').mockResolvedValue(1); - - await expect(controller.createTimeSlot(timeSlot)).rejects.toThrow( - 'The time slot overlaps with existing time slots', - ); - }); - }); - - describe('deleteTimeSlot', () => { - it('should allow deleting an existing time slot', async () => { - jest.spyOn(service, 'findById').mockResolvedValue(mockTimeSlot); - jest.spyOn(service, 'deleteTimeSlot').mockResolvedValue(mockTimeSlot); - - await expect(controller.deleteTimeSlot(mockTimeSlot.id)).resolves.toEqual( - mockTimeSlot, - ); - expect(service.deleteTimeSlot).toHaveBeenCalledWith(mockTimeSlot); - expect(service.deleteTimeSlot).toHaveBeenCalledTimes(1); - }); - - it('should throw an error if the time slot does not exist', async () => { - jest.spyOn(service, 'findById').mockResolvedValue(null); - jest.spyOn(service, 'deleteTimeSlot').mockResolvedValue(mockTimeSlot); - - await expect( - controller.deleteTimeSlot(mockTimeSlot.id), - ).rejects.toThrowError('Time slot not found'); - expect(service.deleteTimeSlot).toHaveBeenCalledTimes(0); - }); - }); }); diff --git a/api/src/timeslots/timeslots.controller.ts b/api/src/timeslots/timeslots.controller.ts index 3202071..9580b8d 100644 --- a/api/src/timeslots/timeslots.controller.ts +++ b/api/src/timeslots/timeslots.controller.ts @@ -1,91 +1,10 @@ -import { - Body, - Controller, - BadRequestException, - NotFoundException, - ConflictException, - Param, - Post, - Delete, -} from '@nestjs/common'; +import { Controller } from '@nestjs/common'; import { TimeSlotsService } from './timeslots.service'; -import { Action, createTimeSlotSchema, TimeSlot } from '@hkrecruitment/shared'; -import { JoiValidate } from '../joi-validation/joi-validate.decorator'; -import { - ApiBadRequestResponse, - ApiBearerAuth, - ApiForbiddenResponse, - ApiNotFoundResponse, - ApiCreatedResponse, - ApiOkResponse, - ApiTags, - ApiConflictResponse, - ApiNoContentResponse, -} from '@nestjs/swagger'; -import { CheckPolicies } from 'src/authorization/check-policies.decorator'; -import { CreateTimeSlotDto } from './create-timeslot.dto'; -import * as Joi from 'joi'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; @ApiBearerAuth() @ApiTags('timeslots') @Controller('timeslots') export class TimeSlotsController { constructor(private readonly timeSlotsService: TimeSlotsService) {} - - @ApiBadRequestResponse() - @ApiForbiddenResponse() - @ApiConflictResponse({ - description: 'The time slot overlaps with existing time slots', - }) - @ApiCreatedResponse() - @JoiValidate({ - body: createTimeSlotSchema, - }) - @CheckPolicies((ability) => ability.can(Action.Create, 'TimeSlot')) - @Post() - async createTimeSlot(@Body() timeSlot: CreateTimeSlotDto): Promise { - const startDate = new Date(timeSlot.start); - const endDate = new Date(timeSlot.end); - - // Check duration - const durationInMinutes = - (endDate.getTime() - startDate.getTime()) / (1000 * 60); - if (durationInMinutes < 30) { - throw new BadRequestException( - 'The duration of the time slot must be at least 30 minutes', - ); - } else if (durationInMinutes > 60) { - throw new BadRequestException( - 'The duration of the time slot must be at most 60 minutes', - ); - } - - // Check overlapping timeslots - const overlappingTimeSlots = - await this.timeSlotsService.countOverlappingTimeSlots(startDate, endDate); - if (overlappingTimeSlots > 0) - throw new ConflictException( - 'The time slot overlaps with existing time slots', - ); - - return await this.timeSlotsService.createTimeSlot(timeSlot); - } - - @ApiBadRequestResponse() - @ApiForbiddenResponse() - @ApiNotFoundResponse() - @ApiOkResponse() - @ApiNoContentResponse() - @CheckPolicies((ability) => ability.can(Action.Delete, 'TimeSlot')) - @Delete('/:time_slot_id') - @JoiValidate({ - param: Joi.number().positive().integer().required().label('time_slot_id'), - }) - async deleteTimeSlot( - @Param('time_slot_id') timeSlotId: number, - ): Promise { - const timeSlot = await this.timeSlotsService.findById(timeSlotId); - if (!timeSlot) throw new NotFoundException('Time slot not found'); - return await this.timeSlotsService.deleteTimeSlot(timeSlot); - } } diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index fde00f1..6284724 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -1,17 +1,19 @@ -import { mockTimeSlot, testDate } from '@mocks/data'; -import { mockedRepository } from '@mocks/repositories'; +import { mockGenerateTimeSlots, mockTimeSlot, testDate } from 'src/mocks/data'; +import { mockedRepository } from 'src/mocks/repositories'; import { TestingModule, Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { TimeSlot } from './timeslot.entity'; import { TimeSlotsService } from './timeslots.service'; +import { mockDataSource } from 'src/mocks/data-sources'; describe('TimeSlotsService', () => { let timeSlotService: TimeSlotsService; + let mockDate: jest.SpyInstance; /************* Test setup ************/ beforeAll(() => { - jest + mockDate = jest .spyOn(global, 'Date') .mockImplementation(() => testDate as unknown as string); }); @@ -73,4 +75,144 @@ describe('TimeSlotsService', () => { expect(mockedRepository.save).toHaveBeenCalledTimes(1); }); }); + + describe('createRecruitmentSessionTimeSlots', () => { + it('should create recruitment session time slots', async () => { + const expectedTimeSlots: Partial[] = [ + { + start: new Date('2023-01-01T10:30:00'), + end: new Date('2023-01-01T11:00:00'), + }, + { + start: new Date('2023-01-01T11:00:00'), + end: new Date('2023-01-01T11:30:00'), + }, + { + start: new Date('2023-01-01T10:00:00'), + end: new Date('2022-01-01T11:00:00'), + }, + { + start: new Date('2023-01-01T10:00:00'), + end: new Date('2022-01-01T11:00:00'), + }, + ]; + + mockDataSource.setMockResults({ TimeSlot: { save: expectedTimeSlots } }); + const queryRunner = mockDataSource.createQueryRunner(); + const result = await timeSlotService.createRecruitmentSessionTimeSlots( + queryRunner, + mockGenerateTimeSlots, + ); + + expect(result).toEqual(expectedTimeSlots); + expect( + queryRunner.manager.getRepository(TimeSlot).save, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe('generateTimeSlots', () => { + it('should generate time slots', () => { + mockDate.mockRestore(); + const slotDuration = 60; + const interviewStart = new Date('2022-01-01T09:00:00'); + const interviewEnd = new Date('2022-01-01T12:00:00'); + const days = [new Date('2022-01-01'), new Date('2022-01-03')]; + const expectedTimeSlots: Partial[] = [ + { + start: new Date('2022-01-01T09:00:00'), + end: new Date('2022-01-01T10:00:00'), + }, + { + start: new Date('2022-01-03T09:00:00'), + end: new Date('2022-01-03T10:00:00'), + }, + { + start: new Date('2022-01-01T10:00:00'), + end: new Date('2022-01-01T11:00:00'), + }, + { + start: new Date('2022-01-03T10:00:00'), + end: new Date('2022-01-03T11:00:00'), + }, + { + start: new Date('2022-01-01T11:00:00'), + end: new Date('2022-01-01T12:00:00'), + }, + { + start: new Date('2022-01-03T11:00:00'), + end: new Date('2022-01-03T12:00:00'), + }, + ]; + testTimeSlotsGeneration( + timeSlotService, + slotDuration, + interviewStart, + interviewEnd, + days, + expectedTimeSlots, + ); + }); + + it("shouldn't generate time slots that overflow interviewEnd time", () => { + mockDate.mockRestore(); + const slotDuration = 50; + const interviewStart = new Date('2022-02-02T09:00:00'); + const interviewEnd = new Date('2022-02-02T11:00:00'); + const days = [new Date('2022-02-02'), new Date('2022-02-04')]; + const expectedTimeSlots: Partial[] = [ + { + start: new Date('2022-02-02T09:00:00'), + end: new Date('2022-02-02T09:50:00'), + }, + { + start: new Date('2022-02-04T09:00:00'), + end: new Date('2022-02-04T09:50:00'), + }, + { + start: new Date('2022-02-02T09:50:00'), + end: new Date('2022-02-02T10:40:00'), + }, + { + start: new Date('2022-02-04T09:50:00'), + end: new Date('2022-02-04T10:40:00'), + }, + ]; + testTimeSlotsGeneration( + timeSlotService, + slotDuration, + interviewStart, + interviewEnd, + days, + expectedTimeSlots, + ); + }); + }); }); + +function testTimeSlotsGeneration( + timeSlotService: TimeSlotsService, + slotDuration: number, + interviewStart: Date, + interviewEnd: Date, + days: Date[], + expectedResult: Partial[], +) { + const expectedTimeSlots: Partial[] = expectedResult.map( + (timeSlot) => + ({ + ...timeSlot, + id: undefined, + availabilities: undefined, + } as TimeSlot), + ); + + const result = timeSlotService.generateTimeSlots( + slotDuration, + interviewStart, + interviewEnd, + days, + ); + + expect(result).toEqual(expectedTimeSlots); +} diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index ec5e1b9..7eb37c0 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, LessThan, MoreThan } from 'typeorm'; +import { Repository, LessThan, MoreThan, QueryRunner } from 'typeorm'; import { TimeSlot } from './timeslot.entity'; +import { RecruitmentSession } from '@hkrecruitment/shared'; import { CreateTimeSlotDto } from './create-timeslot.dto'; @Injectable() @@ -51,4 +52,57 @@ export class TimeSlotsService { async createTimeSlot(timeSlot: CreateTimeSlotDto): Promise { return await this.timeSlotRepository.save(timeSlot); } + + async createRecruitmentSessionTimeSlots( + queryRunner: QueryRunner, + recruitmentSession: RecruitmentSession, + ): Promise { + const { slotDuration, interviewStart, interviewEnd, days } = + recruitmentSession; + const timeSlots = this.generateTimeSlots( + slotDuration, + interviewStart, + interviewEnd, + days, + ); + return await queryRunner.manager.getRepository(TimeSlot).save(timeSlots); + } + + generateTimeSlots( + slotDuration: number, + interviewStart: Date, + interviewEnd: Date, + days: Date[], + ): TimeSlot[] { + const timeSlots: TimeSlot[] = []; + const interviewStartMinutes = + interviewStart.getHours() * 60 + interviewStart.getMinutes(); + const interviewEndMinutes = + interviewEnd.getHours() * 60 + interviewEnd.getMinutes(); + const dailySlots = Math.floor( + (interviewEndMinutes - interviewStartMinutes) / slotDuration, + ); + + for (let i = 0; i < dailySlots; i++) { + for (let day of days) { + const timeSlotStart = new Date(day); + timeSlotStart.setHours( + interviewStart.getHours() + Math.floor((i * slotDuration) / 60), + interviewStart.getMinutes() + ((i * slotDuration) % 60), + 0, + 0, + ); + const timeSlotEnd = new Date( + timeSlotStart.getTime() + slotDuration * 1000 * 60, + ); + + const timeSlot = new TimeSlot(); + timeSlot.start = timeSlotStart; + timeSlot.end = timeSlotEnd; + timeSlots.push(timeSlot); + } + } + + return timeSlots; + } } diff --git a/api/src/users/user.entity.ts b/api/src/users/user.entity.ts index f797bca..81bd96c 100644 --- a/api/src/users/user.entity.ts +++ b/api/src/users/user.entity.ts @@ -1,5 +1,6 @@ -import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { Column, Entity, OneToMany, PrimaryColumn, Relation } from 'typeorm'; import { Person, Role } from '@hkrecruitment/shared'; +import { Availability } from 'src/availability/availability.entity'; @Entity() export class User implements Person { @@ -26,4 +27,13 @@ export class User implements Person { @Column() role: Role; + + @OneToMany(() => Availability, (availability) => availability.user) + availabilities?: Relation; + + @Column() + is_board: boolean; + + @Column() + is_expert: boolean; } diff --git a/api/src/users/users.controller.spec.ts b/api/src/users/users.controller.spec.ts index 90f5ca7..845f281 100644 --- a/api/src/users/users.controller.spec.ts +++ b/api/src/users/users.controller.spec.ts @@ -31,6 +31,8 @@ describe('UsersController', () => { sex: 'M', email: 'example@example.com', role: Role.Applicant, + is_board: false, + is_expert: false, }; const mockMember: Person = { @@ -40,6 +42,8 @@ describe('UsersController', () => { sex: 'F', email: 'jane@hknpolito.org', role: Role.Member, + is_board: false, + is_expert: false, }; const mockUsers = [mockApplicant, mockMember]; diff --git a/api/src/users/users.controller.ts b/api/src/users/users.controller.ts index d3af4d9..21e986b 100644 --- a/api/src/users/users.controller.ts +++ b/api/src/users/users.controller.ts @@ -96,6 +96,8 @@ export class UsersController { return this.usersService.create({ ...user, role: !!user.role ? user.role : defaultRole, + is_board: false, + is_expert: false, }); } diff --git a/api/src/users/users.e2e-spec.ts b/api/src/users/users.e2e-spec.ts index 2291a5a..06f35b3 100644 --- a/api/src/users/users.e2e-spec.ts +++ b/api/src/users/users.e2e-spec.ts @@ -27,6 +27,8 @@ describe('UsersController (e2e)', () => { sex: 'F', email: 'known-superuser-test@example.com', role: Role.Admin, + is_board: true, + is_expert: true, }, { oauthId: getSub(newApplicantToken), @@ -35,6 +37,8 @@ describe('UsersController (e2e)', () => { sex: 'F', email: 'test-applicant@example.com', role: Role.Applicant, + is_board: false, + is_expert: false, }, { oauthId: getSub(newMemberToken), @@ -43,6 +47,8 @@ describe('UsersController (e2e)', () => { sex: 'M', email: 'hknrecruitment-test@hknpolito.org', role: Role.Member, + is_board: false, + is_expert: true, }, ]; }); @@ -138,6 +144,8 @@ describe('UsersController (e2e)', () => { role: Role.Applicant, phone_no: null, telegramId: null, + is_board: false, + is_expert: false, }; await request(app.getHttpServer()) @@ -165,6 +173,8 @@ describe('UsersController (e2e)', () => { role: Role.Member, phone_no: null, telegramId: null, + is_board: false, + is_expert: false, }; await request(app.getHttpServer()) diff --git a/api/src/utils/database.ts b/api/src/utils/database.ts new file mode 100644 index 0000000..f9bdeb9 --- /dev/null +++ b/api/src/utils/database.ts @@ -0,0 +1,33 @@ +import { DataSource, QueryRunner } from 'typeorm'; + +/** + * Executes a transaction using the provided data source and actions. + * @param dataSource The connection configuration to a specific database to use for the transaction. + * @param actions The actions to perform within the transaction. + * @returns The result of the executed actions. + */ +export async function transaction( + dataSource: DataSource, + actions: (queryRunner: QueryRunner) => Promise, +): Promise { + // BEGIN TRANSACTION + const queryRunner = dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + let result: any; + + try { + result = await actions(queryRunner); + // COMMIT TRANSACTION + await queryRunner.commitTransaction(); + } catch (err) { + // ROLLBACK TRANSACTION + await queryRunner.rollbackTransaction(); + throw err; + } finally { + await queryRunner.release(); + } + + return result; +} diff --git a/api/src/utils/db-aware-column.ts b/api/src/utils/db-aware-column.ts new file mode 100644 index 0000000..85aca90 --- /dev/null +++ b/api/src/utils/db-aware-column.ts @@ -0,0 +1,18 @@ +import { Column, ColumnOptions } from 'typeorm'; + +/** + * Decorator function that uses 'varchar' as the column type in the testing environment. + * This is necessary as sqlite does not support custom classes as column types. + * If not in a test environment, it returns a column with its original type. + * + * @param type - The type of the column. + * @param columnOptions - The options for the column. + * @returns The column definition. + */ +export function DbAwareColumn( + type: (t?: any) => Function, + columnOptions: ColumnOptions, +) { + if (process.env.NODE_ENV === 'test') return Column('varchar', columnOptions); + return Column(type, columnOptions); +} diff --git a/api/test/app.e2e-spec.ts b/api/test/app.e2e-spec.ts index 80144af..db63ae9 100644 --- a/api/test/app.e2e-spec.ts +++ b/api/test/app.e2e-spec.ts @@ -4,6 +4,7 @@ import * as dotenv from 'dotenv'; dotenv.config({ path: '.env.test' }); import { readFileSync } from 'fs'; import { decodeJwt } from 'jose'; +import { INestApplication } from '@nestjs/common'; export const createApp = async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -19,10 +20,12 @@ export const createApp = async () => { const allCredentials = JSON.parse( readFileSync('test/user-credentials.json').toString(), ); + const auth0_issuer = process.env.AUTH0_ISSUER_URL; const auth0_client_id = process.env.AUTH0_CLIENT_ID; const auth0_client_secret = process.env.AUTH0_CLIENT_SECRET; const auth0_audience = process.env.AUTH0_AUDIENCE; + export const getAccessToken = async (key: string): Promise => { const credentials: { mail: string; @@ -38,21 +41,25 @@ export const getAccessToken = async (key: string): Promise => { client_id: auth0_client_id, client_secret: auth0_client_secret, }; + const response = await fetch(`${auth0_issuer}oauth/token`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), - }).then((res) => res.json()); + }); + + const json = await response.json(); - return response.access_token; + return json.access_token; }; + export const getSub = (accessToken: string): string => { const decoded = decodeJwt(accessToken); return decoded.sub; }; describe('e2e build test', () => { - let app; + let app: INestApplication; beforeEach(async () => { app = await createApp(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbb0a77..1b5024d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,6 +61,9 @@ importers: googleapis: specifier: ^118.0.0 version: 118.0.0 + jest-mock-extended: + specifier: ^3.0.5 + version: 3.0.5(jest@28.1.3)(typescript@4.5.2) joi: specifier: ^17.7.0 version: 17.7.0 @@ -286,7 +289,6 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.1.1 '@jridgewell/trace-mapping': 0.3.17 - dev: true /@angular-devkit/core@14.2.1(chokidar@3.5.3): resolution: {integrity: sha512-lW8oNGuJqr4r31FWBjfWQYkSXdiOHBGOThIEtHvUVBKfPF/oVrupLueCUgBPel+NvxENXdo93uPsqHN7bZbmsQ==} @@ -401,12 +403,10 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 - dev: true /@babel/compat-data@7.20.5: resolution: {integrity: sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==} engines: {node: '>=6.9.0'} - dev: true /@babel/core@7.20.5: resolution: {integrity: sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==} @@ -429,7 +429,6 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color - dev: true /@babel/core@7.21.3: resolution: {integrity: sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==} @@ -452,7 +451,6 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color - dev: true /@babel/generator@7.20.5: resolution: {integrity: sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==} @@ -461,7 +459,6 @@ packages: '@babel/types': 7.20.5 '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 - dev: true /@babel/generator@7.21.3: resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==} @@ -471,7 +468,6 @@ packages: '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 - dev: true /@babel/helper-compilation-targets@7.20.0(@babel/core@7.20.5): resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} @@ -484,7 +480,6 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 semver: 6.3.0 - dev: true /@babel/helper-compilation-targets@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} @@ -498,12 +493,10 @@ packages: browserslist: 4.21.5 lru-cache: 5.1.1 semver: 6.3.0 - dev: true /@babel/helper-environment-visitor@7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-function-name@7.19.0: resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} @@ -511,7 +504,6 @@ packages: dependencies: '@babel/template': 7.18.10 '@babel/types': 7.20.5 - dev: true /@babel/helper-function-name@7.21.0: resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} @@ -519,21 +511,18 @@ packages: dependencies: '@babel/template': 7.20.7 '@babel/types': 7.21.3 - dev: true /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.20.5 - dev: true /@babel/helper-module-imports@7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.20.5 - dev: true /@babel/helper-module-transforms@7.20.2: resolution: {integrity: sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==} @@ -549,7 +538,6 @@ packages: '@babel/types': 7.20.5 transitivePeerDependencies: - supports-color - dev: true /@babel/helper-module-transforms@7.21.2: resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} @@ -565,46 +553,38 @@ packages: '@babel/types': 7.21.3 transitivePeerDependencies: - supports-color - dev: true /@babel/helper-plugin-utils@7.20.2: resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-simple-access@7.20.2: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.20.5 - dev: true /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.20.5 - dev: true /@babel/helper-string-parser@7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-option@7.18.6: resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-option@7.21.0: resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/helpers@7.20.6: resolution: {integrity: sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==} @@ -615,7 +595,6 @@ packages: '@babel/types': 7.20.5 transitivePeerDependencies: - supports-color - dev: true /@babel/helpers@7.21.0: resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} @@ -626,7 +605,6 @@ packages: '@babel/types': 7.21.3 transitivePeerDependencies: - supports-color - dev: true /@babel/highlight@7.18.6: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} @@ -635,7 +613,6 @@ packages: '@babel/helper-validator-identifier': 7.19.1 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true /@babel/parser@7.20.5: resolution: {integrity: sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==} @@ -643,7 +620,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.20.5 - dev: true /@babel/parser@7.21.3: resolution: {integrity: sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==} @@ -651,7 +627,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.21.3 - dev: true /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.3): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} @@ -660,7 +635,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} @@ -669,7 +643,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.3): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} @@ -678,7 +651,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.3): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} @@ -687,7 +659,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} @@ -696,7 +667,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.21.3): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} @@ -705,7 +675,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -714,7 +683,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.3): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} @@ -723,7 +691,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} @@ -732,7 +699,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} @@ -741,7 +707,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -750,7 +715,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.3): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} @@ -760,7 +724,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.21.3): resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} @@ -770,7 +733,6 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - dev: true /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.3): resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} @@ -806,7 +768,6 @@ packages: '@babel/code-frame': 7.18.6 '@babel/parser': 7.20.5 '@babel/types': 7.20.5 - dev: true /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} @@ -815,7 +776,6 @@ packages: '@babel/code-frame': 7.18.6 '@babel/parser': 7.21.3 '@babel/types': 7.21.3 - dev: true /@babel/traverse@7.20.5: resolution: {integrity: sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==} @@ -833,7 +793,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true /@babel/traverse@7.21.3: resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==} @@ -851,7 +810,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true /@babel/types@7.20.5: resolution: {integrity: sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==} @@ -860,7 +818,6 @@ packages: '@babel/helper-string-parser': 7.19.4 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - dev: true /@babel/types@7.21.3: resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==} @@ -869,11 +826,9 @@ packages: '@babel/helper-string-parser': 7.19.4 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - dev: true /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true /@casl/ability@6.3.3: resolution: {integrity: sha512-UzbqsE9etu6QzZrRmqIyVun2kztAzJ46Tz7lC/2P2buCE6B6Ll7Vptz7JTQtGwapLbeKo2jS7dL966TVOQ7x4g==} @@ -1180,12 +1135,10 @@ packages: get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 - dev: true /@istanbuljs/schema@0.1.3: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - dev: true /@jest/console@28.1.3: resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==} @@ -1197,7 +1150,6 @@ packages: jest-message-util: 28.1.3 jest-util: 28.1.3 slash: 3.0.0 - dev: true /@jest/core@28.1.3(ts-node@10.7.0): resolution: {integrity: sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==} @@ -1240,7 +1192,6 @@ packages: transitivePeerDependencies: - supports-color - ts-node - dev: true /@jest/create-cache-key-function@27.5.1: resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==} @@ -1257,14 +1208,12 @@ packages: '@jest/types': 28.1.3 '@types/node': 16.18.4 jest-mock: 28.1.3 - dev: true /@jest/expect-utils@28.1.3: resolution: {integrity: sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: jest-get-type: 28.0.2 - dev: true /@jest/expect@28.1.3: resolution: {integrity: sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==} @@ -1274,7 +1223,6 @@ packages: jest-snapshot: 28.1.3 transitivePeerDependencies: - supports-color - dev: true /@jest/fake-timers@28.1.3: resolution: {integrity: sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==} @@ -1286,7 +1234,6 @@ packages: jest-message-util: 28.1.3 jest-mock: 28.1.3 jest-util: 28.1.3 - dev: true /@jest/globals@28.1.3: resolution: {integrity: sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==} @@ -1297,7 +1244,6 @@ packages: '@jest/types': 28.1.3 transitivePeerDependencies: - supports-color - dev: true /@jest/reporters@28.1.3: resolution: {integrity: sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==} @@ -1335,14 +1281,12 @@ packages: v8-to-istanbul: 9.0.1 transitivePeerDependencies: - supports-color - dev: true /@jest/schemas@28.1.3: resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@sinclair/typebox': 0.24.51 - dev: true /@jest/source-map@28.1.2: resolution: {integrity: sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==} @@ -1351,7 +1295,6 @@ packages: '@jridgewell/trace-mapping': 0.3.17 callsites: 3.1.0 graceful-fs: 4.2.10 - dev: true /@jest/test-result@28.1.3: resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==} @@ -1361,7 +1304,6 @@ packages: '@jest/types': 28.1.3 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 - dev: true /@jest/test-sequencer@28.1.3: resolution: {integrity: sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==} @@ -1371,7 +1313,6 @@ packages: graceful-fs: 4.2.10 jest-haste-map: 28.1.3 slash: 3.0.0 - dev: true /@jest/transform@28.1.3: resolution: {integrity: sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==} @@ -1394,7 +1335,6 @@ packages: write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color - dev: true /@jest/types@27.5.1: resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} @@ -1417,7 +1357,6 @@ packages: '@types/node': 16.18.4 '@types/yargs': 17.0.15 chalk: 4.1.2 - dev: true /@joi/date@2.1.0: resolution: {integrity: sha512-2zN5m0LgxZp/cynHGbzEImVmFIa+n+IOb/Nlw5LX/PLJneeCwG1NbiGw7MvPjsAKUGQK8z31Nn6V6lEN+4fZhg==} @@ -1431,7 +1370,6 @@ packages: dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.14 - dev: true /@jridgewell/gen-mapping@0.3.2: resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} @@ -1842,19 +1780,16 @@ packages: /@sinclair/typebox@0.24.51: resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} - dev: true /@sinonjs/commons@1.8.6: resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} dependencies: type-detect: 4.0.8 - dev: true /@sinonjs/fake-timers@9.1.2: resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} dependencies: '@sinonjs/commons': 1.8.6 - dev: true /@sqltools/formatter@1.2.5: resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} @@ -2004,26 +1939,22 @@ packages: '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.3 - dev: true /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: '@babel/types': 7.20.5 - dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: '@babel/parser': 7.20.5 '@babel/types': 7.20.5 - dev: true /@types/babel__traverse@7.18.3: resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} dependencies: '@babel/types': 7.20.5 - dev: true /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} @@ -2074,23 +2005,19 @@ packages: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: '@types/node': 16.18.4 - dev: true /@types/istanbul-lib-coverage@2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} - dev: true /@types/istanbul-lib-report@3.0.0: resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} dependencies: '@types/istanbul-lib-coverage': 2.0.4 - dev: true /@types/istanbul-reports@3.0.1: resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} dependencies: '@types/istanbul-lib-report': 3.0.0 - dev: true /@types/jest@28.1.8: resolution: {integrity: sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==} @@ -2151,7 +2078,6 @@ packages: /@types/prettier@2.7.1: resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} - dev: true /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -2193,7 +2119,6 @@ packages: /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} - dev: true /@types/superagent@4.1.16: resolution: {integrity: sha512-tLfnlJf6A5mB6ddqF159GqcDizfzbMUB1/DeT59/wBNqzRTNNKsaw79A/1TZ84X+f/EwWH8FeuSkjlCLyqS/zQ==} @@ -2214,7 +2139,6 @@ packages: /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} - dev: true /@types/yargs@16.0.5: resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==} @@ -2226,7 +2150,6 @@ packages: resolution: {integrity: sha512-ZHc4W2dnEQPfhn06TBEdWaiUHEZAocYaiVMfwOipY5jcJt/251wVrKCBWBetGZWO5CF8tdb7L3DmdxVlZ2BOIg==} dependencies: '@types/yargs-parser': 21.0.0 - dev: true /@typescript-eslint/eslint-plugin@5.45.0(@typescript-eslint/parser@5.45.0)(eslint@8.29.0)(typescript@4.5.2): resolution: {integrity: sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==} @@ -2613,7 +2536,6 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.21.3 - dev: true /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -2624,7 +2546,6 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 - dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -2635,7 +2556,6 @@ packages: /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2647,7 +2567,6 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: true /app-root-path@3.1.0: resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} @@ -2683,7 +2602,6 @@ packages: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 - dev: true /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2729,7 +2647,6 @@ packages: slash: 3.0.0 transitivePeerDependencies: - supports-color - dev: true /babel-plugin-istanbul@6.1.1: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} @@ -2742,7 +2659,6 @@ packages: test-exclude: 6.0.0 transitivePeerDependencies: - supports-color - dev: true /babel-plugin-jest-hoist@28.1.3: resolution: {integrity: sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==} @@ -2752,7 +2668,6 @@ packages: '@babel/types': 7.20.5 '@types/babel__core': 7.1.20 '@types/babel__traverse': 7.18.3 - dev: true /babel-preset-current-node-syntax@1.0.1(@babel/core@7.21.3): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} @@ -2772,7 +2687,6 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.3) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.3) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.3) - dev: true /babel-preset-jest@28.1.3(@babel/core@7.21.3): resolution: {integrity: sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==} @@ -2783,7 +2697,6 @@ packages: '@babel/core': 7.21.3 babel-plugin-jest-hoist: 28.1.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.3) - dev: true /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2855,7 +2768,6 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true /browser-tabs-lock@1.2.15: resolution: {integrity: sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==} @@ -2883,7 +2795,6 @@ packages: electron-to-chromium: 1.4.284 node-releases: 2.0.10 update-browserslist-db: 1.0.10(browserslist@4.21.5) - dev: true /bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} @@ -2896,7 +2807,6 @@ packages: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: node-int64: 0.4.0 - dev: true /buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -2970,7 +2880,6 @@ packages: /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: true /camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} @@ -2982,19 +2891,16 @@ packages: /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - dev: true /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - dev: true /caniuse-lite@1.0.30001436: resolution: {integrity: sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==} /caniuse-lite@1.0.30001468: resolution: {integrity: sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==} - dev: true /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -3003,7 +2909,6 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true /chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} @@ -3023,7 +2928,6 @@ packages: /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} - dev: true /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -3055,11 +2959,9 @@ packages: /ci-info@3.7.0: resolution: {integrity: sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==} engines: {node: '>=8'} - dev: true /cjs-module-lexer@1.2.2: resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} - dev: true /class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} @@ -3144,17 +3046,14 @@ packages: /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: true /collect-v8-coverage@1.0.1: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} - dev: true /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 - dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -3164,7 +3063,6 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -3236,7 +3134,6 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -3285,7 +3182,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -3346,7 +3242,6 @@ packages: /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - dev: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -3355,7 +3250,6 @@ packages: /deepmerge@4.2.2: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} engines: {node: '>=0.10.0'} - dev: true /defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -3391,7 +3285,6 @@ packages: /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - dev: true /dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -3403,7 +3296,6 @@ packages: /diff-sequences@28.1.1: resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dev: true /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} @@ -3500,7 +3392,6 @@ packages: /emittery@0.10.2: resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} engines: {node: '>=12'} - dev: true /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3548,7 +3439,6 @@ packages: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 - dev: true /es-cookie@1.3.2: resolution: {integrity: sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==} @@ -3597,12 +3487,10 @@ packages: /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - dev: true /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -3731,7 +3619,6 @@ packages: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - dev: true /esquery@1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} @@ -3804,12 +3691,10 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 - dev: true /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} - dev: true /expect@28.1.3: resolution: {integrity: sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==} @@ -3820,7 +3705,6 @@ packages: jest-matcher-utils: 28.1.3 jest-message-util: 28.1.3 jest-util: 28.1.3 - dev: true /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} @@ -3919,7 +3803,6 @@ packages: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} dependencies: bser: 2.1.1 - dev: true /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} @@ -3946,7 +3829,6 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -3968,7 +3850,6 @@ packages: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: true /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -4070,7 +3951,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true /function-bind@1.1.1: @@ -4132,7 +4012,6 @@ packages: /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - dev: true /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} @@ -4148,7 +4027,6 @@ packages: /get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} - dev: true /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} @@ -4160,7 +4038,6 @@ packages: /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - dev: true /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -4203,7 +4080,6 @@ packages: /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - dev: true /globals@13.18.0: resolution: {integrity: sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==} @@ -4298,7 +4174,6 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -4333,7 +4208,6 @@ packages: /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true /html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} @@ -4393,7 +4267,6 @@ packages: /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - dev: true /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -4443,7 +4316,6 @@ packages: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 - dev: true /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -4531,7 +4403,6 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} @@ -4544,7 +4415,6 @@ packages: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} dependencies: has: 1.0.3 - dev: true /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} @@ -4558,7 +4428,6 @@ packages: /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} - dev: true /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} @@ -4580,7 +4449,6 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -4606,7 +4474,6 @@ packages: /istanbul-lib-coverage@3.2.0: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} - dev: true /istanbul-lib-instrument@5.2.1: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} @@ -4619,7 +4486,6 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color - dev: true /istanbul-lib-report@3.0.0: resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} @@ -4628,7 +4494,6 @@ packages: istanbul-lib-coverage: 3.2.0 make-dir: 3.1.0 supports-color: 7.2.0 - dev: true /istanbul-lib-source-maps@4.0.1: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} @@ -4639,7 +4504,6 @@ packages: source-map: 0.6.1 transitivePeerDependencies: - supports-color - dev: true /istanbul-reports@3.1.5: resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} @@ -4647,7 +4511,6 @@ packages: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.0 - dev: true /iterare@1.2.1: resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} @@ -4670,7 +4533,6 @@ packages: dependencies: execa: 5.1.1 p-limit: 3.1.0 - dev: true /jest-circus@28.1.3: resolution: {integrity: sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==} @@ -4697,7 +4559,6 @@ packages: stack-utils: 2.0.6 transitivePeerDependencies: - supports-color - dev: true /jest-cli@28.1.3(@types/node@16.18.4)(ts-node@10.7.0): resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==} @@ -4725,7 +4586,6 @@ packages: - '@types/node' - supports-color - ts-node - dev: true /jest-config@28.1.3(@types/node@16.18.4)(ts-node@10.7.0): resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} @@ -4765,7 +4625,6 @@ packages: ts-node: 10.7.0(@swc/core@1.3.56)(@types/node@16.18.4)(typescript@4.5.2) transitivePeerDependencies: - supports-color - dev: true /jest-diff@28.1.3: resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} @@ -4775,14 +4634,12 @@ packages: diff-sequences: 28.1.1 jest-get-type: 28.0.2 pretty-format: 28.1.3 - dev: true /jest-docblock@28.1.1: resolution: {integrity: sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: detect-newline: 3.1.0 - dev: true /jest-each@28.1.3: resolution: {integrity: sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==} @@ -4793,7 +4650,6 @@ packages: jest-get-type: 28.0.2 jest-util: 28.1.3 pretty-format: 28.1.3 - dev: true /jest-environment-node@28.1.3: resolution: {integrity: sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==} @@ -4805,12 +4661,10 @@ packages: '@types/node': 16.18.4 jest-mock: 28.1.3 jest-util: 28.1.3 - dev: true /jest-get-type@28.0.2: resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dev: true /jest-haste-map@28.1.3: resolution: {integrity: sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==} @@ -4829,7 +4683,6 @@ packages: walker: 1.0.8 optionalDependencies: fsevents: 2.3.2 - dev: true /jest-leak-detector@28.1.3: resolution: {integrity: sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==} @@ -4837,7 +4690,6 @@ packages: dependencies: jest-get-type: 28.0.2 pretty-format: 28.1.3 - dev: true /jest-matcher-utils@28.1.3: resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==} @@ -4847,7 +4699,6 @@ packages: jest-diff: 28.1.3 jest-get-type: 28.0.2 pretty-format: 28.1.3 - dev: true /jest-message-util@28.1.3: resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} @@ -4862,7 +4713,6 @@ packages: pretty-format: 28.1.3 slash: 3.0.0 stack-utils: 2.0.6 - dev: true /jest-mock-extended@2.0.9(jest@28.1.3)(typescript@4.5.2): resolution: {integrity: sha512-eRZq7/FgwHbxOMm3Lo4DpQX6S2zi4OvwMVFHEb3FgDLp0Xy3P1WARkF93xxO5uD4nAHiEPYHZ25qVU9mAVxoLQ==} @@ -4875,13 +4725,23 @@ packages: typescript: 4.5.2 dev: true + /jest-mock-extended@3.0.5(jest@28.1.3)(typescript@4.5.2): + resolution: {integrity: sha512-/eHdaNPUAXe7f65gHH5urc8SbRVWjYxBqmCgax2uqOBJy8UUcCBMN1upj1eZ8y/i+IqpyEm4Kq0VKss/GCCTdw==} + peerDependencies: + jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 + typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 + dependencies: + jest: 28.1.3(@types/node@16.18.4)(ts-node@10.7.0) + ts-essentials: 7.0.3(typescript@4.5.2) + typescript: 4.5.2 + dev: false + /jest-mock@28.1.3: resolution: {integrity: sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 '@types/node': 16.18.4 - dev: true /jest-pnp-resolver@1.2.3(jest-resolve@28.1.3): resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} @@ -4893,12 +4753,10 @@ packages: optional: true dependencies: jest-resolve: 28.1.3 - dev: true /jest-regex-util@28.0.2: resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dev: true /jest-resolve-dependencies@28.1.3: resolution: {integrity: sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==} @@ -4908,7 +4766,6 @@ packages: jest-snapshot: 28.1.3 transitivePeerDependencies: - supports-color - dev: true /jest-resolve@28.1.3: resolution: {integrity: sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==} @@ -4923,7 +4780,6 @@ packages: resolve: 1.22.1 resolve.exports: 1.1.0 slash: 3.0.0 - dev: true /jest-runner@28.1.3: resolution: {integrity: sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==} @@ -4952,7 +4808,6 @@ packages: source-map-support: 0.5.13 transitivePeerDependencies: - supports-color - dev: true /jest-runtime@28.1.3: resolution: {integrity: sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==} @@ -4982,7 +4837,6 @@ packages: strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - dev: true /jest-snapshot@28.1.3: resolution: {integrity: sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==} @@ -5013,7 +4867,6 @@ packages: semver: 7.3.8 transitivePeerDependencies: - supports-color - dev: true /jest-util@28.1.3: resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} @@ -5025,7 +4878,6 @@ packages: ci-info: 3.7.0 graceful-fs: 4.2.10 picomatch: 2.3.1 - dev: true /jest-validate@28.1.3: resolution: {integrity: sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==} @@ -5037,7 +4889,6 @@ packages: jest-get-type: 28.0.2 leven: 3.1.0 pretty-format: 28.1.3 - dev: true /jest-watcher@28.1.3: resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==} @@ -5051,7 +4902,6 @@ packages: emittery: 0.10.2 jest-util: 28.1.3 string-length: 4.0.2 - dev: true /jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} @@ -5068,7 +4918,6 @@ packages: '@types/node': 16.18.4 merge-stream: 2.0.0 supports-color: 8.1.1 - dev: true /jest@28.1.3(@types/node@16.18.4)(ts-node@10.7.0): resolution: {integrity: sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==} @@ -5088,7 +4937,6 @@ packages: - '@types/node' - supports-color - ts-node - dev: true /joi@17.7.0: resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==} @@ -5116,7 +4964,6 @@ packages: dependencies: argparse: 1.0.10 esprima: 4.0.1 - dev: true /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} @@ -5128,7 +4975,6 @@ packages: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true - dev: true /json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -5160,7 +5006,6 @@ packages: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - dev: true /jsonc-parser@3.1.0: resolution: {integrity: sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==} @@ -5241,12 +5086,10 @@ packages: /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - dev: true /leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} - dev: true /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -5262,7 +5105,6 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true /loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} @@ -5273,7 +5115,6 @@ packages: engines: {node: '>=8'} dependencies: p-locate: 4.1.0 - dev: true /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -5357,7 +5198,6 @@ packages: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: yallist: 3.1.1 - dev: true /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} @@ -5430,7 +5270,6 @@ packages: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} dependencies: tmpl: 1.0.5 - dev: true /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} @@ -5464,7 +5303,6 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 - dev: true /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -5490,7 +5328,6 @@ packages: /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: true /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5633,7 +5470,6 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} @@ -5708,11 +5544,9 @@ packages: /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - dev: true /node-releases@2.0.10: resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} - dev: true /node-releases@2.0.6: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} @@ -5751,14 +5585,12 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} dependencies: path-key: 3.1.1 - dev: true /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} @@ -5812,7 +5644,6 @@ packages: engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 - dev: true /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} @@ -5859,7 +5690,6 @@ packages: engines: {node: '>=6'} dependencies: p-try: 2.2.0 - dev: true /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} @@ -5872,7 +5702,6 @@ packages: engines: {node: '>=8'} dependencies: p-limit: 2.3.0 - dev: true /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} @@ -5892,7 +5721,6 @@ packages: /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true /packet-reader@1.0.0: resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} @@ -5920,7 +5748,6 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: true /parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -5971,7 +5798,6 @@ packages: /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -5980,11 +5806,9 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -6067,19 +5891,16 @@ packages: /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true /pirates@4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} - dev: true /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} dependencies: find-up: 4.1.0 - dev: true /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} @@ -6143,7 +5964,6 @@ packages: ansi-regex: 5.0.1 ansi-styles: 5.2.0 react-is: 18.2.0 - dev: true /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -6182,7 +6002,6 @@ packages: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true /prop-types-extra@1.1.1(react@18.2.0): resolution: {integrity: sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==} @@ -6298,7 +6117,6 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true /react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} @@ -6439,7 +6257,6 @@ packages: engines: {node: '>=8'} dependencies: resolve-from: 5.0.0 - dev: true /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -6449,12 +6266,10 @@ packages: /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - dev: true /resolve.exports@1.1.0: resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==} engines: {node: '>=10'} - dev: true /resolve@1.22.1: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} @@ -6463,7 +6278,6 @@ packages: is-core-module: 2.11.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} @@ -6623,12 +6437,10 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /shelljs@0.8.5: resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} @@ -6659,12 +6471,10 @@ packages: /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - dev: true /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} @@ -6703,7 +6513,6 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -6731,7 +6540,6 @@ packages: /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true /sqlite3@5.1.6: resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} @@ -6763,7 +6571,6 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 - dev: true /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} @@ -6779,7 +6586,6 @@ packages: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 - dev: true /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -6813,17 +6619,14 @@ packages: /strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} - dev: true /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - dev: true /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - dev: true /superagent@8.0.5: resolution: {integrity: sha512-lQVE0Praz7nHiSaJLKBM/cZyi7J0E4io8tWnGSBdBrqAzhzrjQ/F5iGP9Zr29CJC8N5zYdhG2kKaNcB6dKxp7g==} @@ -6858,7 +6661,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -6878,12 +6680,10 @@ packages: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 - dev: true /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: true /swagger-ui-dist@4.15.1: resolution: {integrity: sha512-DlZARu6ckUFqDe0j5IPayO4k0gQvYQw9Un02MhxAgaMtVnTH2vmyyDe+yKeV0r1LiiPx3JbasdS/5Yyb/AV3iw==} @@ -6915,7 +6715,6 @@ packages: dependencies: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - dev: true /terser-webpack-plugin@5.3.6(@swc/core@1.3.56)(webpack@5.74.0): resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} @@ -6983,7 +6782,6 @@ packages: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 - dev: true /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -7015,19 +6813,16 @@ packages: /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - dev: true /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -7054,7 +6849,6 @@ packages: typescript: '>=3.7.0' dependencies: typescript: 4.5.2 - dev: true /ts-jest@28.0.8(@babel/core@7.21.3)(jest@28.1.3)(typescript@4.5.2): resolution: {integrity: sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==} @@ -7215,7 +7009,6 @@ packages: /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - dev: true /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} @@ -7225,7 +7018,6 @@ packages: /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: true /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} @@ -7407,7 +7199,6 @@ packages: browserslist: 4.21.5 escalade: 3.1.1 picocolors: 1.0.0 - dev: true /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -7444,7 +7235,6 @@ packages: '@jridgewell/trace-mapping': 0.3.17 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 - dev: true /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} @@ -7507,7 +7297,6 @@ packages: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: makeerror: 1.0.12 - dev: true /warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} @@ -7670,7 +7459,6 @@ packages: dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 - dev: true /xml2js@0.4.23: resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} @@ -7699,7 +7487,6 @@ packages: /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: true /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} diff --git a/shared/src/abilities.ts b/shared/src/abilities.ts index 23fd5bf..dd277ee 100644 --- a/shared/src/abilities.ts +++ b/shared/src/abilities.ts @@ -8,7 +8,7 @@ import { import { applyAbilitiesForPerson, Person, Role } from "./person"; import { Application, applyAbilitiesOnApplication } from "./application"; import { applyAbilitiesOnAvailability, Availability } from "./availability"; -import { TimeSlot } from "./timeslot"; +import { applyAbilitiesOnTimeSlot, TimeSlot } from "./timeslot"; import { RecruitmentSession } from "./recruitment-session"; import { applyAbilitiesOnRecruitmentSession } from "./recruitment-session"; @@ -53,6 +53,7 @@ export const abilityForUser = (user: UserAuth): AppAbility => { applyAbilitiesOnApplication(user, builder); applyAbilitiesOnAvailability(user, builder); applyAbilitiesOnRecruitmentSession(user, builder); + applyAbilitiesOnTimeSlot(user, builder); const { build } = builder; return build(); diff --git a/shared/src/availability.spec.ts b/shared/src/availability.spec.ts index 212a245..53f5939 100644 --- a/shared/src/availability.spec.ts +++ b/shared/src/availability.spec.ts @@ -1,39 +1,93 @@ import { AvailabilityState, - AvailabilityType, Availability, - updateAvailabilitySchema, + insertAvailabilitySchema, + applyAbilitiesOnAvailability, } from "./availability"; import { createMockAbility } from "./abilities.spec"; import { Action, UserAuth, checkAbility } from "./abilities"; import { Role } from "./person"; +const mockAbilityForAvailability = (user: UserAuth) => + createMockAbility((builder) => { + applyAbilitiesOnAvailability(user, builder); + }); + +const mockAvailability = { + id: 1, + state: AvailabilityState.Free, + lastModified: new Date(), + timeSlot: { + start: new Date(2024, 0, 1, 10, 0, 0), + end: new Date(2024, 0, 1, 11, 0, 0), + id: 1, + availabilities: [], + }, + user: { + firstName: "John", + lastName: "Doe", + oauthId: "123", + role: Role.Member, + }, +}; + describe("Availability", () => { - describe("updateAvailabilitySchema", () => { - it("should allow a valid update", () => { - const updateAvailability = { - state: AvailabilityState.Confirmed, - timeSlotId: 123, - }; - const { error } = updateAvailabilitySchema.validate(updateAvailability); - expect(error).toBeDefined(); + Object.values(Role).forEach((role: Role) => { + describe("should allow only HKN members to read, create, and delete availabilities", () => { + const mockAbility = mockAbilityForAvailability({ role, sub: "123" }); + it(`should allow only Admin and Supervisors to update availabilities [${role}]`, () => { + expect( + checkAbility( + mockAbility, + Action.Update, + mockAvailability, + "Availability" + ) + ).toBe([Role.Admin, Role.Supervisor].includes(role)); + }); + it(`should allow only HKN members to read, create, and delete availabilities [${role}]`, () => { + expect( + checkAbility( + mockAbility, + Action.Read, + mockAvailability, + "Availability" + ) + ).toBe(![Role.Applicant, Role.None].includes(role)); + expect( + checkAbility( + mockAbility, + Action.Create, + mockAvailability, + "Availability" + ) + ).toBe(![Role.Applicant, Role.None].includes(role)); + expect( + checkAbility( + mockAbility, + Action.Delete, + mockAvailability, + "Availability" + ) + ).toBe(![Role.Applicant, Role.None].includes(role)); + }); }); + }); - it("should not allow updating with an invalid state", () => { - const updateAvailability = { - state: "Non_Existent_State", - timeSlotId: 123, + describe("insertAvailabilitySchema", () => { + it("should allow creating a valid availability", () => { + const validAvailability = { + timeSlotId: 1, }; - const { error } = updateAvailabilitySchema.validate(updateAvailability); - expect(error).toBeDefined(); + + const { error } = insertAvailabilitySchema.validate(validAvailability); + expect(error).toBeUndefined(); }); - it("should not allow updating with an invalid timeSlotId", () => { - const updateAvailability = { - state: AvailabilityState.Confirmed, - timeSlotId: -321, - }; - const { error } = updateAvailabilitySchema.validate(updateAvailability); + it("should not allow creating an availability without a timeSlotId", () => { + const invalidAvailability = {}; + + const { error } = insertAvailabilitySchema.validate(invalidAvailability); expect(error).toBeDefined(); }); }); diff --git a/shared/src/availability.ts b/shared/src/availability.ts index 1cd0e38..f21bb2b 100644 --- a/shared/src/availability.ts +++ b/shared/src/availability.ts @@ -1,11 +1,12 @@ +import { TimeSlot } from "./timeslot"; import { Action, ApplyAbilities } from "./abilities"; import { Person, Role } from "./person"; import * as Joi from "joi"; export enum AvailabilityState { - Subscribed = "subscribed", - Confirmed = "confirmed", - Cancelled = "cancelled", + Free = "free", + Interviewing = "interviewing", + Recovering = "recovering", } export enum AvailabilityType { @@ -14,21 +15,17 @@ export enum AvailabilityType { } export interface Availability { + id: number; state: AvailabilityState; - timeSlotId: number; - member: Person; - // assignedAt?: Date; - // confirmedAt?: Date; - // cancelledAt?: Date; + lastModified: Date; + timeSlot: TimeSlot; + user: Person; } /* Validation schemas */ -export const updateAvailabilitySchema = Joi.object({ - state: Joi.string() - .valid(...Object.values(AvailabilityType)) - .required(), - timeSlotId: Joi.number().positive().required(), +export const insertAvailabilitySchema = Joi.object({ + timeSlotId: Joi.number().required(), }).options({ stripUnknown: true, abortEarly: false, @@ -48,13 +45,12 @@ export const applyAbilitiesOnAvailability: ApplyAbilities = ( break; case Role.Member: case Role.Clerk: + can(Action.Create, "Availability"); can(Action.Read, "Availability"); - can(Action.Update, "Availability", { userId: user.sub }); + can(Action.Delete, "Availability"); + cannot(Action.Update, "Availability"); break; case Role.Applicant: - can(Action.Read, "Availability", { userId: user.sub }); - can(Action.Update, "Availability", { userId: user.sub }); - break; default: cannot(Action.Manage, "Availability"); } diff --git a/shared/src/person.ts b/shared/src/person.ts index b393cd4..ceeca23 100644 --- a/shared/src/person.ts +++ b/shared/src/person.ts @@ -19,6 +19,8 @@ export interface Person { phone_no?: string; telegramId?: string; role: Role; + is_board: boolean; + is_expert: boolean; } export const createUserSchema = Joi.object({ diff --git a/shared/src/recruitment-session.spec.ts b/shared/src/recruitment-session.spec.ts index e86cf48..b0c9a34 100644 --- a/shared/src/recruitment-session.spec.ts +++ b/shared/src/recruitment-session.spec.ts @@ -3,6 +3,7 @@ import { RecruitmentSessionState, createRecruitmentSessionSchema, applyAbilitiesOnRecruitmentSession, + updateRecruitmentSessionSchema, } from "./recruitment-session"; import { createMockAbility } from "./abilities.spec"; import { Action, UserAuth, checkAbility } from "./abilities"; @@ -13,8 +14,8 @@ describe("Recruitment Session", () => { const mockRecSess: Partial = { state: RecruitmentSessionState.Active, slotDuration: 5, - interviewStart: "11:55" as unknown as Date, - interviewEnd: "16:30" as unknown as Date, + interviewStart: "2024-10-05 11:55" as unknown as Date, + interviewEnd: "2024-10-29 16:30" as unknown as Date, days: [new Date("2024-12-23"), new Date("2024-12-23")], lastModified: new Date("2023-10-20 15:10"), }; @@ -80,4 +81,23 @@ describe("Recruitment Session", () => { expect(mockRecSess.interviewStart).toMatch("11:55"); }); }); + + describe("updateRecruitmentSessionSchema", () => { + it("should allow a valild update", () => { + const mockUpdate: Partial = { + state: RecruitmentSessionState.Concluded, + interviewEnd: "2023-10-26 19:30" as unknown as Date, + }; + expect( + updateRecruitmentSessionSchema.validate(mockUpdate) + ).not.toHaveProperty("error"); + }); + + it("should allow not to set optional fields", () => { + const mockUpdate: Partial = {}; + expect( + updateRecruitmentSessionSchema.validate(mockUpdate) + ).not.toHaveProperty("error"); + }); + }); }); diff --git a/shared/src/recruitment-session.ts b/shared/src/recruitment-session.ts index a232e18..ca1890c 100644 --- a/shared/src/recruitment-session.ts +++ b/shared/src/recruitment-session.ts @@ -27,8 +27,8 @@ export interface RecruitmentSession { export const createRecruitmentSessionSchema = Joi.object({ state: Joi.string().valid("active", "concluded").required(), slotDuration: Joi.number().integer().optional(), - interviewStart: JoiDate.date().format("HH:mm").required(), - interviewEnd: JoiDate.date().format("HH:mm").required(), + interviewStart: JoiDate.date().format("YYYY-MM-DD HH:mm").required(), + interviewEnd: JoiDate.date().format("YYYY-MM-DD HH:mm").required(), days: Joi.array().items(JoiDate.date().format("YYYY-MM-DD")).optional(), lastModified: JoiDate.date().format("YYYY-MM-DD HH:mm").required(), }).options({ diff --git a/shared/src/timeslot.ts b/shared/src/timeslot.ts index fea1951..6343754 100644 --- a/shared/src/timeslot.ts +++ b/shared/src/timeslot.ts @@ -1,28 +1,12 @@ import { Action, ApplyAbilities } from "./abilities"; import { Role } from "./person"; -import DateExtension from "@joi/date"; -import * as Joi from "joi"; -const JoiDate = Joi.extend(DateExtension); - -// import BaseJoi from "joi"; -// const Joi = BaseJoi.extend(JoiDate); export interface TimeSlot { + id: number; start: Date; end: Date; } -/* Validation schemas */ - -export const createTimeSlotSchema = Joi.object({ - start: JoiDate.date().format("YYYY-MM-DD HH:mm").required(), - end: JoiDate.date().format("YYYY-MM-DD HH:mm").required(), -}).options({ - stripUnknown: true, - abortEarly: false, - presence: "required", -}); - /* Abilities */ export const applyAbilitiesOnTimeSlot: ApplyAbilities = ( @@ -30,18 +14,17 @@ export const applyAbilitiesOnTimeSlot: ApplyAbilities = ( { can, cannot } ) => { can(Action.Manage, "TimeSlot"); - // switch (user.role) { - // case Role.Admin: - // case Role.Supervisor: - // case Role.Clerk: - // // TODO: Decide who can create/delete timeslots - // can(Action.Manage, "TimeSlot"); - // break; - // case Role.Member: - // case Role.Applicant: - // can(Action.Read, "TimeSlot"); - // break; - // default: - // cannot(Action.Manage, "TimeSlot"); - // } + switch (user.role) { + case Role.Admin: + case Role.Supervisor: + can(Action.Manage, "TimeSlot"); + break; + case Role.Clerk: + case Role.Member: + case Role.Applicant: + can(Action.Read, "TimeSlot"); + break; + default: + cannot(Action.Manage, "TimeSlot"); + } }; diff --git a/sonar-project.properties b/sonar-project.properties index 895f2fc..edb0d90 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,4 +4,4 @@ sonar.projectName=HKrecruitment sonar.javascript.lcov.reportPaths=./coverage/shared/lcov.info, ./coverage/api/lcov.info, ./coverage/api-e2e/lcov.info sonar.sources=api/, frontend/, shared/ -sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md +sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md From 5ffb921fa665a7c4fdbb0b8f3ab77aa330da1f35 Mon Sep 17 00:00:00 2001 From: white Date: Wed, 1 May 2024 18:26:27 +0200 Subject: [PATCH 02/57] add: relations between User, Availabilities --- api/src/availability/availability.entity.ts | 3 +++ api/src/recruitment-session/recruitment-session.entity.ts | 8 +++++++- api/src/timeslots/timeslot.entity.ts | 8 ++++++++ api/src/users/user.entity.ts | 4 +++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/api/src/availability/availability.entity.ts b/api/src/availability/availability.entity.ts index 384669e..a3e5f08 100644 --- a/api/src/availability/availability.entity.ts +++ b/api/src/availability/availability.entity.ts @@ -1,6 +1,7 @@ import { Column, Entity, + JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation, @@ -26,9 +27,11 @@ export class Availability implements AvailabilityInterface { @DbAwareColumn(() => TimeSlot, { name: 'time_slot' }) @ManyToOne(() => TimeSlot, (timeSlot) => timeSlot.availabilities) + @JoinColumn({ name: 'time_slot' }) timeSlot: Relation; @ManyToOne(() => User, (user) => user.availabilities) + @JoinColumn({ name: 'user_id' }) user: Relation; // @OneToOne(() => Interview) diff --git a/api/src/recruitment-session/recruitment-session.entity.ts b/api/src/recruitment-session/recruitment-session.entity.ts index df58326..6c0d65b 100644 --- a/api/src/recruitment-session/recruitment-session.entity.ts +++ b/api/src/recruitment-session/recruitment-session.entity.ts @@ -1,8 +1,9 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, Entity, JoinColumn, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { RecruitmentSession as RecruitmentSessionInterface, RecruitmentSessionState, } from '@hkrecruitment/shared'; +import { TimeSlot } from 'src/timeslots/timeslot.entity'; @Entity() export class RecruitmentSession implements RecruitmentSessionInterface { @@ -29,4 +30,9 @@ export class RecruitmentSession implements RecruitmentSessionInterface { @Column('date', { name: 'last_modified' }) lastModified: Date; + + @OneToMany(() => TimeSlot, (timeSlot) => timeSlot.recruitmentSession) + @JoinColumn({ name: 'time_slots' }) + timeSlots: TimeSlot[]; + } diff --git a/api/src/timeslots/timeslot.entity.ts b/api/src/timeslots/timeslot.entity.ts index 54911ca..dadccae 100644 --- a/api/src/timeslots/timeslot.entity.ts +++ b/api/src/timeslots/timeslot.entity.ts @@ -1,12 +1,15 @@ import { Column, Entity, + JoinColumn, + ManyToOne, OneToMany, PrimaryGeneratedColumn, Relation, } from 'typeorm'; import { TimeSlot as TimeSlotInterface } from '@hkrecruitment/shared'; import { Availability } from 'src/availability/availability.entity'; +import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; @Entity() export class TimeSlot implements TimeSlotInterface { @@ -20,5 +23,10 @@ export class TimeSlot implements TimeSlotInterface { end: Date; @OneToMany(() => Availability, (availability) => availability.timeSlot) + @JoinColumn({ name: 'availabilities' }) availabilities: Relation; + + @ManyToOne(() => RecruitmentSession, (recruitmentSession) => recruitmentSession.timeSlots) + @JoinColumn({ name: 'recruitment_session' }) + recruitmentSession: Relation; } diff --git a/api/src/users/user.entity.ts b/api/src/users/user.entity.ts index 81bd96c..a9b5ccc 100644 --- a/api/src/users/user.entity.ts +++ b/api/src/users/user.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity, OneToMany, PrimaryColumn, Relation } from 'typeorm'; +import { Column, Entity, OneToMany, PrimaryColumn, Relation, JoinColumn } from 'typeorm'; import { Person, Role } from '@hkrecruitment/shared'; import { Availability } from 'src/availability/availability.entity'; +import Joi from 'joi'; @Entity() export class User implements Person { @@ -29,6 +30,7 @@ export class User implements Person { role: Role; @OneToMany(() => Availability, (availability) => availability.user) + @JoinColumn({ name: 'availabilities' }) availabilities?: Relation; @Column() From 6f0fa2e07fd7a0f22e90bfaff95b3a8eb8730998 Mon Sep 17 00:00:00 2001 From: Vincenzo Pellegrini Date: Sun, 5 May 2024 18:11:45 +0200 Subject: [PATCH 03/57] Disable shallow clone in action --- .github/workflows/fullstack.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fullstack.yml b/.github/workflows/fullstack.yml index 48e3293..7ff50a0 100644 --- a/.github/workflows/fullstack.yml +++ b/.github/workflows/fullstack.yml @@ -15,6 +15,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Install Node.js uses: actions/setup-node@v4 @@ -72,4 +74,4 @@ jobs: uses: SonarSource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From ce9dd19899aba40f4199907d51ab66ac336a13f2 Mon Sep 17 00:00:00 2001 From: white Date: Tue, 14 May 2024 17:28:37 +0200 Subject: [PATCH 04/57] fix: entity (recsess, user, timeslot, avail) --- api/src/availability/availability.entity.ts | 7 +------ .../recruitment-session.entity.ts | 12 ++++++++---- api/src/timeslots/timeslot.entity.ts | 7 ++++--- api/src/users/user.entity.ts | 10 +++++++--- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/api/src/availability/availability.entity.ts b/api/src/availability/availability.entity.ts index a3e5f08..a9aa34f 100644 --- a/api/src/availability/availability.entity.ts +++ b/api/src/availability/availability.entity.ts @@ -1,7 +1,6 @@ import { Column, Entity, - JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation, @@ -12,7 +11,6 @@ import { } from '../../../shared/src/availability'; import { User } from 'src/users/user.entity'; import { TimeSlot } from 'src/timeslots/timeslot.entity'; -import { DbAwareColumn } from 'src/utils/db-aware-column'; @Entity() export class Availability implements AvailabilityInterface { @@ -25,15 +23,12 @@ export class Availability implements AvailabilityInterface { @Column({ name: 'last_modified' }) lastModified: Date; - @DbAwareColumn(() => TimeSlot, { name: 'time_slot' }) @ManyToOne(() => TimeSlot, (timeSlot) => timeSlot.availabilities) - @JoinColumn({ name: 'time_slot' }) timeSlot: Relation; @ManyToOne(() => User, (user) => user.availabilities) - @JoinColumn({ name: 'user_id' }) user: Relation; - // @OneToOne(() => Interview) + // @OneToOne(() => Interview, (interview) => interview.availability) // interview: Interview; } diff --git a/api/src/recruitment-session/recruitment-session.entity.ts b/api/src/recruitment-session/recruitment-session.entity.ts index 6c0d65b..aebbe13 100644 --- a/api/src/recruitment-session/recruitment-session.entity.ts +++ b/api/src/recruitment-session/recruitment-session.entity.ts @@ -1,4 +1,10 @@ -import { Column, Entity, JoinColumn, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + Entity, + Relation, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; import { RecruitmentSession as RecruitmentSessionInterface, RecruitmentSessionState, @@ -32,7 +38,5 @@ export class RecruitmentSession implements RecruitmentSessionInterface { lastModified: Date; @OneToMany(() => TimeSlot, (timeSlot) => timeSlot.recruitmentSession) - @JoinColumn({ name: 'time_slots' }) - timeSlots: TimeSlot[]; - + timeSlots?: Relation; } diff --git a/api/src/timeslots/timeslot.entity.ts b/api/src/timeslots/timeslot.entity.ts index dadccae..de1e72d 100644 --- a/api/src/timeslots/timeslot.entity.ts +++ b/api/src/timeslots/timeslot.entity.ts @@ -23,10 +23,11 @@ export class TimeSlot implements TimeSlotInterface { end: Date; @OneToMany(() => Availability, (availability) => availability.timeSlot) - @JoinColumn({ name: 'availabilities' }) availabilities: Relation; - @ManyToOne(() => RecruitmentSession, (recruitmentSession) => recruitmentSession.timeSlots) - @JoinColumn({ name: 'recruitment_session' }) + @ManyToOne( + () => RecruitmentSession, + (recruitmentSession) => recruitmentSession.timeSlots, + ) recruitmentSession: Relation; } diff --git a/api/src/users/user.entity.ts b/api/src/users/user.entity.ts index a9b5ccc..8ec7472 100644 --- a/api/src/users/user.entity.ts +++ b/api/src/users/user.entity.ts @@ -1,7 +1,12 @@ -import { Column, Entity, OneToMany, PrimaryColumn, Relation, JoinColumn } from 'typeorm'; +import { + Column, + Entity, + OneToMany, + PrimaryColumn, + Relation, +} from 'typeorm'; import { Person, Role } from '@hkrecruitment/shared'; import { Availability } from 'src/availability/availability.entity'; -import Joi from 'joi'; @Entity() export class User implements Person { @@ -30,7 +35,6 @@ export class User implements Person { role: Role; @OneToMany(() => Availability, (availability) => availability.user) - @JoinColumn({ name: 'availabilities' }) availabilities?: Relation; @Column() From 90ebd95d356868a5443f5632ce7b67a512efc9f5 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 16:16:05 +0200 Subject: [PATCH 05/57] add: find available time slots functionality, tested --- .../availability/availability.controller.ts | 5 +- api/src/availability/availability.service.ts | 28 +++ api/src/mocks/db.sql | 183 ++++++++++++++++++ api/src/mocks/requests.http | 2 + .../recruitment-session.service.ts | 33 ++++ .../timeslots/timeslots.controller.spec.ts | 30 +++ api/src/timeslots/timeslots.controller.ts | 15 +- api/src/timeslots/timeslots.service.spec.ts | 68 ++++++- api/src/timeslots/timeslots.service.ts | 109 ++++++++++- api/src/users/user.entity.ts | 8 +- api/src/users/users.service.ts | 29 +++ 11 files changed, 497 insertions(+), 13 deletions(-) create mode 100644 api/src/mocks/db.sql create mode 100644 api/src/mocks/requests.http diff --git a/api/src/availability/availability.controller.ts b/api/src/availability/availability.controller.ts index 80bd194..5087f41 100644 --- a/api/src/availability/availability.controller.ts +++ b/api/src/availability/availability.controller.ts @@ -78,8 +78,9 @@ export class AvailabilityController { const user = await this.userService.findByOauthId(req.user.sub); if (!user) throw new NotFoundException('User not found'); + /********** DISABLED BEACUSE findByUserAndTimeSlot HAS AN ERROR ************/ /* Verify availability for timeslot does not already exist */ - const existing = await this.availabilityService.findByUserAndTimeSlot( + /* const existing = await this.availabilityService.findByUserAndTimeSlot( user, timeSlot, ); @@ -87,7 +88,7 @@ export class AvailabilityController { throw new ConflictException( 'Availability already exists for this timeslot', ); - +*/ const availability = { timeSlot: timeSlot, state: AvailabilityState.Free, diff --git a/api/src/availability/availability.service.ts b/api/src/availability/availability.service.ts index 6f9904d..df91eca 100644 --- a/api/src/availability/availability.service.ts +++ b/api/src/availability/availability.service.ts @@ -16,10 +16,19 @@ export class AvailabilityService { private dataSource: DataSource, ) {} + /** + * List all availabilities + * @returns {Promise} - List of availabilities + */ async listAvailabilities(): Promise { return await this.availabilityRepository.find(); } + /** + * Find all availabilities for a given user + * @param user - User to find availabilities for + * @returns {Promise} - List of availabilities for the user + */ async findById(id: number): Promise { const matches = await this.availabilityRepository.findBy({ id: id, @@ -27,6 +36,7 @@ export class AvailabilityService { return matches.length > 0 ? matches[0] : null; } + /******** THIS FUNCTION GETS AN ERROR AT COMPILE TIME ********* async findByUserAndTimeSlot(user: User, timeSlot: TimeSlot) { const matches = await this.availabilityRepository.findBy({ user: user, @@ -34,11 +44,23 @@ export class AvailabilityService { }); return matches.length > 0 ? matches[0] : null; } +*/ + /** + * Create an availability + * @param availability - Availability to create + * @returns {Promise} - Created availability + */ async createAvailability(availability: Availability): Promise { return await this.availabilityRepository.save(availability); } + /** + * Update an availability + * @param oldAvailabilityId - ID of the + * @param newAvailability - New availability + * @returns {Promise} - Updated availability + */ async updateAvailability( oldAvailabilityId: number, newAvailability: Availability, @@ -49,6 +71,12 @@ export class AvailabilityService { }); } + /** + * Delete an availability + * @param availabilityId - ID of the availability to delete + * @returns {Promise} - Deleted availability + * @throws {ConflictException} - If availability is in use + */ async deleteAvailability(availabilityId: number): Promise { return await transaction( this.dataSource, diff --git a/api/src/mocks/db.sql b/api/src/mocks/db.sql new file mode 100644 index 0000000..d61b0d1 --- /dev/null +++ b/api/src/mocks/db.sql @@ -0,0 +1,183 @@ +-- Active: 1715697529039@@127.0.0.1@5432@hkrecruitment@public + +# Recruitment Session +INSERT INTO "recruitment_session" + ("id","state","slot_duration","last_modified","created_at","days","interview_start","interview_end") +VALUES + (1, 'concluded', 1, '2024-04-10', '2024-04-04', ARRAY['2024-04-05', '2024-04-06', '2024-04-07', '2024-04-08', '2024-04-09']::date[], '2024-04-05', '2024-04-10'), + (2, 'concluded', 1, '2024-05-10', '2024-05-04', ARRAY['2024-05-05', '2024-05-06', '2024-05-07', '2024-05-08']::date[], '2024-05-05', '2024-05-09'), + (3, 'active', 1, '2024-05-14', '2024-05-14', ARRAY['2024-05-16', '2024-05-17', '2024-05-18', '2024-05-19', '2024-05-20']::date[], '2024-05-16', '2024-05-21') +; + + + +# Time Slots +INSERT INTO "time_slot" + ("id","start","end","recruitmentSessionId") +VALUES + (1, '2024-04-05 10:00:00', '2024-05-21 11:00:00', 1), + (2, '2024-04-05 11:00:00', '2024-05-21 12:00:00', 1), + (3, '2024-04-05 15:00:00', '2024-05-21 16:00:00', 1), + (4, '2024-04-05 16:00:00', '2024-05-21 17:00:00', 1), + (5, '2024-04-05 17:00:00', '2024-05-21 18:00:00', 1), + (6, '2024-04-05 18:00:00', '2024-05-21 19:00:00', 1), + (7, '2024-04-06 10:00:00', '2024-06-21 11:00:00', 1), + (8, '2024-04-06 11:00:00', '2024-06-21 12:00:00', 1), + (9, '2024-04-06 15:00:00', '2024-06-21 16:00:00', 1), + (10, '2024-04-06 16:00:00', '2024-06-21 17:00:00', 1), + (11, '2024-04-06 17:00:00', '2024-06-21 18:00:00', 1), + (12, '2024-04-06 18:00:00', '2024-06-21 19:00:00', 1), + (13, '2024-04-07 10:00:00', '2024-07-21 11:00:00', 1), + (14, '2024-04-07 11:00:00', '2024-07-21 12:00:00', 1), + (15, '2024-04-07 15:00:00', '2024-07-21 16:00:00', 1), + (16, '2024-04-07 16:00:00', '2024-07-21 17:00:00', 1), + (17, '2024-04-07 17:00:00', '2024-07-21 18:00:00', 1), + (18, '2024-04-07 18:00:00', '2024-07-21 19:00:00', 1), + (19, '2024-04-08 10:00:00', '2024-08-21 11:00:00', 1), + (20, '2024-04-08 11:00:00', '2024-08-21 12:00:00', 1), + (21, '2024-04-08 15:00:00', '2024-08-21 16:00:00', 1), + (22, '2024-04-08 16:00:00', '2024-08-21 17:00:00', 1), + (23, '2024-04-08 17:00:00', '2024-08-21 18:00:00', 1), + (24, '2024-04-08 18:00:00', '2024-08-21 19:00:00', 1), + (25, '2024-04-09 10:00:00', '2024-09-21 11:00:00', 1), + (26, '2024-04-09 11:00:00', '2024-09-21 12:00:00', 1), + (27, '2024-04-09 15:00:00', '2024-09-21 16:00:00', 1), + (28, '2024-04-09 16:00:00', '2024-09-21 17:00:00', 1), + (29, '2024-04-09 17:00:00', '2024-09-21 18:00:00', 1), + (30, '2024-04-09 18:00:00', '2024-09-21 19:00:00', 1), + (31, '2024-05-05 10:00:00', '2024-05-05 11:00:00', 2), + (32, '2024-05-05 11:00:00', '2024-05-05 12:00:00', 2), + (33, '2024-05-05 15:00:00', '2024-05-05 16:00:00', 2), + (34, '2024-05-05 16:00:00', '2024-05-05 17:00:00', 2), + (35, '2024-05-05 17:00:00', '2024-05-05 18:00:00', 2), + (36, '2024-05-05 18:00:00', '2024-05-05 19:00:00', 2), + (37, '2024-05-06 10:00:00', '2024-05-06 11:00:00', 2), + (38, '2024-05-06 11:00:00', '2024-05-06 12:00:00', 2), + (39, '2024-05-06 15:00:00', '2024-05-06 16:00:00', 2), + (40, '2024-05-06 16:00:00', '2024-05-06 17:00:00', 2), + (41, '2024-05-06 17:00:00', '2024-05-06 18:00:00', 2), + (42, '2024-05-06 18:00:00', '2024-05-06 19:00:00', 2), + (43, '2024-05-07 10:00:00', '2024-05-07 11:00:00', 2), + (44, '2024-05-07 11:00:00', '2024-05-07 12:00:00', 2), + (45, '2024-05-07 15:00:00', '2024-05-07 16:00:00', 2), + (46, '2024-05-07 16:00:00', '2024-05-07 17:00:00', 2), + (47, '2024-05-07 17:00:00', '2024-05-07 18:00:00', 2), + (48, '2024-05-07 18:00:00', '2024-05-07 19:00:00', 2), + (49, '2024-05-08 10:00:00', '2024-05-08 11:00:00', 2), + (50, '2024-05-08 11:00:00', '2024-05-08 12:00:00', 2), + (51, '2024-05-16 10:00:00', '2024-05-16 11:00:00', 3), + (52, '2024-05-16 11:00:00', '2024-05-16 12:00:00', 3), + (53, '2024-05-16 15:00:00', '2024-05-16 16:00:00', 3), + (54, '2024-05-16 16:00:00', '2024-05-16 17:00:00', 3), + (55, '2024-05-16 17:00:00', '2024-05-16 18:00:00', 3), + (56, '2024-05-16 18:00:00', '2024-05-16 19:00:00', 3), + (57, '2024-05-17 10:00:00', '2024-05-17 11:00:00', 3), + (58, '2024-05-17 11:00:00', '2024-05-17 12:00:00', 3), + (59, '2024-05-17 15:00:00', '2024-05-17 16:00:00', 3), + (60, '2024-05-17 16:00:00', '2024-05-17 17:00:00', 3), + (61, '2024-05-17 17:00:00', '2024-05-17 18:00:00', 3), + (62, '2024-05-17 18:00:00', '2024-05-17 19:00:00', 3), + (63, '2024-05-18 10:00:00', '2024-05-18 11:00:00', 3), + (64, '2024-05-18 11:00:00', '2024-05-18 12:00:00', 3), + (65, '2024-05-18 15:00:00', '2024-05-18 16:00:00', 3), + (66, '2024-05-18 16:00:00', '2024-05-18 17:00:00', 3), + (67, '2024-05-18 17:00:00', '2024-05-18 18:00:00', 3), + (68, '2024-05-18 18:00:00', '2024-05-18 19:00:00', 3), + (69, '2024-05-19 10:00:00', '2024-05-19 11:00:00', 3), + (70, '2024-05-19 11:00:00', '2024-05-19 12:00:00', 3), + (71, '2024-05-19 15:00:00', '2024-05-19 16:00:00', 3), + (72, '2024-05-19 16:00:00', '2024-05-19 17:00:00', 3), + (73, '2024-05-19 17:00:00', '2024-05-19 18:00:00', 3), + (74, '2024-05-19 18:00:00', '2024-05-19 19:00:00', 3), + (75, '2024-05-20 10:00:00', '2024-05-20 11:00:00', 3), + (76, '2024-05-20 11:00:00', '2024-05-20 12:00:00', 3), + (77, '2024-05-20 15:00:00', '2024-05-20 16:00:00', 3), + (78, '2024-05-20 16:00:00', '2024-05-20 17:00:00', 3), + (79, '2024-05-20 17:00:00', '2024-05-20 18:00:00', 3), + (80, '2024-05-20 18:00:00', '2024-05-20 19:00:00', 3) +; + + +INSERT INTO "user" + ("oauthId","firstName","lastName","sex","email","phone_no","telegramId","role","is_board","is_expert") +VALUES + ('1', 'Pasquale', 'Bianco', 'male', 'p.bianco@gmail.com', '3405174444', 'pbianco', 'member', true, true), + ('2', 'John', 'Doe', 'male', 'j.doe@gmail.com', '1234567890', 'jdoe', 'member', false, true), + ('3', 'Jane', 'Smith', 'female', 'j.smith@gmail.com', '9876543210', 'jsmith', 'member', false, false), + ('4', 'Michael', 'Johnson', 'male', 'm.johnson@gmail.com', '5555555555', 'mjohnson', 'member', true, true), + ('5', 'Emily', 'Brown', 'female', 'e.brown@gmail.com', '1111111111', 'ebrown', 'member', false, true), + ('6', 'David', 'Wilson', 'male', 'd.wilson@gmail.com', '2222222222', 'dwilson', 'member', false, false), + ('7', 'Olivia', 'Johnson', 'female', 'o.johnson@gmail.com', '3333333333', 'ojohnson', 'member', true, false), + ('8', 'James', 'Smith', 'male', 'j.smith@gmail.com', '4444444444', 'jsmith', 'member', false, false), + ('9', 'Sophia', 'Miller', 'female', 's.miller@gmail.com', '5555555555', 'smiller', 'member', false, true), + ('10', 'Benjamin', 'Davis', 'male', 'b.davis@gmail.com', '6666666666', 'bdavis', 'member', false, false), + ('11', 'Ava', 'Wilson', 'female', 'a.wilson@gmail.com', '7777777777', 'awilson', 'member', true, true), + ('12', 'William', 'Anderson','male', 'w.anderson@gmail.com', '8888888888', 'wanderson', 'member', false, false), + ('13', 'Mia', 'Thomas', 'female', 'm.thomas@gmail.com', '9999999999', 'mthomas', 'member', false, false), + ('14', 'Alexander', 'Taylor', 'male', 'a.taylor@gmail.com', '1111111111', 'ataylor', 'member', false, true), + ('15', 'Charlotte', 'Clark', 'female', 'c.clark@gmail.com', '2222222222', 'cclark', 'member', false, false), + ('16', 'Daniel', 'Moore', 'male', 'd.moore@gmail.com', '3333333333', 'dmoore', 'member', false, false), + ('17', 'Amelia', 'Walker', 'female', 'a.walker@gmail.com', '4444444444', 'awalker', 'clerk', false, false), + ('18', 'Matthew', 'Lewis', 'male', 'm.lewis@gmail.com', '5555555555', 'mlewis', 'admin', false, true), + ('19', 'Ella', 'Harris', 'female', 'e.harris@gmail.com', '6666666666', 'eharris', 'supervisor', false, true), + ('20', 'Joseph', 'King', 'male', 'j.king@gmail.com', '7777777777', 'jking', 'supervisor', true, false) + + +# Availability +INSERT INTO "availability" + ("id","state","last_modified","timeSlotId","userOauthId") +VALUES + (1, 'free' ,'2024-05-14 00:00:00', 51, '5' ), + (2, 'free' ,'2024-05-14 00:00:00', 53, '7' ), + (3, 'free' ,'2024-05-14 00:00:00', 55, '12'), + (4, 'recovering' ,'2024-05-14 00:00:00', 61, '19'), + (5, 'free' ,'2024-05-14 00:00:00', 71, '3' ), + (6, 'free' ,'2024-05-14 00:00:00', 78, '15'), + (7, 'free' ,'2024-05-14 00:00:00', 58, '11'), + (8, 'free' ,'2024-05-14 00:00:00', 57, '8' ), + (9, 'free' ,'2024-05-14 00:00:00', 64, '5' ), + (10, 'recovering' ,'2024-05-14 00:00:00', 53, '17'), + (11, 'free' ,'2024-05-14 00:00:00', 62, '2' ), + (12, 'free' ,'2024-05-14 00:00:00', 71, '9' ), + (13, 'recovering' ,'2024-05-14 00:00:00', 72, '14'), + (14, 'free' ,'2024-05-14 00:00:00', 74, '6' ), + (15, 'free' ,'2024-05-14 00:00:00', 75, '13'), + (16, 'recovering' ,'2024-05-14 00:00:00', 74, '10'), + (17, 'free' ,'2024-05-14 00:00:00', 71, '4' ), + (18, 'free' ,'2024-05-14 00:00:00', 64, '16'), + (19, 'recovering' ,'2024-05-14 00:00:00', 59, '18'), + (20, 'free' ,'2024-05-14 00:00:00', 57, '1' ), + (21, 'interviewing' ,'2024-05-14 00:00:00', 66, '20'), + (22, 'recovering' ,'2024-05-14 00:00:00', 68, '19'), + (23, 'free' ,'2024-05-14 00:00:00', 69, '7' ), + (24, 'interviewing' ,'2024-05-14 00:00:00', 70, '16'), + (25, 'recovering' ,'2024-05-14 00:00:00', 72, '15'), + (26, 'free' ,'2024-05-14 00:00:00', 73, '11'), + (27, 'free' ,'2024-05-14 00:00:00', 75, '14'), + (28, 'recovering' ,'2024-05-14 00:00:00', 76, '9' ), + (29, 'free' ,'2024-05-14 00:00:00', 77, '8' ), + (30, 'free' ,'2024-05-14 00:00:00', 78, '10'), + (31, 'free' ,'2024-05-14 00:00:00', 79, '13'), + (32, 'free' ,'2024-05-14 00:00:00', 80, '12'), + (33, 'interviewing' ,'2024-05-14 00:00:00', 79, '20'), + (34, 'free' ,'2024-05-14 00:00:00', 78, '18'), + (35, 'free' ,'2024-05-14 00:00:00', 77, '17'), + (36, 'interviewing' ,'2024-05-14 00:00:00', 76, '19'), + (37, 'free' ,'2024-05-14 00:00:00', 75, '16'), + (38, 'free' ,'2024-05-14 00:00:00', 74, '15'), + (39, 'free' ,'2024-05-14 00:00:00', 73, '14'), + (40, 'free' ,'2024-05-14 00:00:00', 72, '13'), + (41, 'free' ,'2024-05-14 00:00:00', 71, '12'), + (42, 'interviewing' ,'2024-05-14 00:00:00', 70, '11'), + (43, 'recovering' ,'2024-05-14 00:00:00', 69, '10'), + (44, 'free' ,'2024-05-14 00:00:00', 68, '9' ), + (45, 'interviewing' ,'2024-05-14 00:00:00', 67, '8' ), + (46, 'free' ,'2024-05-14 00:00:00', 66, '7' ), + (47, 'free' ,'2024-05-14 00:00:00', 65, '6' ), + (48, 'interviewing' ,'2024-05-14 00:00:00', 64, '5' ), + (49, 'recovering' ,'2024-05-14 00:00:00', 63, '8' ), + (50, 'free' ,'2024-05-14 00:00:00', 62, '14'), + (51, 'free' ,'2024-05-14 00:00:00', 61, '4' ), + (52, 'free' ,'2024-05-14 00:00:00', 60, '13') +; + diff --git a/api/src/mocks/requests.http b/api/src/mocks/requests.http new file mode 100644 index 0000000..74e9e9e --- /dev/null +++ b/api/src/mocks/requests.http @@ -0,0 +1,2 @@ +GET http://localhost:3000/v1/timeslots/ +Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc4aFdBRTB0U00tX3JJVUR0WElNMyJ9.eyJlbWFpbCI6InBhc3F1YWxlLmJpYW5jb0Boa25wb2xpdG8ub3JnIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYyMjgyMTU0MzYxNTk5ODcwNTEiLCJhdWQiOlsiaHR0cDovL2hrcmVjcnVpdG1lbnQub3JnIiwiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzE2MTEzNTk3LCJleHAiOjE3MTYxOTk5OTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJaekNWd2R2eUJOUWZKc3R1ZUJPcVh3TW1jazZCa0d4NiJ9.PyyW67LJIWc9NbMibzvzfxlY0Pe1YTsZVsV2--ckBQ5hfYsCKqCUi6aIjmiliz_uZO5qdPAsZ7FYNOtOI8fGhPt9QfKSqA166D-CZWnpuwE4cM_EjbpCoHRRQ49r2Ycv_ZnzVXrO6zCyEVZYgx-USGiuBnRcKbIPDJCS4Tc9bcmklcUdq4ujAPtSDdmid9jcVN3DM5eLtXAQuYIUho3G3NRVuKlUnoNIsK16yrKtx5kgOHKuUP6dFYExpp2ViB4oZV-v_linCsXZEE5x-7GBMntJAGtenCs-1uihB-OGLYJY_6fPsRG2rgw5Fl8Ax0Ioc8LeXkwlJ2L-MO1ms-r8uQ \ No newline at end of file diff --git a/api/src/recruitment-session/recruitment-session.service.ts b/api/src/recruitment-session/recruitment-session.service.ts index 670a9e6..8a982f2 100644 --- a/api/src/recruitment-session/recruitment-session.service.ts +++ b/api/src/recruitment-session/recruitment-session.service.ts @@ -17,6 +17,11 @@ export class RecruitmentSessionService { private dataSource: DataSource, ) {} + /** + * Create a recruitment session + * @param recruitmentSession - Recruitment session to create + * @returns {Promise} - Created recruitment session + */ async createRecruitmentSession( recruitmentSession: CreateRecruitmentSessionDto, ): Promise { @@ -40,10 +45,19 @@ export class RecruitmentSessionService { }); } + /** + * List all recruitment sessions + * @returns {Promise} - List of recruitment sessions + */ async findAllRecruitmentSessions(): Promise { return await this.recruitmentSessionRepository.find(); } + /** + * Find a recruitment session by its ID + * @param RSid - ID of the recruitment session + * @returns {Promise} - Recruitment session with the given ID + */ async findRecruitmentSessionById( RSid: number, ): Promise { @@ -53,6 +67,10 @@ export class RecruitmentSessionService { return matches.length > 0 ? matches[0] : null; } + /** + * Find an active recruitment session + * @returns {Promise} - Active recruitment session + */ async findActiveRecruitmentSession(): Promise { const matches = await this.recruitmentSessionRepository.findBy({ state: RecruitmentSessionState.Active, @@ -60,18 +78,33 @@ export class RecruitmentSessionService { return matches.length > 0 ? matches[0] : null; } + /** + * Delete a recruitment session + * @param recruitmentSession - Recruitment session to delete + * @returns {Promise} - Deleted recruitment session + */ async deleteRecruitmentSession( recruitmentSession: RecruitmentSession, ): Promise { return await this.recruitmentSessionRepository.remove(recruitmentSession); } + /** + * Update a recruitment session + * @param recruitmentSession - Recruitment session to update + * @returns {Promise} - Updated recruitment session + */ async updateRecruitmentSession( recruitmentSession: RecruitmentSession, ): Promise { return await this.recruitmentSessionRepository.save(recruitmentSession); } + /** + * Check if a recruitment session has pending interviews + * @param recruitmentSession - Recruitment session to check + * @returns {Promise} - True if the recruitment session has pending interviews, false otherwise + */ async sessionHasPendingInterviews( recruitmentSession: RecruitmentSession, ): Promise { diff --git a/api/src/timeslots/timeslots.controller.spec.ts b/api/src/timeslots/timeslots.controller.spec.ts index 0c1c16d..da039cc 100644 --- a/api/src/timeslots/timeslots.controller.spec.ts +++ b/api/src/timeslots/timeslots.controller.spec.ts @@ -1,6 +1,8 @@ import { TestBed } from '@automock/jest'; import { TimeSlotsController } from './timeslots.controller'; import { TimeSlotsService } from './timeslots.service'; +import { TimeSlot } from './timeslot.entity'; +import { Test } from '@nestjs/testing'; describe('TimeSlotController', () => { let controller: TimeSlotsController; @@ -18,4 +20,32 @@ describe('TimeSlotController', () => { expect(controller).toBeDefined(); expect(service).toBeDefined(); }); + + describe('findAvailableTimeSlots', () => { + it('should return an array of available time slots', async () => { + const expectedTimeSlots: TimeSlot[] = [ + { + id: 71, + start: new Date('2024-05-19T13:00:00.000Z'), + end: new Date('2024-05-19T14:00:00.000Z'), + } as TimeSlot, + { + id: 73, + start: new Date('2024-05-19T15:00:00.000Z'), + end: new Date('2024-05-19T16:00:00.000Z'), + } as TimeSlot, + ]; + + jest + .spyOn(service, 'findAvailableTimeSlots') + .mockResolvedValue(expectedTimeSlots); + + // Act + const result = await controller.findAvailableTimeSlots(); + + // Assert + expect(result).toEqual(expectedTimeSlots); + expect(service.findAvailableTimeSlots).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/api/src/timeslots/timeslots.controller.ts b/api/src/timeslots/timeslots.controller.ts index 9580b8d..05311bc 100644 --- a/api/src/timeslots/timeslots.controller.ts +++ b/api/src/timeslots/timeslots.controller.ts @@ -1,10 +1,21 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Get, Post } from '@nestjs/common'; import { TimeSlotsService } from './timeslots.service'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { TimeSlot } from './timeslot.entity'; @ApiBearerAuth() @ApiTags('timeslots') @Controller('timeslots') export class TimeSlotsController { constructor(private readonly timeSlotsService: TimeSlotsService) {} + + @ApiUnauthorizedResponse() + @Get() + async findAvailableTimeSlots(): Promise { + return await this.timeSlotsService.findAvailableTimeSlots(); + } } diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index 6284724..cb95d62 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -5,6 +5,11 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { TimeSlot } from './timeslot.entity'; import { TimeSlotsService } from './timeslots.service'; import { mockDataSource } from 'src/mocks/data-sources'; +import { + AvailabilityState, + RecruitmentSessionState, + Role, +} from '@hkrecruitment/shared'; describe('TimeSlotsService', () => { let timeSlotService: TimeSlotsService; @@ -43,7 +48,9 @@ describe('TimeSlotsService', () => { describe('deleteTimeSlot', () => { it('should remove the specified timeslot from the database', async () => { jest.spyOn(mockedRepository, 'remove').mockResolvedValue(mockTimeSlot); - const result = await timeSlotService.deleteTimeSlot(mockTimeSlot); + const result = await timeSlotService.deleteTimeSlot( + mockTimeSlot as TimeSlot, + ); expect(result).toEqual(mockTimeSlot); expect(mockedRepository.remove).toHaveBeenCalledTimes(1); }); @@ -188,6 +195,65 @@ describe('TimeSlotsService', () => { ); }); }); + + describe('findAvailableTimeSlots', () => { + it('should correctly call all functions provided for database query', async () => { + // Mock the query builder and its methods + const mockQueryBuilder = { + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([]), + }; + + // Mock the timeSlotRepository and its methods + const mockTimeSlotRepository = { + createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), + }; + + const timeSlotService = new TimeSlotsService( + mockTimeSlotRepository as any, + ); + const result = await timeSlotService.findAvailableTimeSlots(); + + // Assert that the query builder methods were called correctly + expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith( + 'TimeSlot.availabilities', + 'availability', + ); + expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith( + 'TimeSlot.recruitmentSession', + 'recruitmentSession', + ); + expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith( + 'availability.user', + 'user', + ); + expect(mockQueryBuilder.where).toHaveBeenCalledWith( + 'recruitmentSession.state = :recruitmentSessionState', + { + recruitmentSessionState: RecruitmentSessionState.Active, + }, + ); + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + 'user.role NOT IN (:...roles)', + { + roles: [Role.None, Role.Applicant], + }, + ); + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + 'availability.state = :availabilityState AND (user.is_board = true OR user.is_expert = true)', + { + availabilityState: AvailabilityState.Free, + }, + ); + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', + ); + + expect(result).toEqual([]); + }); + }); }); function testTimeSlotsGeneration( diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 7eb37c0..20a5cb1 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -2,8 +2,13 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, LessThan, MoreThan, QueryRunner } from 'typeorm'; import { TimeSlot } from './timeslot.entity'; -import { RecruitmentSession } from '@hkrecruitment/shared'; +import { + RecruitmentSession, + RecruitmentSessionState, +} from '@hkrecruitment/shared'; import { CreateTimeSlotDto } from './create-timeslot.dto'; +import { Role } from '@hkrecruitment/shared/person'; +import { AvailabilityState } from '@hkrecruitment/shared/availability'; @Injectable() export class TimeSlotsService { @@ -12,6 +17,12 @@ export class TimeSlotsService { private readonly timeSlotRepository: Repository, ) {} + /** + * Count the number of overlapping time slots + * @param startDate - Start date of the time slot + * @param endDate - End date of the time slot + * @returns {Promise} - Number of overlapping time slots + */ async countOverlappingTimeSlots( startDate: Date, endDate: Date, @@ -34,14 +45,28 @@ export class TimeSlotsService { return count; } + /** + * List all time slots + * @returns {Promise} - List of time slots + */ async listTimeSlots(): Promise { return await this.timeSlotRepository.find(); } + /** + * Delete a time slot + * @param timeSlot - Time slot to delete + * @returns {Promise} - Deleted time slot + */ async deleteTimeSlot(timeSlot: TimeSlot): Promise { return await this.timeSlotRepository.remove(timeSlot); } + /** + * Find a time slot by its ID + * @param timeSlotId - ID of the time slot + * @returns {Promise} - Time slot with the given ID + */ async findById(timeSlotId: number): Promise { const matches = await this.timeSlotRepository.findBy({ id: timeSlotId, @@ -49,10 +74,21 @@ export class TimeSlotsService { return matches.length > 0 ? matches[0] : null; } + /** + * Create a time slot + * @param timeSlot - Time slot to create + * @returns {Promise} - Created time slot + */ async createTimeSlot(timeSlot: CreateTimeSlotDto): Promise { return await this.timeSlotRepository.save(timeSlot); } + /** + * Create time slots for a recruitment session + * @param queryRunner - Query runner + * @param recruitmentSession - Recruitment session + * @returns {Promise} - List of created time slots + */ async createRecruitmentSessionTimeSlots( queryRunner: QueryRunner, recruitmentSession: RecruitmentSession, @@ -68,6 +104,14 @@ export class TimeSlotsService { return await queryRunner.manager.getRepository(TimeSlot).save(timeSlots); } + /** + * Generate time slots for a recruitment session + * @param slotDuration - Duration of each time slot in minutes + * @param interviewStart - Start time of the interview + * @param interviewEnd - End time of the interview + * @param days - Days of the week the interview will be held + * @returns {TimeSlot[]} - List of generated time slots + */ generateTimeSlots( slotDuration: number, interviewStart: Date, @@ -105,4 +149,67 @@ export class TimeSlotsService { return timeSlots; } + + /** + * Find available time slots for the current recruitment session + * @returns {TimeSlot[]} - List of available time slots + */ + async findAvailableTimeSlots(): Promise { + const queryBuilder = this.timeSlotRepository.createQueryBuilder('TimeSlot'); + queryBuilder + .leftJoinAndSelect('TimeSlot.availabilities', 'availability') + .leftJoinAndSelect('TimeSlot.recruitmentSession', 'recruitmentSession') + .leftJoinAndSelect('availability.user', 'user') + + // only active recruitment sessions (the current one) + .where('recruitmentSession.state = :recruitmentSessionState', { + recruitmentSessionState: RecruitmentSessionState.Active, + }) + + // available people should be members of hkn + .andWhere('user.role NOT IN (:...roles)', { + roles: [Role.None, Role.Applicant], + }) + + // only free people that are board OR expert member + .andWhere( + 'availability.state = :availabilityState AND (user.is_board = true OR user.is_expert = true)', + { + availabilityState: AvailabilityState.Free, + }, + ) + + // there should be at least 2 available people (hopefully one of them is a board member) + .andWhere( + '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', + ); + + const allMatches = await queryBuilder.getMany(); + + let goodTimeSlots: TimeSlot[] = []; + allMatches.forEach((timeSlot) => { + let boardMembers = 0; + let expertMembers = 0; + for (let availability of timeSlot.availabilities) { + // redundant checks + if (availability.state !== AvailabilityState.Free) continue; + if (availability.user.role === Role.None) continue; + if (availability.user.role === Role.Applicant) continue; + + if (availability.user.is_board) ++boardMembers; + else if (availability.user.is_expert) ++expertMembers; + } + + if ((boardMembers && expertMembers) || boardMembers > 1) { + const timeslotToPush = { + id: timeSlot.id, + start: timeSlot.start, + end: timeSlot.end, + } as TimeSlot; + goodTimeSlots.push(timeslotToPush); + } + }); + + return goodTimeSlots; + } } diff --git a/api/src/users/user.entity.ts b/api/src/users/user.entity.ts index 8ec7472..81bd96c 100644 --- a/api/src/users/user.entity.ts +++ b/api/src/users/user.entity.ts @@ -1,10 +1,4 @@ -import { - Column, - Entity, - OneToMany, - PrimaryColumn, - Relation, -} from 'typeorm'; +import { Column, Entity, OneToMany, PrimaryColumn, Relation } from 'typeorm'; import { Person, Role } from '@hkrecruitment/shared'; import { Availability } from 'src/availability/availability.entity'; diff --git a/api/src/users/users.service.ts b/api/src/users/users.service.ts index a068892..8eb1fd8 100644 --- a/api/src/users/users.service.ts +++ b/api/src/users/users.service.ts @@ -11,26 +11,55 @@ export class UsersService { private readonly userRepository: Repository, ) {} + /** + * Find all users + * @returns {Promise} - List of users + */ async findAll(): Promise { return this.userRepository.find(); } + /** + * Find a user by their OAuth ID + * @param {string} oauthId - OAuth ID of the user + * @returns {Promise} - User with the given OAuth ID + */ async findByOauthId(oauthId: string): Promise { return this.userRepository.findOne({ where: { oauthId } }); } + /** + * Delete a user + * @param {User} user - User to delete + * @returns {Promise} - Deleted user + */ async delete(user: User): Promise { return this.userRepository.remove(user); } + /** + * Create a user + * @param {User} user - User to create + * @returns {Promise} - Created user + */ async create(user: User): Promise { return this.userRepository.save(user); } + /** + * Update a user + * @param {User} user - User to update + * @returns {Promise} - Updated user + */ async update(user: User): Promise { return this.userRepository.save(user); } + /** + * Get the role and abilities for a user + * @param {string} oauthId - OAuth ID of the user + * @returns {[Role, AppAbility]} - Role and abilities for the user + */ async getRoleAndAbilityForOauthId( oauthId: string, ): Promise<[Role, AppAbility]> { From 59c3f48ce295c020e9d113ff6a95918f5b3af438 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 16:38:58 +0200 Subject: [PATCH 06/57] fix: previous simplification for dev --- api/src/availability/availability.controller.ts | 5 ++--- api/src/availability/availability.service.ts | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/src/availability/availability.controller.ts b/api/src/availability/availability.controller.ts index 5087f41..5e95cab 100644 --- a/api/src/availability/availability.controller.ts +++ b/api/src/availability/availability.controller.ts @@ -78,9 +78,8 @@ export class AvailabilityController { const user = await this.userService.findByOauthId(req.user.sub); if (!user) throw new NotFoundException('User not found'); - /********** DISABLED BEACUSE findByUserAndTimeSlot HAS AN ERROR ************/ /* Verify availability for timeslot does not already exist */ - /* const existing = await this.availabilityService.findByUserAndTimeSlot( + const existing = await this.availabilityService.findByUserAndTimeSlot( user, timeSlot, ); @@ -88,7 +87,7 @@ export class AvailabilityController { throw new ConflictException( 'Availability already exists for this timeslot', ); -*/ + const availability = { timeSlot: timeSlot, state: AvailabilityState.Free, diff --git a/api/src/availability/availability.service.ts b/api/src/availability/availability.service.ts index df91eca..66a0a23 100644 --- a/api/src/availability/availability.service.ts +++ b/api/src/availability/availability.service.ts @@ -36,7 +36,6 @@ export class AvailabilityService { return matches.length > 0 ? matches[0] : null; } - /******** THIS FUNCTION GETS AN ERROR AT COMPILE TIME ********* async findByUserAndTimeSlot(user: User, timeSlot: TimeSlot) { const matches = await this.availabilityRepository.findBy({ user: user, @@ -44,7 +43,7 @@ export class AvailabilityService { }); return matches.length > 0 ? matches[0] : null; } -*/ + /** * Create an availability From 74b0d00ceb86402dc1a09ea120e00b937e1e5705 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 16:41:47 +0200 Subject: [PATCH 07/57] format --- api/src/availability/availability.controller.ts | 2 +- api/src/availability/availability.service.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/availability/availability.controller.ts b/api/src/availability/availability.controller.ts index 5e95cab..80bd194 100644 --- a/api/src/availability/availability.controller.ts +++ b/api/src/availability/availability.controller.ts @@ -79,7 +79,7 @@ export class AvailabilityController { if (!user) throw new NotFoundException('User not found'); /* Verify availability for timeslot does not already exist */ - const existing = await this.availabilityService.findByUserAndTimeSlot( + const existing = await this.availabilityService.findByUserAndTimeSlot( user, timeSlot, ); diff --git a/api/src/availability/availability.service.ts b/api/src/availability/availability.service.ts index 66a0a23..1f8693c 100644 --- a/api/src/availability/availability.service.ts +++ b/api/src/availability/availability.service.ts @@ -44,7 +44,6 @@ export class AvailabilityService { return matches.length > 0 ? matches[0] : null; } - /** * Create an availability * @param availability - Availability to create From 019f24a1ebc486843afeeb00d1f21e2b3466e110 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 16:49:39 +0200 Subject: [PATCH 08/57] fix: minor --- api/src/availability/availability.service.ts | 4 ++-- api/src/timeslots/timeslots.service.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/availability/availability.service.ts b/api/src/availability/availability.service.ts index 1f8693c..6caa4aa 100644 --- a/api/src/availability/availability.service.ts +++ b/api/src/availability/availability.service.ts @@ -38,8 +38,8 @@ export class AvailabilityService { async findByUserAndTimeSlot(user: User, timeSlot: TimeSlot) { const matches = await this.availabilityRepository.findBy({ - user: user, - timeSlot: timeSlot, + user: user as any, + timeSlot: timeSlot as any, }); return matches.length > 0 ? matches[0] : null; } diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 20a5cb1..9782c6f 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -7,7 +7,7 @@ import { RecruitmentSessionState, } from '@hkrecruitment/shared'; import { CreateTimeSlotDto } from './create-timeslot.dto'; -import { Role } from '@hkrecruitment/shared/person'; +import { Role } from '@hkrecruitment/shared'; import { AvailabilityState } from '@hkrecruitment/shared/availability'; @Injectable() From dc04c2fdf496232195b5abe4ed62788778e3fa02 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 16:54:47 +0200 Subject: [PATCH 09/57] fix: minor 2 --- api/src/timeslots/timeslots.controller.spec.ts | 1 - api/src/timeslots/timeslots.controller.ts | 2 +- api/src/timeslots/timeslots.service.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/src/timeslots/timeslots.controller.spec.ts b/api/src/timeslots/timeslots.controller.spec.ts index da039cc..2ab30d8 100644 --- a/api/src/timeslots/timeslots.controller.spec.ts +++ b/api/src/timeslots/timeslots.controller.spec.ts @@ -2,7 +2,6 @@ import { TestBed } from '@automock/jest'; import { TimeSlotsController } from './timeslots.controller'; import { TimeSlotsService } from './timeslots.service'; import { TimeSlot } from './timeslot.entity'; -import { Test } from '@nestjs/testing'; describe('TimeSlotController', () => { let controller: TimeSlotsController; diff --git a/api/src/timeslots/timeslots.controller.ts b/api/src/timeslots/timeslots.controller.ts index 05311bc..90c5b84 100644 --- a/api/src/timeslots/timeslots.controller.ts +++ b/api/src/timeslots/timeslots.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post } from '@nestjs/common'; +import { Controller, Get } from '@nestjs/common'; import { TimeSlotsService } from './timeslots.service'; import { ApiBearerAuth, diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 9782c6f..0e34c88 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -5,10 +5,10 @@ import { TimeSlot } from './timeslot.entity'; import { RecruitmentSession, RecruitmentSessionState, + AvailabilityState, + Role, } from '@hkrecruitment/shared'; import { CreateTimeSlotDto } from './create-timeslot.dto'; -import { Role } from '@hkrecruitment/shared'; -import { AvailabilityState } from '@hkrecruitment/shared/availability'; @Injectable() export class TimeSlotsService { From 71c66ed7523a0ab5d4cd5a30472ea5f8b690c89f Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 18:52:38 +0200 Subject: [PATCH 10/57] fix: minor 3 --- api/src/mocks/db.sql | 47 +++++++++++------------- api/src/timeslots/create-timeslot.dto.ts | 2 +- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/api/src/mocks/db.sql b/api/src/mocks/db.sql index d61b0d1..c1cc5f2 100644 --- a/api/src/mocks/db.sql +++ b/api/src/mocks/db.sql @@ -6,8 +6,7 @@ INSERT INTO "recruitment_session" VALUES (1, 'concluded', 1, '2024-04-10', '2024-04-04', ARRAY['2024-04-05', '2024-04-06', '2024-04-07', '2024-04-08', '2024-04-09']::date[], '2024-04-05', '2024-04-10'), (2, 'concluded', 1, '2024-05-10', '2024-05-04', ARRAY['2024-05-05', '2024-05-06', '2024-05-07', '2024-05-08']::date[], '2024-05-05', '2024-05-09'), - (3, 'active', 1, '2024-05-14', '2024-05-14', ARRAY['2024-05-16', '2024-05-17', '2024-05-18', '2024-05-19', '2024-05-20']::date[], '2024-05-16', '2024-05-21') -; + (3, 'active', 1, '2024-05-14', '2024-05-14', ARRAY['2024-05-16', '2024-05-17', '2024-05-18', '2024-05-19', '2024-05-20']::date[], '2024-05-16', '2024-05-21'); @@ -94,33 +93,32 @@ VALUES (77, '2024-05-20 15:00:00', '2024-05-20 16:00:00', 3), (78, '2024-05-20 16:00:00', '2024-05-20 17:00:00', 3), (79, '2024-05-20 17:00:00', '2024-05-20 18:00:00', 3), - (80, '2024-05-20 18:00:00', '2024-05-20 19:00:00', 3) -; + (80, '2024-05-20 18:00:00', '2024-05-20 19:00:00', 3); INSERT INTO "user" ("oauthId","firstName","lastName","sex","email","phone_no","telegramId","role","is_board","is_expert") VALUES - ('1', 'Pasquale', 'Bianco', 'male', 'p.bianco@gmail.com', '3405174444', 'pbianco', 'member', true, true), - ('2', 'John', 'Doe', 'male', 'j.doe@gmail.com', '1234567890', 'jdoe', 'member', false, true), - ('3', 'Jane', 'Smith', 'female', 'j.smith@gmail.com', '9876543210', 'jsmith', 'member', false, false), - ('4', 'Michael', 'Johnson', 'male', 'm.johnson@gmail.com', '5555555555', 'mjohnson', 'member', true, true), - ('5', 'Emily', 'Brown', 'female', 'e.brown@gmail.com', '1111111111', 'ebrown', 'member', false, true), - ('6', 'David', 'Wilson', 'male', 'd.wilson@gmail.com', '2222222222', 'dwilson', 'member', false, false), - ('7', 'Olivia', 'Johnson', 'female', 'o.johnson@gmail.com', '3333333333', 'ojohnson', 'member', true, false), - ('8', 'James', 'Smith', 'male', 'j.smith@gmail.com', '4444444444', 'jsmith', 'member', false, false), - ('9', 'Sophia', 'Miller', 'female', 's.miller@gmail.com', '5555555555', 'smiller', 'member', false, true), - ('10', 'Benjamin', 'Davis', 'male', 'b.davis@gmail.com', '6666666666', 'bdavis', 'member', false, false), - ('11', 'Ava', 'Wilson', 'female', 'a.wilson@gmail.com', '7777777777', 'awilson', 'member', true, true), - ('12', 'William', 'Anderson','male', 'w.anderson@gmail.com', '8888888888', 'wanderson', 'member', false, false), - ('13', 'Mia', 'Thomas', 'female', 'm.thomas@gmail.com', '9999999999', 'mthomas', 'member', false, false), - ('14', 'Alexander', 'Taylor', 'male', 'a.taylor@gmail.com', '1111111111', 'ataylor', 'member', false, true), - ('15', 'Charlotte', 'Clark', 'female', 'c.clark@gmail.com', '2222222222', 'cclark', 'member', false, false), - ('16', 'Daniel', 'Moore', 'male', 'd.moore@gmail.com', '3333333333', 'dmoore', 'member', false, false), - ('17', 'Amelia', 'Walker', 'female', 'a.walker@gmail.com', '4444444444', 'awalker', 'clerk', false, false), - ('18', 'Matthew', 'Lewis', 'male', 'm.lewis@gmail.com', '5555555555', 'mlewis', 'admin', false, true), + ('1', 'Pasquale', 'Bianco', 'male', 'p.bianco@gmail.com', '3405174444', 'pbianco', 'member', true, true), + ('2', 'John', 'Doe', 'male', 'j.doe@gmail.com', '1234567890', 'jdoe', 'member', false, true), + ('3', 'Jane', 'Smith', 'female', 'j.smith@gmail.com', '9876543210', 'jsmith', 'member', false, false), + ('4', 'Michael', 'Johnson', 'male', 'm.johnson@gmail.com', '5555555555', 'mjohnson', 'member', true, true), + ('5', 'Emily', 'Brown', 'female', 'e.brown@gmail.com', '1111111111', 'ebrown', 'member', false, true), + ('6', 'David', 'Wilson', 'male', 'd.wilson@gmail.com', '2222222222', 'dwilson', 'member', false, false), + ('7', 'Olivia', 'Johnson', 'female', 'o.johnson@gmail.com', '3333333333', 'ojohnson', 'member', true, false), + ('8', 'James', 'Smith', 'male', 'j.smith@gmail.com', '4444444444', 'jsmith', 'member', false, false), + ('9', 'Sophia', 'Miller', 'female', 's.miller@gmail.com', '5555555555', 'smiller', 'member', false, true), + ('10', 'Benjamin', 'Davis', 'male', 'b.davis@gmail.com', '6666666666', 'bdavis', 'member', false, false), + ('11', 'Ava', 'Wilson', 'female', 'a.wilson@gmail.com', '7777777777', 'awilson', 'member', true, true), + ('12', 'William', 'Anderson','male', 'w.anderson@gmail.com', '8888888888', 'wanderson', 'member', false, false), + ('13', 'Mia', 'Thomas', 'female', 'm.thomas@gmail.com', '9999999999', 'mthomas', 'member', false, false), + ('14', 'Alexander', 'Taylor', 'male', 'a.taylor@gmail.com', '1111111111', 'ataylor', 'member', false, true), + ('15', 'Charlotte', 'Clark', 'female', 'c.clark@gmail.com', '2222222222', 'cclark', 'member', false, false), + ('16', 'Daniel', 'Moore', 'male', 'd.moore@gmail.com', '3333333333', 'dmoore', 'member', false, false), + ('17', 'Amelia', 'Walker', 'female', 'a.walker@gmail.com', '4444444444', 'awalker', 'clerk', false, false), + ('18', 'Matthew', 'Lewis', 'male', 'm.lewis@gmail.com', '5555555555', 'mlewis', 'admin', false, true), ('19', 'Ella', 'Harris', 'female', 'e.harris@gmail.com', '6666666666', 'eharris', 'supervisor', false, true), - ('20', 'Joseph', 'King', 'male', 'j.king@gmail.com', '7777777777', 'jking', 'supervisor', true, false) + ('20', 'Joseph', 'King', 'male', 'j.king@gmail.com', '7777777777', 'jking', 'supervisor', true, false); # Availability @@ -178,6 +176,5 @@ VALUES (49, 'recovering' ,'2024-05-14 00:00:00', 63, '8' ), (50, 'free' ,'2024-05-14 00:00:00', 62, '14'), (51, 'free' ,'2024-05-14 00:00:00', 61, '4' ), - (52, 'free' ,'2024-05-14 00:00:00', 60, '13') -; + (52, 'free' ,'2024-05-14 00:00:00', 60, '13'); diff --git a/api/src/timeslots/create-timeslot.dto.ts b/api/src/timeslots/create-timeslot.dto.ts index 2e7836d..704d66a 100644 --- a/api/src/timeslots/create-timeslot.dto.ts +++ b/api/src/timeslots/create-timeslot.dto.ts @@ -2,7 +2,7 @@ import { TimeSlot } from '@hkrecruitment/shared'; import { ApiProperty } from '@nestjs/swagger'; export class CreateTimeSlotDto implements Partial { - @ApiProperty() + @ApiProperty() // start: Date; @ApiProperty() From 2050e010047738a401e189e9b2f68a6a1e45ed56 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:04:41 +0200 Subject: [PATCH 11/57] add: test 2e2 --- api/src/timeslots/timeslots.e2e-spec.ts | 230 ++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 api/src/timeslots/timeslots.e2e-spec.ts diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts new file mode 100644 index 0000000..d24138c --- /dev/null +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -0,0 +1,230 @@ +import { INestApplication } from "@nestjs/common"; +import * as request from 'supertest'; +import { TimeSlotsService } from "./timeslots.service"; +import { UsersService } from "../users/users.service"; +import { AvailabilityService } from "../availability/availability.service"; +import { RecruitmentSessionService } from "../recruitment-session/recruitment-session.service"; +import { Person, Role, TimeSlot, AvailabilityState, RecruitmentSessionState } from '@hkrecruitment/shared'; +import { RecruitmentSession } from "src/recruitment-session/recruitment-session.entity"; +import { Availability } from "src/availability/availability.entity"; +import { createApp, getAccessToken, getSub } from "test/app.e2e-spec"; + +describe("TimeslotsController", () => { + let app: INestApplication; + let newMemberToken: string; + let timeSlotsService: TimeSlotsService; + let usersService: UsersService; + let availabilityService: AvailabilityService; + let recruitmentSessionService: RecruitmentSessionService; + let mockUsers: Person[]; + let mockTimeSlots: TimeSlot[]; + let mockRecruitmentSessions: RecruitmentSession[]; + let mockAvailability: Availability[]; + + beforeAll(async () => { + newMemberToken = await getAccessToken('newMember'); + + mockTimeSlots = [ + {id: 1, start: new Date('2024-04-05 10:00:00'), end: new Date('2024-05-21 11:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 2, start: new Date('2024-04-05 11:00:00'), end: new Date('2024-05-21 12:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 3, start: new Date('2024-04-05 15:00:00'), end: new Date('2024-05-21 16:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 4, start: new Date('2024-04-05 16:00:00'), end: new Date('2024-05-21 17:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 5, start: new Date('2024-04-05 17:00:00'), end: new Date('2024-05-21 18:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 6, start: new Date('2024-04-05 18:00:00'), end: new Date('2024-05-21 19:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 7, start: new Date('2024-04-06 10:00:00'), end: new Date('2024-06-21 11:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 8, start: new Date('2024-04-06 11:00:00'), end: new Date('2024-06-21 12:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 9, start: new Date('2024-04-06 15:00:00'), end: new Date('2024-06-21 16:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 10, start: new Date('2024-04-06 16:00:00'), end: new Date('2024-06-21 17:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 11, start: new Date('2024-04-06 17:00:00'), end: new Date('2024-06-21 18:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 12, start: new Date('2024-04-06 18:00:00'), end: new Date('2024-06-21 19:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 13, start: new Date('2024-04-07 10:00:00'), end: new Date('2024-07-21 11:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 14, start: new Date('2024-04-07 11:00:00'), end: new Date('2024-07-21 12:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 15, start: new Date('2024-04-07 15:00:00'), end: new Date('2024-07-21 16:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 16, start: new Date('2024-04-07 16:00:00'), end: new Date('2024-07-21 17:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 17, start: new Date('2024-04-07 17:00:00'), end: new Date('2024-07-21 18:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 18, start: new Date('2024-04-07 18:00:00'), end: new Date('2024-07-21 19:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 19, start: new Date('2024-04-08 10:00:00'), end: new Date('2024-08-21 11:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 20, start: new Date('2024-04-08 11:00:00'), end: new Date('2024-08-21 12:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 21, start: new Date('2024-04-08 15:00:00'), end: new Date('2024-08-21 16:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 22, start: new Date('2024-04-08 16:00:00'), end: new Date('2024-08-21 17:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 23, start: new Date('2024-04-08 17:00:00'), end: new Date('2024-08-21 18:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 24, start: new Date('2024-04-08 18:00:00'), end: new Date('2024-08-21 19:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 25, start: new Date('2024-04-09 10:00:00'), end: new Date('2024-09-21 11:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 26, start: new Date('2024-04-09 11:00:00'), end: new Date('2024-09-21 12:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 27, start: new Date('2024-04-09 15:00:00'), end: new Date('2024-09-21 16:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 28, start: new Date('2024-04-09 16:00:00'), end: new Date('2024-09-21 17:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 29, start: new Date('2024-04-09 17:00:00'), end: new Date('2024-09-21 18:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 30, start: new Date('2024-04-09 18:00:00'), end: new Date('2024-09-21 19:00:00'), recruitmentSession: 1} as TimeSlot, + {id: 31, start: new Date('2024-05-05 10:00:00'), end: new Date('2024-05-05 11:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 32, start: new Date('2024-05-05 11:00:00'), end: new Date('2024-05-05 12:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 33, start: new Date('2024-05-05 15:00:00'), end: new Date('2024-05-05 16:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 34, start: new Date('2024-05-05 16:00:00'), end: new Date('2024-05-05 17:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 35, start: new Date('2024-05-05 17:00:00'), end: new Date('2024-05-05 18:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 36, start: new Date('2024-05-05 18:00:00'), end: new Date('2024-05-05 19:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 37, start: new Date('2024-05-06 10:00:00'), end: new Date('2024-05-06 11:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 38, start: new Date('2024-05-06 11:00:00'), end: new Date('2024-05-06 12:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 39, start: new Date('2024-05-06 15:00:00'), end: new Date('2024-05-06 16:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 40, start: new Date('2024-05-06 16:00:00'), end: new Date('2024-05-06 17:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 41, start: new Date('2024-05-06 17:00:00'), end: new Date('2024-05-06 18:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 42, start: new Date('2024-05-06 18:00:00'), end: new Date('2024-05-06 19:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 43, start: new Date('2024-05-07 10:00:00'), end: new Date('2024-05-07 11:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 44, start: new Date('2024-05-07 11:00:00'), end: new Date('2024-05-07 12:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 45, start: new Date('2024-05-07 15:00:00'), end: new Date('2024-05-07 16:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 46, start: new Date('2024-05-07 16:00:00'), end: new Date('2024-05-07 17:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 47, start: new Date('2024-05-07 17:00:00'), end: new Date('2024-05-07 18:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 48, start: new Date('2024-05-07 18:00:00'), end: new Date('2024-05-07 19:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 49, start: new Date('2024-05-08 10:00:00'), end: new Date('2024-05-08 11:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 50, start: new Date('2024-05-08 11:00:00'), end: new Date('2024-05-08 12:00:00'), recruitmentSession: 2} as TimeSlot, + {id: 51, start: new Date('2024-05-16 10:00:00'), end: new Date('2024-05-16 11:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 52, start: new Date('2024-05-16 11:00:00'), end: new Date('2024-05-16 12:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 53, start: new Date('2024-05-16 15:00:00'), end: new Date('2024-05-16 16:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 54, start: new Date('2024-05-16 16:00:00'), end: new Date('2024-05-16 17:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 55, start: new Date('2024-05-16 17:00:00'), end: new Date('2024-05-16 18:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 56, start: new Date('2024-05-16 18:00:00'), end: new Date('2024-05-16 19:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 57, start: new Date('2024-05-17 10:00:00'), end: new Date('2024-05-17 11:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 58, start: new Date('2024-05-17 11:00:00'), end: new Date('2024-05-17 12:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 59, start: new Date('2024-05-17 15:00:00'), end: new Date('2024-05-17 16:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 60, start: new Date('2024-05-17 16:00:00'), end: new Date('2024-05-17 17:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 61, start: new Date('2024-05-17 17:00:00'), end: new Date('2024-05-17 18:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 62, start: new Date('2024-05-17 18:00:00'), end: new Date('2024-05-17 19:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 63, start: new Date('2024-05-18 10:00:00'), end: new Date('2024-05-18 11:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 64, start: new Date('2024-05-18 11:00:00'), end: new Date('2024-05-18 12:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 65, start: new Date('2024-05-18 15:00:00'), end: new Date('2024-05-18 16:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 66, start: new Date('2024-05-18 16:00:00'), end: new Date('2024-05-18 17:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 67, start: new Date('2024-05-18 17:00:00'), end: new Date('2024-05-18 18:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 68, start: new Date('2024-05-18 18:00:00'), end: new Date('2024-05-18 19:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 69, start: new Date('2024-05-19 10:00:00'), end: new Date('2024-05-19 11:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 70, start: new Date('2024-05-19 11:00:00'), end: new Date('2024-05-19 12:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 71, start: new Date('2024-05-19 15:00:00'), end: new Date('2024-05-19 16:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 72, start: new Date('2024-05-19 16:00:00'), end: new Date('2024-05-19 17:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 73, start: new Date('2024-05-19 17:00:00'), end: new Date('2024-05-19 18:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 74, start: new Date('2024-05-19 18:00:00'), end: new Date('2024-05-19 19:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 75, start: new Date('2024-05-20 10:00:00'), end: new Date('2024-05-20 11:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 76, start: new Date('2024-05-20 11:00:00'), end: new Date('2024-05-20 12:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 77, start: new Date('2024-05-20 15:00:00'), end: new Date('2024-05-20 16:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 78, start: new Date('2024-05-20 16:00:00'), end: new Date('2024-05-20 17:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 79, start: new Date('2024-05-20 17:00:00'), end: new Date('2024-05-20 18:00:00'), recruitmentSession: 3} as TimeSlot, + {id: 80, start: new Date('2024-05-20 18:00:00'), end: new Date('2024-05-20 19:00:00'), recruitmentSession: 3} as TimeSlot + ] + + mockAvailability = [ + {id: 1, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 51, userOauthId: '5' } as unknown as Availability, + {id: 2, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '7' } as unknown as Availability, + {id: 3, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 55, userOauthId: '12'} as unknown as Availability, + {id: 4, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '19'} as unknown as Availability, + {id: 5, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '3' } as unknown as Availability, + {id: 6, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '15'} as unknown as Availability, + {id: 7, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 58, userOauthId: '11'} as unknown as Availability, + {id: 8, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '8' } as unknown as Availability, + {id: 9, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5' } as unknown as Availability, + {id: 10, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '17'} as unknown as Availability, + {id: 11, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '2' } as unknown as Availability, + {id: 12, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '9' } as unknown as Availability, + {id: 13, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '14'} as unknown as Availability, + {id: 14, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '6' } as unknown as Availability, + {id: 15, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '13'} as unknown as Availability, + {id: 16, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '10'} as unknown as Availability, + {id: 17, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '4' } as unknown as Availability, + {id: 18, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '16'} as unknown as Availability, + {id: 19, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 59, userOauthId: '18'} as unknown as Availability, + {id: 20, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '1' } as unknown as Availability, + {id: 21, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '20'} as unknown as Availability, + {id: 22, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '19'} as unknown as Availability, + {id: 23, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '7' } as unknown as Availability, + {id: 24, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '16'} as unknown as Availability, + {id: 25, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '15'} as unknown as Availability, + {id: 26, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '11'} as unknown as Availability, + {id: 27, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '14'} as unknown as Availability, + {id: 28, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '9' } as unknown as Availability, + {id: 29, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '8' } as unknown as Availability, + {id: 30, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '10'} as unknown as Availability, + {id: 31, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '13'} as unknown as Availability, + {id: 32, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 80, userOauthId: '12'} as unknown as Availability, + {id: 33, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '20'} as unknown as Availability, + {id: 34, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '18'} as unknown as Availability, + {id: 35, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '17'} as unknown as Availability, + {id: 36, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '19'} as unknown as Availability, + {id: 37, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '16'} as unknown as Availability, + {id: 38, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '15'} as unknown as Availability, + {id: 39, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '14'} as unknown as Availability, + {id: 40, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '13'} as unknown as Availability, + {id: 41, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '12'} as unknown as Availability, + {id: 42, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '11'} as unknown as Availability, + {id: 43, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '10'} as unknown as Availability, + {id: 44, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '9' } as unknown as Availability, + {id: 45, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 67, userOauthId: '8' } as unknown as Availability, + {id: 46, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '7' } as unknown as Availability, + {id: 47, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 65, userOauthId: '6' } as unknown as Availability, + {id: 48, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5' } as unknown as Availability, + {id: 49, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 63, userOauthId: '8' } as unknown as Availability, + {id: 50, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '14'} as unknown as Availability, + {id: 51, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '4' } as unknown as Availability, + {id: 52, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 60, userOauthId: '13'} as unknown as Availability, + ]; + + mockRecruitmentSessions = [ + {id: 1, state: RecruitmentSessionState.Concluded, slotDuration: 1, lastModified: new Date('2024-04-10'), createdAt: new Date('2024-04-04'), days:[new Date('2024-04-05'), new Date('2024-04-06'), new Date('2024-04-07'), new Date('2024-04-08'), new Date('2024-04-09')], interviewStart: new Date('2024-04-05'), interviewEnd: new Date('2024-04-10')}, + {id: 2, state: RecruitmentSessionState.Concluded, slotDuration: 1, lastModified: new Date('2024-05-10'), createdAt: new Date('2024-05-04'), days:[new Date('2024-05-05'), new Date('2024-05-06'), new Date('2024-05-07'), new Date('2024-05-08')] , interviewStart: new Date('2024-05-05'), interviewEnd: new Date('2024-05-09')}, + {id: 3, state: RecruitmentSessionState.Active, slotDuration: 1, lastModified: new Date('2024-05-14'), createdAt: new Date('2024-05-14'), days:[new Date('2024-05-16'), new Date('2024-05-17'), new Date('2024-05-18'), new Date('2024-05-19'), new Date('2024-05-20')], interviewStart: new Date('2024-05-16'), interviewEnd: new Date('2024-05-21')} + ]; + + mockUsers = [ + {oauthId: '1', firstName: 'Pasquale', lastName: 'Bianco', sex: 'male', email: 'p.bianco@gmail.com', phone_no: '3405174444', telegramId: 'pbianco', role: Role.Member, is_board: true, is_expert: true}, + {oauthId: '2', firstName: 'John', lastName: 'Doe', sex: 'male', email: 'j.doe@gmail.com', phone_no: '1234567890', telegramId: 'jdoe', role: Role.Member, is_board: false, is_expert: true}, + {oauthId: '3', firstName: 'Jane', lastName: 'Smith', sex: 'female', email: 'j.smith@gmail.com', phone_no: '9876543210', telegramId: 'jsmith', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '4', firstName: 'Michael', lastName: 'Johnson', sex: 'male', email: 'm.johnson@gmail.com', phone_no: '5555555555', telegramId: 'mjohnson', role: Role.Member, is_board: true, is_expert: true}, + {oauthId: '5', firstName: 'Emily', lastName: 'Brown', sex: 'female', email: 'e.brown@gmail.com', phone_no: '1111111111', telegramId: 'ebrown', role: Role.Member, is_board: false, is_expert: true}, + {oauthId: '6', firstName: 'David', lastName: 'Wilson', sex: 'male', email: 'd.wilson@gmail.com', phone_no: '2222222222', telegramId: 'dwilson', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '7', firstName: 'Olivia', lastName: 'Johnson', sex: 'female', email: 'o.johnson@gmail.com', phone_no: '3333333333', telegramId: 'ojohnson', role: Role.Member, is_board: true, is_expert: false}, + {oauthId: '8', firstName: 'James', lastName: 'Smith', sex: 'male', email: 'j.smith@gmail.com', phone_no: '4444444444', telegramId: 'jsmith', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '9', firstName: 'Sophia', lastName: 'Miller', sex: 'female', email: 's.miller@gmail.com', phone_no: '5555555555', telegramId: 'smiller', role: Role.Member, is_board: false, is_expert: true}, + {oauthId: '10', firstName: 'Benjamin', lastName: 'Davis', sex: 'male', email: 'b.davis@gmail.com', phone_no: '6666666666', telegramId: 'bdavis', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '11', firstName: 'Ava', lastName: 'Wilson', sex: 'female', email: 'a.wilson@gmail.com', phone_no: '7777777777', telegramId: 'awilson', role: Role.Member, is_board: true, is_expert: true}, + {oauthId: '12', firstName: 'William', lastName: 'Anderson',sex: 'male', email: 'w.anderson@gmail.com', phone_no: '8888888888', telegramId: 'wanderson', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '13', firstName: 'Mia', lastName: 'Thomas', sex: 'female', email: 'm.thomas@gmail.com', phone_no: '9999999999', telegramId: 'mthomas', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '14', firstName: 'Alexander', lastName: 'Taylor', sex: 'male', email: 'a.taylor@gmail.com', phone_no: '1111111111', telegramId: 'ataylor', role: Role.Member, is_board: false, is_expert: true}, + {oauthId: '15', firstName: 'Charlotte', lastName: 'Clark', sex: 'female', email: 'c.clark@gmail.com', phone_no: '2222222222', telegramId: 'cclark', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '16', firstName: 'Daniel', lastName: 'Moore', sex: 'male', email: 'd.moore@gmail.com', phone_no: '3333333333', telegramId: 'dmoore', role: Role.Member, is_board: false, is_expert: false}, + {oauthId: '17', firstName: 'Amelia', lastName: 'Walker', sex: 'female', email: 'a.walker@gmail.com', phone_no: '4444444444', telegramId: 'awalker', role: Role.Clerk, is_board: false, is_expert: false}, + {oauthId: '18', firstName: 'Matthew', lastName: 'Lewis', sex: 'male', email: 'm.lewis@gmail.com', phone_no: '5555555555', telegramId: 'mlewis', role: Role.Admin, is_board: false, is_expert: true}, + {oauthId: '19', firstName: 'Ella', lastName: 'Harris', sex: 'female', email: 'e.harris@gmail.com', phone_no: '6666666666', telegramId: 'eharris', role: Role.Supervisor, is_board: false, is_expert: true}, + {oauthId: '20', firstName: 'Joseph', lastName: 'King', sex: 'male', email: 'j.king@gmail.com', phone_no: '7777777777', telegramId: 'jking', role: Role.Supervisor, is_board: true, is_expert: false} + ]; + + + }); + + beforeEach(async () => { + app = await createApp(); + timeSlotsService = app.get(TimeSlotsService); + availabilityService = app.get(AvailabilityService); + recruitmentSessionService = app.get(RecruitmentSessionService); + usersService = app.get(UsersService); + }); + + afterEach(async () => { + await app.close(); + }); + + describe(" GET /timeslots", () => { + beforeEach(async () => { + await mockUsers.forEach(async (u) => await usersService.create(u)); + await mockTimeSlots.forEach(async (ts) => await timeSlotsService.createTimeSlot(ts)); + await mockAvailability.forEach(async (a) => await availabilityService.createAvailability(a)); + await mockRecruitmentSessions.forEach(async (rs) => await recruitmentSessionService.createRecruitmentSession(rs)); + }); + + it('should return all available timeslots', async () => { + + return await request(app.getHttpServer()) + .get('/timeslots') + .set('Authorization', `Bearer ${newMemberToken}`) + .expect(200) + .expect((res) => { + console.log(res.body); + expect(res.body).toBeInstanceOf(Array); + }); + }); + }); + +}); \ No newline at end of file From 4602a26e1b5b10d6d9feb4ad9eddfc244965b78a Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:05:01 +0200 Subject: [PATCH 12/57] format --- api/src/timeslots/timeslots.e2e-spec.ts | 1417 +++++++++++++++++++---- 1 file changed, 1202 insertions(+), 215 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index d24138c..e8b7512 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1,230 +1,1217 @@ -import { INestApplication } from "@nestjs/common"; +import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; -import { TimeSlotsService } from "./timeslots.service"; -import { UsersService } from "../users/users.service"; -import { AvailabilityService } from "../availability/availability.service"; -import { RecruitmentSessionService } from "../recruitment-session/recruitment-session.service"; -import { Person, Role, TimeSlot, AvailabilityState, RecruitmentSessionState } from '@hkrecruitment/shared'; -import { RecruitmentSession } from "src/recruitment-session/recruitment-session.entity"; -import { Availability } from "src/availability/availability.entity"; -import { createApp, getAccessToken, getSub } from "test/app.e2e-spec"; +import { TimeSlotsService } from './timeslots.service'; +import { UsersService } from '../users/users.service'; +import { AvailabilityService } from '../availability/availability.service'; +import { RecruitmentSessionService } from '../recruitment-session/recruitment-session.service'; +import { + Person, + Role, + TimeSlot, + AvailabilityState, + RecruitmentSessionState, +} from '@hkrecruitment/shared'; +import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; +import { Availability } from 'src/availability/availability.entity'; +import { createApp, getAccessToken, getSub } from 'test/app.e2e-spec'; -describe("TimeslotsController", () => { - let app: INestApplication; - let newMemberToken: string; - let timeSlotsService: TimeSlotsService; - let usersService: UsersService; - let availabilityService: AvailabilityService; - let recruitmentSessionService: RecruitmentSessionService; - let mockUsers: Person[]; - let mockTimeSlots: TimeSlot[]; - let mockRecruitmentSessions: RecruitmentSession[]; - let mockAvailability: Availability[]; +describe('TimeslotsController', () => { + let app: INestApplication; + let newMemberToken: string; + let timeSlotsService: TimeSlotsService; + let usersService: UsersService; + let availabilityService: AvailabilityService; + let recruitmentSessionService: RecruitmentSessionService; + let mockUsers: Person[]; + let mockTimeSlots: TimeSlot[]; + let mockRecruitmentSessions: RecruitmentSession[]; + let mockAvailability: Availability[]; - beforeAll(async () => { - newMemberToken = await getAccessToken('newMember'); + beforeAll(async () => { + newMemberToken = await getAccessToken('newMember'); - mockTimeSlots = [ - {id: 1, start: new Date('2024-04-05 10:00:00'), end: new Date('2024-05-21 11:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 2, start: new Date('2024-04-05 11:00:00'), end: new Date('2024-05-21 12:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 3, start: new Date('2024-04-05 15:00:00'), end: new Date('2024-05-21 16:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 4, start: new Date('2024-04-05 16:00:00'), end: new Date('2024-05-21 17:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 5, start: new Date('2024-04-05 17:00:00'), end: new Date('2024-05-21 18:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 6, start: new Date('2024-04-05 18:00:00'), end: new Date('2024-05-21 19:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 7, start: new Date('2024-04-06 10:00:00'), end: new Date('2024-06-21 11:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 8, start: new Date('2024-04-06 11:00:00'), end: new Date('2024-06-21 12:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 9, start: new Date('2024-04-06 15:00:00'), end: new Date('2024-06-21 16:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 10, start: new Date('2024-04-06 16:00:00'), end: new Date('2024-06-21 17:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 11, start: new Date('2024-04-06 17:00:00'), end: new Date('2024-06-21 18:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 12, start: new Date('2024-04-06 18:00:00'), end: new Date('2024-06-21 19:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 13, start: new Date('2024-04-07 10:00:00'), end: new Date('2024-07-21 11:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 14, start: new Date('2024-04-07 11:00:00'), end: new Date('2024-07-21 12:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 15, start: new Date('2024-04-07 15:00:00'), end: new Date('2024-07-21 16:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 16, start: new Date('2024-04-07 16:00:00'), end: new Date('2024-07-21 17:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 17, start: new Date('2024-04-07 17:00:00'), end: new Date('2024-07-21 18:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 18, start: new Date('2024-04-07 18:00:00'), end: new Date('2024-07-21 19:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 19, start: new Date('2024-04-08 10:00:00'), end: new Date('2024-08-21 11:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 20, start: new Date('2024-04-08 11:00:00'), end: new Date('2024-08-21 12:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 21, start: new Date('2024-04-08 15:00:00'), end: new Date('2024-08-21 16:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 22, start: new Date('2024-04-08 16:00:00'), end: new Date('2024-08-21 17:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 23, start: new Date('2024-04-08 17:00:00'), end: new Date('2024-08-21 18:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 24, start: new Date('2024-04-08 18:00:00'), end: new Date('2024-08-21 19:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 25, start: new Date('2024-04-09 10:00:00'), end: new Date('2024-09-21 11:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 26, start: new Date('2024-04-09 11:00:00'), end: new Date('2024-09-21 12:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 27, start: new Date('2024-04-09 15:00:00'), end: new Date('2024-09-21 16:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 28, start: new Date('2024-04-09 16:00:00'), end: new Date('2024-09-21 17:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 29, start: new Date('2024-04-09 17:00:00'), end: new Date('2024-09-21 18:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 30, start: new Date('2024-04-09 18:00:00'), end: new Date('2024-09-21 19:00:00'), recruitmentSession: 1} as TimeSlot, - {id: 31, start: new Date('2024-05-05 10:00:00'), end: new Date('2024-05-05 11:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 32, start: new Date('2024-05-05 11:00:00'), end: new Date('2024-05-05 12:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 33, start: new Date('2024-05-05 15:00:00'), end: new Date('2024-05-05 16:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 34, start: new Date('2024-05-05 16:00:00'), end: new Date('2024-05-05 17:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 35, start: new Date('2024-05-05 17:00:00'), end: new Date('2024-05-05 18:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 36, start: new Date('2024-05-05 18:00:00'), end: new Date('2024-05-05 19:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 37, start: new Date('2024-05-06 10:00:00'), end: new Date('2024-05-06 11:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 38, start: new Date('2024-05-06 11:00:00'), end: new Date('2024-05-06 12:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 39, start: new Date('2024-05-06 15:00:00'), end: new Date('2024-05-06 16:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 40, start: new Date('2024-05-06 16:00:00'), end: new Date('2024-05-06 17:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 41, start: new Date('2024-05-06 17:00:00'), end: new Date('2024-05-06 18:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 42, start: new Date('2024-05-06 18:00:00'), end: new Date('2024-05-06 19:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 43, start: new Date('2024-05-07 10:00:00'), end: new Date('2024-05-07 11:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 44, start: new Date('2024-05-07 11:00:00'), end: new Date('2024-05-07 12:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 45, start: new Date('2024-05-07 15:00:00'), end: new Date('2024-05-07 16:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 46, start: new Date('2024-05-07 16:00:00'), end: new Date('2024-05-07 17:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 47, start: new Date('2024-05-07 17:00:00'), end: new Date('2024-05-07 18:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 48, start: new Date('2024-05-07 18:00:00'), end: new Date('2024-05-07 19:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 49, start: new Date('2024-05-08 10:00:00'), end: new Date('2024-05-08 11:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 50, start: new Date('2024-05-08 11:00:00'), end: new Date('2024-05-08 12:00:00'), recruitmentSession: 2} as TimeSlot, - {id: 51, start: new Date('2024-05-16 10:00:00'), end: new Date('2024-05-16 11:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 52, start: new Date('2024-05-16 11:00:00'), end: new Date('2024-05-16 12:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 53, start: new Date('2024-05-16 15:00:00'), end: new Date('2024-05-16 16:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 54, start: new Date('2024-05-16 16:00:00'), end: new Date('2024-05-16 17:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 55, start: new Date('2024-05-16 17:00:00'), end: new Date('2024-05-16 18:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 56, start: new Date('2024-05-16 18:00:00'), end: new Date('2024-05-16 19:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 57, start: new Date('2024-05-17 10:00:00'), end: new Date('2024-05-17 11:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 58, start: new Date('2024-05-17 11:00:00'), end: new Date('2024-05-17 12:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 59, start: new Date('2024-05-17 15:00:00'), end: new Date('2024-05-17 16:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 60, start: new Date('2024-05-17 16:00:00'), end: new Date('2024-05-17 17:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 61, start: new Date('2024-05-17 17:00:00'), end: new Date('2024-05-17 18:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 62, start: new Date('2024-05-17 18:00:00'), end: new Date('2024-05-17 19:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 63, start: new Date('2024-05-18 10:00:00'), end: new Date('2024-05-18 11:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 64, start: new Date('2024-05-18 11:00:00'), end: new Date('2024-05-18 12:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 65, start: new Date('2024-05-18 15:00:00'), end: new Date('2024-05-18 16:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 66, start: new Date('2024-05-18 16:00:00'), end: new Date('2024-05-18 17:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 67, start: new Date('2024-05-18 17:00:00'), end: new Date('2024-05-18 18:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 68, start: new Date('2024-05-18 18:00:00'), end: new Date('2024-05-18 19:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 69, start: new Date('2024-05-19 10:00:00'), end: new Date('2024-05-19 11:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 70, start: new Date('2024-05-19 11:00:00'), end: new Date('2024-05-19 12:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 71, start: new Date('2024-05-19 15:00:00'), end: new Date('2024-05-19 16:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 72, start: new Date('2024-05-19 16:00:00'), end: new Date('2024-05-19 17:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 73, start: new Date('2024-05-19 17:00:00'), end: new Date('2024-05-19 18:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 74, start: new Date('2024-05-19 18:00:00'), end: new Date('2024-05-19 19:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 75, start: new Date('2024-05-20 10:00:00'), end: new Date('2024-05-20 11:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 76, start: new Date('2024-05-20 11:00:00'), end: new Date('2024-05-20 12:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 77, start: new Date('2024-05-20 15:00:00'), end: new Date('2024-05-20 16:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 78, start: new Date('2024-05-20 16:00:00'), end: new Date('2024-05-20 17:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 79, start: new Date('2024-05-20 17:00:00'), end: new Date('2024-05-20 18:00:00'), recruitmentSession: 3} as TimeSlot, - {id: 80, start: new Date('2024-05-20 18:00:00'), end: new Date('2024-05-20 19:00:00'), recruitmentSession: 3} as TimeSlot - ] + mockTimeSlots = [ + { + id: 1, + start: new Date('2024-04-05 10:00:00'), + end: new Date('2024-05-21 11:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 2, + start: new Date('2024-04-05 11:00:00'), + end: new Date('2024-05-21 12:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 3, + start: new Date('2024-04-05 15:00:00'), + end: new Date('2024-05-21 16:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 4, + start: new Date('2024-04-05 16:00:00'), + end: new Date('2024-05-21 17:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 5, + start: new Date('2024-04-05 17:00:00'), + end: new Date('2024-05-21 18:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 6, + start: new Date('2024-04-05 18:00:00'), + end: new Date('2024-05-21 19:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 7, + start: new Date('2024-04-06 10:00:00'), + end: new Date('2024-06-21 11:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 8, + start: new Date('2024-04-06 11:00:00'), + end: new Date('2024-06-21 12:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 9, + start: new Date('2024-04-06 15:00:00'), + end: new Date('2024-06-21 16:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 10, + start: new Date('2024-04-06 16:00:00'), + end: new Date('2024-06-21 17:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 11, + start: new Date('2024-04-06 17:00:00'), + end: new Date('2024-06-21 18:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 12, + start: new Date('2024-04-06 18:00:00'), + end: new Date('2024-06-21 19:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 13, + start: new Date('2024-04-07 10:00:00'), + end: new Date('2024-07-21 11:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 14, + start: new Date('2024-04-07 11:00:00'), + end: new Date('2024-07-21 12:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 15, + start: new Date('2024-04-07 15:00:00'), + end: new Date('2024-07-21 16:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 16, + start: new Date('2024-04-07 16:00:00'), + end: new Date('2024-07-21 17:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 17, + start: new Date('2024-04-07 17:00:00'), + end: new Date('2024-07-21 18:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 18, + start: new Date('2024-04-07 18:00:00'), + end: new Date('2024-07-21 19:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 19, + start: new Date('2024-04-08 10:00:00'), + end: new Date('2024-08-21 11:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 20, + start: new Date('2024-04-08 11:00:00'), + end: new Date('2024-08-21 12:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 21, + start: new Date('2024-04-08 15:00:00'), + end: new Date('2024-08-21 16:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 22, + start: new Date('2024-04-08 16:00:00'), + end: new Date('2024-08-21 17:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 23, + start: new Date('2024-04-08 17:00:00'), + end: new Date('2024-08-21 18:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 24, + start: new Date('2024-04-08 18:00:00'), + end: new Date('2024-08-21 19:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 25, + start: new Date('2024-04-09 10:00:00'), + end: new Date('2024-09-21 11:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 26, + start: new Date('2024-04-09 11:00:00'), + end: new Date('2024-09-21 12:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 27, + start: new Date('2024-04-09 15:00:00'), + end: new Date('2024-09-21 16:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 28, + start: new Date('2024-04-09 16:00:00'), + end: new Date('2024-09-21 17:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 29, + start: new Date('2024-04-09 17:00:00'), + end: new Date('2024-09-21 18:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 30, + start: new Date('2024-04-09 18:00:00'), + end: new Date('2024-09-21 19:00:00'), + recruitmentSession: 1, + } as TimeSlot, + { + id: 31, + start: new Date('2024-05-05 10:00:00'), + end: new Date('2024-05-05 11:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 32, + start: new Date('2024-05-05 11:00:00'), + end: new Date('2024-05-05 12:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 33, + start: new Date('2024-05-05 15:00:00'), + end: new Date('2024-05-05 16:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 34, + start: new Date('2024-05-05 16:00:00'), + end: new Date('2024-05-05 17:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 35, + start: new Date('2024-05-05 17:00:00'), + end: new Date('2024-05-05 18:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 36, + start: new Date('2024-05-05 18:00:00'), + end: new Date('2024-05-05 19:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 37, + start: new Date('2024-05-06 10:00:00'), + end: new Date('2024-05-06 11:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 38, + start: new Date('2024-05-06 11:00:00'), + end: new Date('2024-05-06 12:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 39, + start: new Date('2024-05-06 15:00:00'), + end: new Date('2024-05-06 16:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 40, + start: new Date('2024-05-06 16:00:00'), + end: new Date('2024-05-06 17:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 41, + start: new Date('2024-05-06 17:00:00'), + end: new Date('2024-05-06 18:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 42, + start: new Date('2024-05-06 18:00:00'), + end: new Date('2024-05-06 19:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 43, + start: new Date('2024-05-07 10:00:00'), + end: new Date('2024-05-07 11:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 44, + start: new Date('2024-05-07 11:00:00'), + end: new Date('2024-05-07 12:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 45, + start: new Date('2024-05-07 15:00:00'), + end: new Date('2024-05-07 16:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 46, + start: new Date('2024-05-07 16:00:00'), + end: new Date('2024-05-07 17:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 47, + start: new Date('2024-05-07 17:00:00'), + end: new Date('2024-05-07 18:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 48, + start: new Date('2024-05-07 18:00:00'), + end: new Date('2024-05-07 19:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 49, + start: new Date('2024-05-08 10:00:00'), + end: new Date('2024-05-08 11:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 50, + start: new Date('2024-05-08 11:00:00'), + end: new Date('2024-05-08 12:00:00'), + recruitmentSession: 2, + } as TimeSlot, + { + id: 51, + start: new Date('2024-05-16 10:00:00'), + end: new Date('2024-05-16 11:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 52, + start: new Date('2024-05-16 11:00:00'), + end: new Date('2024-05-16 12:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 53, + start: new Date('2024-05-16 15:00:00'), + end: new Date('2024-05-16 16:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 54, + start: new Date('2024-05-16 16:00:00'), + end: new Date('2024-05-16 17:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 55, + start: new Date('2024-05-16 17:00:00'), + end: new Date('2024-05-16 18:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 56, + start: new Date('2024-05-16 18:00:00'), + end: new Date('2024-05-16 19:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 57, + start: new Date('2024-05-17 10:00:00'), + end: new Date('2024-05-17 11:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 58, + start: new Date('2024-05-17 11:00:00'), + end: new Date('2024-05-17 12:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 59, + start: new Date('2024-05-17 15:00:00'), + end: new Date('2024-05-17 16:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 60, + start: new Date('2024-05-17 16:00:00'), + end: new Date('2024-05-17 17:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 61, + start: new Date('2024-05-17 17:00:00'), + end: new Date('2024-05-17 18:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 62, + start: new Date('2024-05-17 18:00:00'), + end: new Date('2024-05-17 19:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 63, + start: new Date('2024-05-18 10:00:00'), + end: new Date('2024-05-18 11:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 64, + start: new Date('2024-05-18 11:00:00'), + end: new Date('2024-05-18 12:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 65, + start: new Date('2024-05-18 15:00:00'), + end: new Date('2024-05-18 16:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 66, + start: new Date('2024-05-18 16:00:00'), + end: new Date('2024-05-18 17:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 67, + start: new Date('2024-05-18 17:00:00'), + end: new Date('2024-05-18 18:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 68, + start: new Date('2024-05-18 18:00:00'), + end: new Date('2024-05-18 19:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 69, + start: new Date('2024-05-19 10:00:00'), + end: new Date('2024-05-19 11:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 70, + start: new Date('2024-05-19 11:00:00'), + end: new Date('2024-05-19 12:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 71, + start: new Date('2024-05-19 15:00:00'), + end: new Date('2024-05-19 16:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 72, + start: new Date('2024-05-19 16:00:00'), + end: new Date('2024-05-19 17:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 73, + start: new Date('2024-05-19 17:00:00'), + end: new Date('2024-05-19 18:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 74, + start: new Date('2024-05-19 18:00:00'), + end: new Date('2024-05-19 19:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 75, + start: new Date('2024-05-20 10:00:00'), + end: new Date('2024-05-20 11:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 76, + start: new Date('2024-05-20 11:00:00'), + end: new Date('2024-05-20 12:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 77, + start: new Date('2024-05-20 15:00:00'), + end: new Date('2024-05-20 16:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 78, + start: new Date('2024-05-20 16:00:00'), + end: new Date('2024-05-20 17:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 79, + start: new Date('2024-05-20 17:00:00'), + end: new Date('2024-05-20 18:00:00'), + recruitmentSession: 3, + } as TimeSlot, + { + id: 80, + start: new Date('2024-05-20 18:00:00'), + end: new Date('2024-05-20 19:00:00'), + recruitmentSession: 3, + } as TimeSlot, + ]; - mockAvailability = [ - {id: 1, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 51, userOauthId: '5' } as unknown as Availability, - {id: 2, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '7' } as unknown as Availability, - {id: 3, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 55, userOauthId: '12'} as unknown as Availability, - {id: 4, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '19'} as unknown as Availability, - {id: 5, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '3' } as unknown as Availability, - {id: 6, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '15'} as unknown as Availability, - {id: 7, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 58, userOauthId: '11'} as unknown as Availability, - {id: 8, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '8' } as unknown as Availability, - {id: 9, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5' } as unknown as Availability, - {id: 10, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '17'} as unknown as Availability, - {id: 11, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '2' } as unknown as Availability, - {id: 12, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '9' } as unknown as Availability, - {id: 13, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '14'} as unknown as Availability, - {id: 14, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '6' } as unknown as Availability, - {id: 15, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '13'} as unknown as Availability, - {id: 16, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '10'} as unknown as Availability, - {id: 17, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '4' } as unknown as Availability, - {id: 18, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '16'} as unknown as Availability, - {id: 19, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 59, userOauthId: '18'} as unknown as Availability, - {id: 20, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '1' } as unknown as Availability, - {id: 21, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '20'} as unknown as Availability, - {id: 22, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '19'} as unknown as Availability, - {id: 23, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '7' } as unknown as Availability, - {id: 24, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '16'} as unknown as Availability, - {id: 25, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '15'} as unknown as Availability, - {id: 26, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '11'} as unknown as Availability, - {id: 27, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '14'} as unknown as Availability, - {id: 28, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '9' } as unknown as Availability, - {id: 29, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '8' } as unknown as Availability, - {id: 30, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '10'} as unknown as Availability, - {id: 31, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '13'} as unknown as Availability, - {id: 32, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 80, userOauthId: '12'} as unknown as Availability, - {id: 33, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '20'} as unknown as Availability, - {id: 34, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '18'} as unknown as Availability, - {id: 35, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '17'} as unknown as Availability, - {id: 36, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '19'} as unknown as Availability, - {id: 37, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '16'} as unknown as Availability, - {id: 38, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '15'} as unknown as Availability, - {id: 39, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '14'} as unknown as Availability, - {id: 40, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '13'} as unknown as Availability, - {id: 41, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '12'} as unknown as Availability, - {id: 42, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '11'} as unknown as Availability, - {id: 43, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '10'} as unknown as Availability, - {id: 44, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '9' } as unknown as Availability, - {id: 45, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 67, userOauthId: '8' } as unknown as Availability, - {id: 46, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '7' } as unknown as Availability, - {id: 47, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 65, userOauthId: '6' } as unknown as Availability, - {id: 48, state: AvailabilityState.Interviewing , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5' } as unknown as Availability, - {id: 49, state: AvailabilityState.Recovering , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 63, userOauthId: '8' } as unknown as Availability, - {id: 50, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '14'} as unknown as Availability, - {id: 51, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '4' } as unknown as Availability, - {id: 52, state: AvailabilityState.Free , lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 60, userOauthId: '13'} as unknown as Availability, - ]; + mockAvailability = [ + { + id: 1, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 51, + userOauthId: '5', + } as unknown as Availability, + { + id: 2, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 53, + userOauthId: '7', + } as unknown as Availability, + { + id: 3, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 55, + userOauthId: '12', + } as unknown as Availability, + { + id: 4, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 61, + userOauthId: '19', + } as unknown as Availability, + { + id: 5, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '3', + } as unknown as Availability, + { + id: 6, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 78, + userOauthId: '15', + } as unknown as Availability, + { + id: 7, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 58, + userOauthId: '11', + } as unknown as Availability, + { + id: 8, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 57, + userOauthId: '8', + } as unknown as Availability, + { + id: 9, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '5', + } as unknown as Availability, + { + id: 10, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 53, + userOauthId: '17', + } as unknown as Availability, + { + id: 11, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 62, + userOauthId: '2', + } as unknown as Availability, + { + id: 12, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '9', + } as unknown as Availability, + { + id: 13, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '14', + } as unknown as Availability, + { + id: 14, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '6', + } as unknown as Availability, + { + id: 15, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '13', + } as unknown as Availability, + { + id: 16, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '10', + } as unknown as Availability, + { + id: 17, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '4', + } as unknown as Availability, + { + id: 18, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '16', + } as unknown as Availability, + { + id: 19, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 59, + userOauthId: '18', + } as unknown as Availability, + { + id: 20, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 57, + userOauthId: '1', + } as unknown as Availability, + { + id: 21, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 66, + userOauthId: '20', + } as unknown as Availability, + { + id: 22, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 68, + userOauthId: '19', + } as unknown as Availability, + { + id: 23, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 69, + userOauthId: '7', + } as unknown as Availability, + { + id: 24, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 70, + userOauthId: '16', + } as unknown as Availability, + { + id: 25, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '15', + } as unknown as Availability, + { + id: 26, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 73, + userOauthId: '11', + } as unknown as Availability, + { + id: 27, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '14', + } as unknown as Availability, + { + id: 28, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 76, + userOauthId: '9', + } as unknown as Availability, + { + id: 29, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 77, + userOauthId: '8', + } as unknown as Availability, + { + id: 30, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 78, + userOauthId: '10', + } as unknown as Availability, + { + id: 31, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 79, + userOauthId: '13', + } as unknown as Availability, + { + id: 32, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 80, + userOauthId: '12', + } as unknown as Availability, + { + id: 33, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 79, + userOauthId: '20', + } as unknown as Availability, + { + id: 34, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 78, + userOauthId: '18', + } as unknown as Availability, + { + id: 35, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 77, + userOauthId: '17', + } as unknown as Availability, + { + id: 36, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 76, + userOauthId: '19', + } as unknown as Availability, + { + id: 37, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '16', + } as unknown as Availability, + { + id: 38, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '15', + } as unknown as Availability, + { + id: 39, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 73, + userOauthId: '14', + } as unknown as Availability, + { + id: 40, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '13', + } as unknown as Availability, + { + id: 41, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '12', + } as unknown as Availability, + { + id: 42, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 70, + userOauthId: '11', + } as unknown as Availability, + { + id: 43, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 69, + userOauthId: '10', + } as unknown as Availability, + { + id: 44, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 68, + userOauthId: '9', + } as unknown as Availability, + { + id: 45, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 67, + userOauthId: '8', + } as unknown as Availability, + { + id: 46, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 66, + userOauthId: '7', + } as unknown as Availability, + { + id: 47, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 65, + userOauthId: '6', + } as unknown as Availability, + { + id: 48, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '5', + } as unknown as Availability, + { + id: 49, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 63, + userOauthId: '8', + } as unknown as Availability, + { + id: 50, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 62, + userOauthId: '14', + } as unknown as Availability, + { + id: 51, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 61, + userOauthId: '4', + } as unknown as Availability, + { + id: 52, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 60, + userOauthId: '13', + } as unknown as Availability, + ]; - mockRecruitmentSessions = [ - {id: 1, state: RecruitmentSessionState.Concluded, slotDuration: 1, lastModified: new Date('2024-04-10'), createdAt: new Date('2024-04-04'), days:[new Date('2024-04-05'), new Date('2024-04-06'), new Date('2024-04-07'), new Date('2024-04-08'), new Date('2024-04-09')], interviewStart: new Date('2024-04-05'), interviewEnd: new Date('2024-04-10')}, - {id: 2, state: RecruitmentSessionState.Concluded, slotDuration: 1, lastModified: new Date('2024-05-10'), createdAt: new Date('2024-05-04'), days:[new Date('2024-05-05'), new Date('2024-05-06'), new Date('2024-05-07'), new Date('2024-05-08')] , interviewStart: new Date('2024-05-05'), interviewEnd: new Date('2024-05-09')}, - {id: 3, state: RecruitmentSessionState.Active, slotDuration: 1, lastModified: new Date('2024-05-14'), createdAt: new Date('2024-05-14'), days:[new Date('2024-05-16'), new Date('2024-05-17'), new Date('2024-05-18'), new Date('2024-05-19'), new Date('2024-05-20')], interviewStart: new Date('2024-05-16'), interviewEnd: new Date('2024-05-21')} - ]; + mockRecruitmentSessions = [ + { + id: 1, + state: RecruitmentSessionState.Concluded, + slotDuration: 1, + lastModified: new Date('2024-04-10'), + createdAt: new Date('2024-04-04'), + days: [ + new Date('2024-04-05'), + new Date('2024-04-06'), + new Date('2024-04-07'), + new Date('2024-04-08'), + new Date('2024-04-09'), + ], + interviewStart: new Date('2024-04-05'), + interviewEnd: new Date('2024-04-10'), + }, + { + id: 2, + state: RecruitmentSessionState.Concluded, + slotDuration: 1, + lastModified: new Date('2024-05-10'), + createdAt: new Date('2024-05-04'), + days: [ + new Date('2024-05-05'), + new Date('2024-05-06'), + new Date('2024-05-07'), + new Date('2024-05-08'), + ], + interviewStart: new Date('2024-05-05'), + interviewEnd: new Date('2024-05-09'), + }, + { + id: 3, + state: RecruitmentSessionState.Active, + slotDuration: 1, + lastModified: new Date('2024-05-14'), + createdAt: new Date('2024-05-14'), + days: [ + new Date('2024-05-16'), + new Date('2024-05-17'), + new Date('2024-05-18'), + new Date('2024-05-19'), + new Date('2024-05-20'), + ], + interviewStart: new Date('2024-05-16'), + interviewEnd: new Date('2024-05-21'), + }, + ]; - mockUsers = [ - {oauthId: '1', firstName: 'Pasquale', lastName: 'Bianco', sex: 'male', email: 'p.bianco@gmail.com', phone_no: '3405174444', telegramId: 'pbianco', role: Role.Member, is_board: true, is_expert: true}, - {oauthId: '2', firstName: 'John', lastName: 'Doe', sex: 'male', email: 'j.doe@gmail.com', phone_no: '1234567890', telegramId: 'jdoe', role: Role.Member, is_board: false, is_expert: true}, - {oauthId: '3', firstName: 'Jane', lastName: 'Smith', sex: 'female', email: 'j.smith@gmail.com', phone_no: '9876543210', telegramId: 'jsmith', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '4', firstName: 'Michael', lastName: 'Johnson', sex: 'male', email: 'm.johnson@gmail.com', phone_no: '5555555555', telegramId: 'mjohnson', role: Role.Member, is_board: true, is_expert: true}, - {oauthId: '5', firstName: 'Emily', lastName: 'Brown', sex: 'female', email: 'e.brown@gmail.com', phone_no: '1111111111', telegramId: 'ebrown', role: Role.Member, is_board: false, is_expert: true}, - {oauthId: '6', firstName: 'David', lastName: 'Wilson', sex: 'male', email: 'd.wilson@gmail.com', phone_no: '2222222222', telegramId: 'dwilson', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '7', firstName: 'Olivia', lastName: 'Johnson', sex: 'female', email: 'o.johnson@gmail.com', phone_no: '3333333333', telegramId: 'ojohnson', role: Role.Member, is_board: true, is_expert: false}, - {oauthId: '8', firstName: 'James', lastName: 'Smith', sex: 'male', email: 'j.smith@gmail.com', phone_no: '4444444444', telegramId: 'jsmith', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '9', firstName: 'Sophia', lastName: 'Miller', sex: 'female', email: 's.miller@gmail.com', phone_no: '5555555555', telegramId: 'smiller', role: Role.Member, is_board: false, is_expert: true}, - {oauthId: '10', firstName: 'Benjamin', lastName: 'Davis', sex: 'male', email: 'b.davis@gmail.com', phone_no: '6666666666', telegramId: 'bdavis', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '11', firstName: 'Ava', lastName: 'Wilson', sex: 'female', email: 'a.wilson@gmail.com', phone_no: '7777777777', telegramId: 'awilson', role: Role.Member, is_board: true, is_expert: true}, - {oauthId: '12', firstName: 'William', lastName: 'Anderson',sex: 'male', email: 'w.anderson@gmail.com', phone_no: '8888888888', telegramId: 'wanderson', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '13', firstName: 'Mia', lastName: 'Thomas', sex: 'female', email: 'm.thomas@gmail.com', phone_no: '9999999999', telegramId: 'mthomas', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '14', firstName: 'Alexander', lastName: 'Taylor', sex: 'male', email: 'a.taylor@gmail.com', phone_no: '1111111111', telegramId: 'ataylor', role: Role.Member, is_board: false, is_expert: true}, - {oauthId: '15', firstName: 'Charlotte', lastName: 'Clark', sex: 'female', email: 'c.clark@gmail.com', phone_no: '2222222222', telegramId: 'cclark', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '16', firstName: 'Daniel', lastName: 'Moore', sex: 'male', email: 'd.moore@gmail.com', phone_no: '3333333333', telegramId: 'dmoore', role: Role.Member, is_board: false, is_expert: false}, - {oauthId: '17', firstName: 'Amelia', lastName: 'Walker', sex: 'female', email: 'a.walker@gmail.com', phone_no: '4444444444', telegramId: 'awalker', role: Role.Clerk, is_board: false, is_expert: false}, - {oauthId: '18', firstName: 'Matthew', lastName: 'Lewis', sex: 'male', email: 'm.lewis@gmail.com', phone_no: '5555555555', telegramId: 'mlewis', role: Role.Admin, is_board: false, is_expert: true}, - {oauthId: '19', firstName: 'Ella', lastName: 'Harris', sex: 'female', email: 'e.harris@gmail.com', phone_no: '6666666666', telegramId: 'eharris', role: Role.Supervisor, is_board: false, is_expert: true}, - {oauthId: '20', firstName: 'Joseph', lastName: 'King', sex: 'male', email: 'j.king@gmail.com', phone_no: '7777777777', telegramId: 'jking', role: Role.Supervisor, is_board: true, is_expert: false} - ]; + mockUsers = [ + { + oauthId: '1', + firstName: 'Pasquale', + lastName: 'Bianco', + sex: 'male', + email: 'p.bianco@gmail.com', + phone_no: '3405174444', + telegramId: 'pbianco', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '2', + firstName: 'John', + lastName: 'Doe', + sex: 'male', + email: 'j.doe@gmail.com', + phone_no: '1234567890', + telegramId: 'jdoe', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '3', + firstName: 'Jane', + lastName: 'Smith', + sex: 'female', + email: 'j.smith@gmail.com', + phone_no: '9876543210', + telegramId: 'jsmith', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '4', + firstName: 'Michael', + lastName: 'Johnson', + sex: 'male', + email: 'm.johnson@gmail.com', + phone_no: '5555555555', + telegramId: 'mjohnson', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '5', + firstName: 'Emily', + lastName: 'Brown', + sex: 'female', + email: 'e.brown@gmail.com', + phone_no: '1111111111', + telegramId: 'ebrown', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '6', + firstName: 'David', + lastName: 'Wilson', + sex: 'male', + email: 'd.wilson@gmail.com', + phone_no: '2222222222', + telegramId: 'dwilson', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '7', + firstName: 'Olivia', + lastName: 'Johnson', + sex: 'female', + email: 'o.johnson@gmail.com', + phone_no: '3333333333', + telegramId: 'ojohnson', + role: Role.Member, + is_board: true, + is_expert: false, + }, + { + oauthId: '8', + firstName: 'James', + lastName: 'Smith', + sex: 'male', + email: 'j.smith@gmail.com', + phone_no: '4444444444', + telegramId: 'jsmith', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '9', + firstName: 'Sophia', + lastName: 'Miller', + sex: 'female', + email: 's.miller@gmail.com', + phone_no: '5555555555', + telegramId: 'smiller', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '10', + firstName: 'Benjamin', + lastName: 'Davis', + sex: 'male', + email: 'b.davis@gmail.com', + phone_no: '6666666666', + telegramId: 'bdavis', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '11', + firstName: 'Ava', + lastName: 'Wilson', + sex: 'female', + email: 'a.wilson@gmail.com', + phone_no: '7777777777', + telegramId: 'awilson', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '12', + firstName: 'William', + lastName: 'Anderson', + sex: 'male', + email: 'w.anderson@gmail.com', + phone_no: '8888888888', + telegramId: 'wanderson', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '13', + firstName: 'Mia', + lastName: 'Thomas', + sex: 'female', + email: 'm.thomas@gmail.com', + phone_no: '9999999999', + telegramId: 'mthomas', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '14', + firstName: 'Alexander', + lastName: 'Taylor', + sex: 'male', + email: 'a.taylor@gmail.com', + phone_no: '1111111111', + telegramId: 'ataylor', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '15', + firstName: 'Charlotte', + lastName: 'Clark', + sex: 'female', + email: 'c.clark@gmail.com', + phone_no: '2222222222', + telegramId: 'cclark', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '16', + firstName: 'Daniel', + lastName: 'Moore', + sex: 'male', + email: 'd.moore@gmail.com', + phone_no: '3333333333', + telegramId: 'dmoore', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '17', + firstName: 'Amelia', + lastName: 'Walker', + sex: 'female', + email: 'a.walker@gmail.com', + phone_no: '4444444444', + telegramId: 'awalker', + role: Role.Clerk, + is_board: false, + is_expert: false, + }, + { + oauthId: '18', + firstName: 'Matthew', + lastName: 'Lewis', + sex: 'male', + email: 'm.lewis@gmail.com', + phone_no: '5555555555', + telegramId: 'mlewis', + role: Role.Admin, + is_board: false, + is_expert: true, + }, + { + oauthId: '19', + firstName: 'Ella', + lastName: 'Harris', + sex: 'female', + email: 'e.harris@gmail.com', + phone_no: '6666666666', + telegramId: 'eharris', + role: Role.Supervisor, + is_board: false, + is_expert: true, + }, + { + oauthId: '20', + firstName: 'Joseph', + lastName: 'King', + sex: 'male', + email: 'j.king@gmail.com', + phone_no: '7777777777', + telegramId: 'jking', + role: Role.Supervisor, + is_board: true, + is_expert: false, + }, + ]; + }); + beforeEach(async () => { + app = await createApp(); + timeSlotsService = app.get(TimeSlotsService); + availabilityService = app.get(AvailabilityService); + recruitmentSessionService = app.get( + RecruitmentSessionService, + ); + usersService = app.get(UsersService); + }); - }); + afterEach(async () => { + await app.close(); + }); + describe(' GET /timeslots', () => { beforeEach(async () => { - app = await createApp(); - timeSlotsService = app.get(TimeSlotsService); - availabilityService = app.get(AvailabilityService); - recruitmentSessionService = app.get(RecruitmentSessionService); - usersService = app.get(UsersService); - }); - - afterEach(async () => { - await app.close(); + await mockUsers.forEach(async (u) => await usersService.create(u)); + await mockTimeSlots.forEach( + async (ts) => await timeSlotsService.createTimeSlot(ts), + ); + await mockAvailability.forEach( + async (a) => await availabilityService.createAvailability(a), + ); + await mockRecruitmentSessions.forEach( + async (rs) => + await recruitmentSessionService.createRecruitmentSession(rs), + ); }); - describe(" GET /timeslots", () => { - beforeEach(async () => { - await mockUsers.forEach(async (u) => await usersService.create(u)); - await mockTimeSlots.forEach(async (ts) => await timeSlotsService.createTimeSlot(ts)); - await mockAvailability.forEach(async (a) => await availabilityService.createAvailability(a)); - await mockRecruitmentSessions.forEach(async (rs) => await recruitmentSessionService.createRecruitmentSession(rs)); - }); - - it('should return all available timeslots', async () => { - - return await request(app.getHttpServer()) - .get('/timeslots') - .set('Authorization', `Bearer ${newMemberToken}`) - .expect(200) - .expect((res) => { - console.log(res.body); - expect(res.body).toBeInstanceOf(Array); - }); + it('should return all available timeslots', async () => { + return await request(app.getHttpServer()) + .get('/timeslots') + .set('Authorization', `Bearer ${newMemberToken}`) + .expect(200) + .expect((res) => { + console.log(res.body); + expect(res.body).toBeInstanceOf(Array); }); }); - -}); \ No newline at end of file + }); +}); From d034c3315c12cf4e344b224ad3aad0c6b7c1d31d Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:12:59 +0200 Subject: [PATCH 13/57] fix: sql errors --- api/src/timeslots/timeslots.e2e-spec.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index e8b7512..d265175 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1190,6 +1190,10 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { + await mockRecruitmentSessions.forEach( + async (rs) => + await recruitmentSessionService.createRecruitmentSession(rs), + ); await mockUsers.forEach(async (u) => await usersService.create(u)); await mockTimeSlots.forEach( async (ts) => await timeSlotsService.createTimeSlot(ts), @@ -1197,13 +1201,21 @@ describe('TimeslotsController', () => { await mockAvailability.forEach( async (a) => await availabilityService.createAvailability(a), ); - await mockRecruitmentSessions.forEach( - async (rs) => - await recruitmentSessionService.createRecruitmentSession(rs), - ); }); it('should return all available timeslots', async () => { + const expected = [ + { + id: 71, + start: '2024-05-19T13:00:00.000Z', + end: '2024-05-19T14:00:00.000Z', + }, + { + id: 73, + start: '2024-05-19T15:00:00.000Z', + end: '2024-05-19T16:00:00.000Z', + }, + ]; return await request(app.getHttpServer()) .get('/timeslots') .set('Authorization', `Bearer ${newMemberToken}`) @@ -1211,6 +1223,7 @@ describe('TimeslotsController', () => { .expect((res) => { console.log(res.body); expect(res.body).toBeInstanceOf(Array); + expect(res.body).toEqual(expected); }); }); }); From 127c223a9abff5c60c73a9e6af382a6f13223918 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:21:19 +0200 Subject: [PATCH 14/57] fix: sql error 2 --- api/src/timeslots/timeslots.e2e-spec.ts | 35 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index d265175..4bb9b6a 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1190,17 +1190,32 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { - await mockRecruitmentSessions.forEach( - async (rs) => - await recruitmentSessionService.createRecruitmentSession(rs), - ); - await mockUsers.forEach(async (u) => await usersService.create(u)); - await mockTimeSlots.forEach( - async (ts) => await timeSlotsService.createTimeSlot(ts), - ); - await mockAvailability.forEach( - async (a) => await availabilityService.createAvailability(a), + + let promises = []; + mockRecruitmentSessions.forEach(async (rs) => + promises.push(recruitmentSessionService.createRecruitmentSession(rs)), ); + + Promise.all(promises).then(() => { + promises = []; + mockUsers.forEach(async (u) => promises.push(usersService.create(u))); + + Promise.all(promises).then(() => { + promises = []; + mockTimeSlots.forEach(async (ts) => + promises.push(timeSlotsService.createTimeSlot(ts)), + ); + + Promise.all(promises).then(() => { + promises = []; + mockAvailability.forEach(async (a) => + promises.push(availabilityService.createAvailability(a)), + ); + + Promise.all(promises); + }); + }); + }); }); it('should return all available timeslots', async () => { From cd7290964c68e439aa487824ebcabca383eafa75 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:22:17 +0200 Subject: [PATCH 15/57] format --- api/src/timeslots/timeslots.e2e-spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 4bb9b6a..64a547d 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1190,7 +1190,6 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { - let promises = []; mockRecruitmentSessions.forEach(async (rs) => promises.push(recruitmentSessionService.createRecruitmentSession(rs)), From 520a1f4f2ecd932f4a31e74ec9cf12032b67e570 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:29:06 +0200 Subject: [PATCH 16/57] fix: minor --- api/src/timeslots/timeslots.e2e-spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 64a547d..fd96f40 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1220,14 +1220,14 @@ describe('TimeslotsController', () => { it('should return all available timeslots', async () => { const expected = [ { + end: '2024-05-19T14:00:00.000Z', id: 71, start: '2024-05-19T13:00:00.000Z', - end: '2024-05-19T14:00:00.000Z', }, { + end: '2024-05-19T16:00:00.000Z', id: 73, start: '2024-05-19T15:00:00.000Z', - end: '2024-05-19T16:00:00.000Z', }, ]; return await request(app.getHttpServer()) @@ -1235,7 +1235,6 @@ describe('TimeslotsController', () => { .set('Authorization', `Bearer ${newMemberToken}`) .expect(200) .expect((res) => { - console.log(res.body); expect(res.body).toBeInstanceOf(Array); expect(res.body).toEqual(expected); }); From 3a8c0d23f03e007698d25965888ae4a8a050c05c Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:35:46 +0200 Subject: [PATCH 17/57] fix: 2 --- api/src/timeslots/timeslots.e2e-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index fd96f40..6b63e08 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1195,7 +1195,7 @@ describe('TimeslotsController', () => { promises.push(recruitmentSessionService.createRecruitmentSession(rs)), ); - Promise.all(promises).then(() => { + await Promise.all(promises).then(() => { promises = []; mockUsers.forEach(async (u) => promises.push(usersService.create(u))); @@ -1231,7 +1231,7 @@ describe('TimeslotsController', () => { }, ]; return await request(app.getHttpServer()) - .get('/timeslots') + .get('/v1/timeslots') .set('Authorization', `Bearer ${newMemberToken}`) .expect(200) .expect((res) => { From 50533c5cc6eb7eef7573daed7b32a79e4512cb15 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:38:34 +0200 Subject: [PATCH 18/57] fix: 3 --- api/src/timeslots/timeslots.e2e-spec.ts | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 6b63e08..7259342 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1191,30 +1191,30 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { let promises = []; - mockRecruitmentSessions.forEach(async (rs) => + mockRecruitmentSessions.forEach((rs) => promises.push(recruitmentSessionService.createRecruitmentSession(rs)), ); - await Promise.all(promises).then(() => { - promises = []; - mockUsers.forEach(async (u) => promises.push(usersService.create(u))); + await Promise.all(promises); - Promise.all(promises).then(() => { - promises = []; - mockTimeSlots.forEach(async (ts) => - promises.push(timeSlotsService.createTimeSlot(ts)), - ); + promises = []; + mockUsers.forEach((u) => promises.push(usersService.create(u))); - Promise.all(promises).then(() => { - promises = []; - mockAvailability.forEach(async (a) => - promises.push(availabilityService.createAvailability(a)), - ); + await Promise.all(promises); - Promise.all(promises); - }); - }); - }); + promises = []; + mockTimeSlots.forEach((ts) => + promises.push(timeSlotsService.createTimeSlot(ts)), + ); + + await Promise.all(promises); + + promises = []; + mockAvailability.forEach((a) => + promises.push(availabilityService.createAvailability(a)), + ); + + await Promise.all(promises); }); it('should return all available timeslots', async () => { From 682b5b8c79fe86b1d8ba56025e1695948ae37038 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:42:00 +0200 Subject: [PATCH 19/57] fix 4 --- api/src/timeslots/timeslots.e2e-spec.ts | 36 +++++++++---------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 7259342..dc590b3 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -1190,31 +1190,21 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { - let promises = []; - mockRecruitmentSessions.forEach((rs) => - promises.push(recruitmentSessionService.createRecruitmentSession(rs)), - ); + for (const rs of mockRecruitmentSessions) { + await recruitmentSessionService.createRecruitmentSession(rs); + } - await Promise.all(promises); + for (const u of mockUsers) { + await usersService.create(u); + } - promises = []; - mockUsers.forEach((u) => promises.push(usersService.create(u))); + for (const ts of mockTimeSlots) { + await timeSlotsService.createTimeSlot(ts); + } - await Promise.all(promises); - - promises = []; - mockTimeSlots.forEach((ts) => - promises.push(timeSlotsService.createTimeSlot(ts)), - ); - - await Promise.all(promises); - - promises = []; - mockAvailability.forEach((a) => - promises.push(availabilityService.createAvailability(a)), - ); - - await Promise.all(promises); + for (const a of mockAvailability) { + await availabilityService.createAvailability(a); + } }); it('should return all available timeslots', async () => { @@ -1231,7 +1221,7 @@ describe('TimeslotsController', () => { }, ]; return await request(app.getHttpServer()) - .get('/v1/timeslots') + .get('/timeslots') .set('Authorization', `Bearer ${newMemberToken}`) .expect(200) .expect((res) => { From d7dd6c3be2055fd183994fabab3da9bef881bb18 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:51:02 +0200 Subject: [PATCH 20/57] fix: 6 --- api/src/timeslots/timeslots.e2e-spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index dc590b3..8a27c0e 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -13,7 +13,7 @@ import { } from '@hkrecruitment/shared'; import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; import { Availability } from 'src/availability/availability.entity'; -import { createApp, getAccessToken, getSub } from 'test/app.e2e-spec'; +import { createApp, getAccessToken } from 'test/app.e2e-spec'; describe('TimeslotsController', () => { let app: INestApplication; @@ -1220,14 +1220,17 @@ describe('TimeslotsController', () => { start: '2024-05-19T15:00:00.000Z', }, ]; - return await request(app.getHttpServer()) + await request(app.getHttpServer()) .get('/timeslots') .set('Authorization', `Bearer ${newMemberToken}`) .expect(200) .expect((res) => { expect(res.body).toBeInstanceOf(Array); - expect(res.body).toEqual(expected); }); + + const availableTimeSlots = + await timeSlotsService.findAvailableTimeSlots(); + expect(availableTimeSlots).toEqual(expected); }); }); }); From 2154316e98b8d68ae8ff072822905d3462e22dea Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 19:57:36 +0200 Subject: [PATCH 21/57] fix: 5 --- api/src/timeslots/timeslots.e2e-spec.ts | 78 ++++++------------------- 1 file changed, 19 insertions(+), 59 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 8a27c0e..a0319e7 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -935,10 +935,8 @@ describe('TimeslotsController', () => { oauthId: '1', firstName: 'Pasquale', lastName: 'Bianco', - sex: 'male', + sex: 'M', email: 'p.bianco@gmail.com', - phone_no: '3405174444', - telegramId: 'pbianco', role: Role.Member, is_board: true, is_expert: true, @@ -947,10 +945,8 @@ describe('TimeslotsController', () => { oauthId: '2', firstName: 'John', lastName: 'Doe', - sex: 'male', + sex: 'M', email: 'j.doe@gmail.com', - phone_no: '1234567890', - telegramId: 'jdoe', role: Role.Member, is_board: false, is_expert: true, @@ -959,10 +955,8 @@ describe('TimeslotsController', () => { oauthId: '3', firstName: 'Jane', lastName: 'Smith', - sex: 'female', + sex: 'F', email: 'j.smith@gmail.com', - phone_no: '9876543210', - telegramId: 'jsmith', role: Role.Member, is_board: false, is_expert: false, @@ -971,10 +965,8 @@ describe('TimeslotsController', () => { oauthId: '4', firstName: 'Michael', lastName: 'Johnson', - sex: 'male', + sex: 'M', email: 'm.johnson@gmail.com', - phone_no: '5555555555', - telegramId: 'mjohnson', role: Role.Member, is_board: true, is_expert: true, @@ -983,10 +975,8 @@ describe('TimeslotsController', () => { oauthId: '5', firstName: 'Emily', lastName: 'Brown', - sex: 'female', + sex: 'F', email: 'e.brown@gmail.com', - phone_no: '1111111111', - telegramId: 'ebrown', role: Role.Member, is_board: false, is_expert: true, @@ -995,10 +985,8 @@ describe('TimeslotsController', () => { oauthId: '6', firstName: 'David', lastName: 'Wilson', - sex: 'male', + sex: 'M', email: 'd.wilson@gmail.com', - phone_no: '2222222222', - telegramId: 'dwilson', role: Role.Member, is_board: false, is_expert: false, @@ -1007,10 +995,8 @@ describe('TimeslotsController', () => { oauthId: '7', firstName: 'Olivia', lastName: 'Johnson', - sex: 'female', + sex: 'F', email: 'o.johnson@gmail.com', - phone_no: '3333333333', - telegramId: 'ojohnson', role: Role.Member, is_board: true, is_expert: false, @@ -1019,10 +1005,8 @@ describe('TimeslotsController', () => { oauthId: '8', firstName: 'James', lastName: 'Smith', - sex: 'male', + sex: 'M', email: 'j.smith@gmail.com', - phone_no: '4444444444', - telegramId: 'jsmith', role: Role.Member, is_board: false, is_expert: false, @@ -1031,10 +1015,8 @@ describe('TimeslotsController', () => { oauthId: '9', firstName: 'Sophia', lastName: 'Miller', - sex: 'female', + sex: 'F', email: 's.miller@gmail.com', - phone_no: '5555555555', - telegramId: 'smiller', role: Role.Member, is_board: false, is_expert: true, @@ -1043,10 +1025,8 @@ describe('TimeslotsController', () => { oauthId: '10', firstName: 'Benjamin', lastName: 'Davis', - sex: 'male', + sex: 'M', email: 'b.davis@gmail.com', - phone_no: '6666666666', - telegramId: 'bdavis', role: Role.Member, is_board: false, is_expert: false, @@ -1057,8 +1037,6 @@ describe('TimeslotsController', () => { lastName: 'Wilson', sex: 'female', email: 'a.wilson@gmail.com', - phone_no: '7777777777', - telegramId: 'awilson', role: Role.Member, is_board: true, is_expert: true, @@ -1067,10 +1045,8 @@ describe('TimeslotsController', () => { oauthId: '12', firstName: 'William', lastName: 'Anderson', - sex: 'male', + sex: 'M', email: 'w.anderson@gmail.com', - phone_no: '8888888888', - telegramId: 'wanderson', role: Role.Member, is_board: false, is_expert: false, @@ -1079,10 +1055,8 @@ describe('TimeslotsController', () => { oauthId: '13', firstName: 'Mia', lastName: 'Thomas', - sex: 'female', + sex: 'F', email: 'm.thomas@gmail.com', - phone_no: '9999999999', - telegramId: 'mthomas', role: Role.Member, is_board: false, is_expert: false, @@ -1091,10 +1065,8 @@ describe('TimeslotsController', () => { oauthId: '14', firstName: 'Alexander', lastName: 'Taylor', - sex: 'male', + sex: 'M', email: 'a.taylor@gmail.com', - phone_no: '1111111111', - telegramId: 'ataylor', role: Role.Member, is_board: false, is_expert: true, @@ -1103,10 +1075,8 @@ describe('TimeslotsController', () => { oauthId: '15', firstName: 'Charlotte', lastName: 'Clark', - sex: 'female', + sex: 'F', email: 'c.clark@gmail.com', - phone_no: '2222222222', - telegramId: 'cclark', role: Role.Member, is_board: false, is_expert: false, @@ -1115,10 +1085,8 @@ describe('TimeslotsController', () => { oauthId: '16', firstName: 'Daniel', lastName: 'Moore', - sex: 'male', + sex: 'M', email: 'd.moore@gmail.com', - phone_no: '3333333333', - telegramId: 'dmoore', role: Role.Member, is_board: false, is_expert: false, @@ -1127,10 +1095,8 @@ describe('TimeslotsController', () => { oauthId: '17', firstName: 'Amelia', lastName: 'Walker', - sex: 'female', + sex: 'F', email: 'a.walker@gmail.com', - phone_no: '4444444444', - telegramId: 'awalker', role: Role.Clerk, is_board: false, is_expert: false, @@ -1139,10 +1105,8 @@ describe('TimeslotsController', () => { oauthId: '18', firstName: 'Matthew', lastName: 'Lewis', - sex: 'male', + sex: 'M', email: 'm.lewis@gmail.com', - phone_no: '5555555555', - telegramId: 'mlewis', role: Role.Admin, is_board: false, is_expert: true, @@ -1151,10 +1115,8 @@ describe('TimeslotsController', () => { oauthId: '19', firstName: 'Ella', lastName: 'Harris', - sex: 'female', + sex: 'F', email: 'e.harris@gmail.com', - phone_no: '6666666666', - telegramId: 'eharris', role: Role.Supervisor, is_board: false, is_expert: true, @@ -1163,10 +1125,8 @@ describe('TimeslotsController', () => { oauthId: '20', firstName: 'Joseph', lastName: 'King', - sex: 'male', + sex: 'M', email: 'j.king@gmail.com', - phone_no: '7777777777', - telegramId: 'jking', role: Role.Supervisor, is_board: true, is_expert: false, From fc00fc51b6e88fae5aa927a72826f337067646f7 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 20:11:21 +0200 Subject: [PATCH 22/57] fix: 7 --- api/src/timeslots/create-timeslot.dto.ts | 5 ++ api/src/timeslots/timeslots.e2e-spec.ts | 83 +----------------------- shared/src/timeslot.ts | 4 ++ 3 files changed, 11 insertions(+), 81 deletions(-) diff --git a/api/src/timeslots/create-timeslot.dto.ts b/api/src/timeslots/create-timeslot.dto.ts index 704d66a..0b50071 100644 --- a/api/src/timeslots/create-timeslot.dto.ts +++ b/api/src/timeslots/create-timeslot.dto.ts @@ -1,5 +1,7 @@ import { TimeSlot } from '@hkrecruitment/shared'; import { ApiProperty } from '@nestjs/swagger'; +import { Availability } from 'src/availability/availability.entity'; +import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; export class CreateTimeSlotDto implements Partial { @ApiProperty() // @@ -7,4 +9,7 @@ export class CreateTimeSlotDto implements Partial { @ApiProperty() end: Date; + + @ApiProperty() + recruitmentSession: RecruitmentSession; } diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index a0319e7..41cedb9 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -14,6 +14,7 @@ import { import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; import { Availability } from 'src/availability/availability.entity'; import { createApp, getAccessToken } from 'test/app.e2e-spec'; +import { CreateTimeSlotDto } from './create-timeslot.dto'; describe('TimeslotsController', () => { let app: INestApplication; @@ -23,7 +24,7 @@ describe('TimeslotsController', () => { let availabilityService: AvailabilityService; let recruitmentSessionService: RecruitmentSessionService; let mockUsers: Person[]; - let mockTimeSlots: TimeSlot[]; + let mockTimeSlots: CreateTimeSlotDto[]; let mockRecruitmentSessions: RecruitmentSession[]; let mockAvailability: Availability[]; @@ -32,181 +33,151 @@ describe('TimeslotsController', () => { mockTimeSlots = [ { - id: 1, start: new Date('2024-04-05 10:00:00'), end: new Date('2024-05-21 11:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 2, start: new Date('2024-04-05 11:00:00'), end: new Date('2024-05-21 12:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 3, start: new Date('2024-04-05 15:00:00'), end: new Date('2024-05-21 16:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 4, start: new Date('2024-04-05 16:00:00'), end: new Date('2024-05-21 17:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 5, start: new Date('2024-04-05 17:00:00'), end: new Date('2024-05-21 18:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 6, start: new Date('2024-04-05 18:00:00'), end: new Date('2024-05-21 19:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 7, start: new Date('2024-04-06 10:00:00'), end: new Date('2024-06-21 11:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 8, start: new Date('2024-04-06 11:00:00'), end: new Date('2024-06-21 12:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 9, start: new Date('2024-04-06 15:00:00'), end: new Date('2024-06-21 16:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 10, start: new Date('2024-04-06 16:00:00'), end: new Date('2024-06-21 17:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 11, start: new Date('2024-04-06 17:00:00'), end: new Date('2024-06-21 18:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 12, start: new Date('2024-04-06 18:00:00'), end: new Date('2024-06-21 19:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 13, start: new Date('2024-04-07 10:00:00'), end: new Date('2024-07-21 11:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 14, start: new Date('2024-04-07 11:00:00'), end: new Date('2024-07-21 12:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 15, start: new Date('2024-04-07 15:00:00'), end: new Date('2024-07-21 16:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 16, start: new Date('2024-04-07 16:00:00'), end: new Date('2024-07-21 17:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 17, start: new Date('2024-04-07 17:00:00'), end: new Date('2024-07-21 18:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 18, start: new Date('2024-04-07 18:00:00'), end: new Date('2024-07-21 19:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 19, start: new Date('2024-04-08 10:00:00'), end: new Date('2024-08-21 11:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 20, start: new Date('2024-04-08 11:00:00'), end: new Date('2024-08-21 12:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 21, start: new Date('2024-04-08 15:00:00'), end: new Date('2024-08-21 16:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 22, start: new Date('2024-04-08 16:00:00'), end: new Date('2024-08-21 17:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 23, start: new Date('2024-04-08 17:00:00'), end: new Date('2024-08-21 18:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 24, start: new Date('2024-04-08 18:00:00'), end: new Date('2024-08-21 19:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 25, start: new Date('2024-04-09 10:00:00'), end: new Date('2024-09-21 11:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 26, start: new Date('2024-04-09 11:00:00'), end: new Date('2024-09-21 12:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 27, start: new Date('2024-04-09 15:00:00'), end: new Date('2024-09-21 16:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 28, start: new Date('2024-04-09 16:00:00'), end: new Date('2024-09-21 17:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 29, start: new Date('2024-04-09 17:00:00'), end: new Date('2024-09-21 18:00:00'), recruitmentSession: 1, } as TimeSlot, { - id: 30, start: new Date('2024-04-09 18:00:00'), end: new Date('2024-09-21 19:00:00'), recruitmentSession: 1, @@ -218,295 +189,245 @@ describe('TimeslotsController', () => { recruitmentSession: 2, } as TimeSlot, { - id: 32, start: new Date('2024-05-05 11:00:00'), end: new Date('2024-05-05 12:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 33, start: new Date('2024-05-05 15:00:00'), end: new Date('2024-05-05 16:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 34, start: new Date('2024-05-05 16:00:00'), end: new Date('2024-05-05 17:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 35, start: new Date('2024-05-05 17:00:00'), end: new Date('2024-05-05 18:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 36, start: new Date('2024-05-05 18:00:00'), end: new Date('2024-05-05 19:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 37, start: new Date('2024-05-06 10:00:00'), end: new Date('2024-05-06 11:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 38, start: new Date('2024-05-06 11:00:00'), end: new Date('2024-05-06 12:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 39, start: new Date('2024-05-06 15:00:00'), end: new Date('2024-05-06 16:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 40, - start: new Date('2024-05-06 16:00:00'), end: new Date('2024-05-06 17:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 41, start: new Date('2024-05-06 17:00:00'), end: new Date('2024-05-06 18:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 42, start: new Date('2024-05-06 18:00:00'), end: new Date('2024-05-06 19:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 43, start: new Date('2024-05-07 10:00:00'), end: new Date('2024-05-07 11:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 44, start: new Date('2024-05-07 11:00:00'), end: new Date('2024-05-07 12:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 45, start: new Date('2024-05-07 15:00:00'), end: new Date('2024-05-07 16:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 46, start: new Date('2024-05-07 16:00:00'), end: new Date('2024-05-07 17:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 47, start: new Date('2024-05-07 17:00:00'), end: new Date('2024-05-07 18:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 48, start: new Date('2024-05-07 18:00:00'), end: new Date('2024-05-07 19:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 49, start: new Date('2024-05-08 10:00:00'), end: new Date('2024-05-08 11:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 50, start: new Date('2024-05-08 11:00:00'), end: new Date('2024-05-08 12:00:00'), recruitmentSession: 2, } as TimeSlot, { - id: 51, start: new Date('2024-05-16 10:00:00'), end: new Date('2024-05-16 11:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 52, start: new Date('2024-05-16 11:00:00'), end: new Date('2024-05-16 12:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 53, start: new Date('2024-05-16 15:00:00'), end: new Date('2024-05-16 16:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 54, start: new Date('2024-05-16 16:00:00'), end: new Date('2024-05-16 17:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 55, start: new Date('2024-05-16 17:00:00'), end: new Date('2024-05-16 18:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 56, start: new Date('2024-05-16 18:00:00'), end: new Date('2024-05-16 19:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 57, start: new Date('2024-05-17 10:00:00'), end: new Date('2024-05-17 11:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 58, start: new Date('2024-05-17 11:00:00'), end: new Date('2024-05-17 12:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 59, start: new Date('2024-05-17 15:00:00'), end: new Date('2024-05-17 16:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 60, start: new Date('2024-05-17 16:00:00'), end: new Date('2024-05-17 17:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 61, start: new Date('2024-05-17 17:00:00'), end: new Date('2024-05-17 18:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 62, start: new Date('2024-05-17 18:00:00'), end: new Date('2024-05-17 19:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 63, start: new Date('2024-05-18 10:00:00'), end: new Date('2024-05-18 11:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 64, start: new Date('2024-05-18 11:00:00'), end: new Date('2024-05-18 12:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 65, start: new Date('2024-05-18 15:00:00'), end: new Date('2024-05-18 16:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 66, start: new Date('2024-05-18 16:00:00'), end: new Date('2024-05-18 17:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 67, start: new Date('2024-05-18 17:00:00'), end: new Date('2024-05-18 18:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 68, start: new Date('2024-05-18 18:00:00'), end: new Date('2024-05-18 19:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 69, start: new Date('2024-05-19 10:00:00'), end: new Date('2024-05-19 11:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 70, start: new Date('2024-05-19 11:00:00'), end: new Date('2024-05-19 12:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 71, start: new Date('2024-05-19 15:00:00'), end: new Date('2024-05-19 16:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 72, start: new Date('2024-05-19 16:00:00'), end: new Date('2024-05-19 17:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 73, start: new Date('2024-05-19 17:00:00'), end: new Date('2024-05-19 18:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 74, start: new Date('2024-05-19 18:00:00'), end: new Date('2024-05-19 19:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 75, start: new Date('2024-05-20 10:00:00'), end: new Date('2024-05-20 11:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 76, start: new Date('2024-05-20 11:00:00'), end: new Date('2024-05-20 12:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 77, start: new Date('2024-05-20 15:00:00'), end: new Date('2024-05-20 16:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 78, start: new Date('2024-05-20 16:00:00'), end: new Date('2024-05-20 17:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 79, start: new Date('2024-05-20 17:00:00'), end: new Date('2024-05-20 18:00:00'), recruitmentSession: 3, } as TimeSlot, { - id: 80, start: new Date('2024-05-20 18:00:00'), end: new Date('2024-05-20 19:00:00'), recruitmentSession: 3, diff --git a/shared/src/timeslot.ts b/shared/src/timeslot.ts index 6343754..3665746 100644 --- a/shared/src/timeslot.ts +++ b/shared/src/timeslot.ts @@ -1,10 +1,14 @@ +import { Availability } from "availability"; import { Action, ApplyAbilities } from "./abilities"; import { Role } from "./person"; +import { RecruitmentSession } from "recruitment-session"; export interface TimeSlot { id: number; start: Date; end: Date; + recruitmentSession: RecruitmentSession; + availabilities: Availability[]; } /* Abilities */ From 5aa5400f0969b71cab798e194b381a3b50f20741 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 20:47:12 +0200 Subject: [PATCH 23/57] fix: 8 --- api/src/mocks/data.ts | 15 +- api/src/timeslots/timeslot.entity.ts | 1 - api/src/timeslots/timeslots.e2e-spec.ts | 1138 ++++++++----------- api/src/timeslots/timeslots.service.spec.ts | 8 +- api/src/timeslots/timeslots.service.ts | 8 +- shared/src/timeslot.ts | 4 +- 6 files changed, 474 insertions(+), 700 deletions(-) diff --git a/api/src/mocks/data.ts b/api/src/mocks/data.ts index 64b1c36..c4c0bd3 100644 --- a/api/src/mocks/data.ts +++ b/api/src/mocks/data.ts @@ -28,13 +28,6 @@ export const testDateTime1Hour = new Date(2023, 0, 1, 11, 30, 0); export const testDateTime3Hours = new Date(2023, 0, 1, 13, 30, 0); export const testDateTimeEnd = new Date(2023, 0, 1, 11, 30, 0); -export const mockTimeSlot = { - start: testDateTimeStart, - end: testDateTimeEnd, - id: 1, - availabilities: [], -} as TimeSlot & { availabilities: any[] }; - export const testInterviewStart = '11:55' as unknown as Date; export const testInterviewEnd = '20:35' as unknown as Date; export const testDay1 = '2024-10-20' as unknown as Date; @@ -54,6 +47,14 @@ export const mockRecruitmentSession = { lastModified: testDateLastModified, }; +export const mockTimeSlot = { + recruitmentSession: mockRecruitmentSession, + start: testDateTimeStart, + end: testDateTimeEnd, + id: 1, + availabilities: [], +} as TimeSlot & { availabilities: any[] }; + export const mockCreateRecruitmentSessionDto = { slotDuration: 50, interviewStart: testInterviewStart, diff --git a/api/src/timeslots/timeslot.entity.ts b/api/src/timeslots/timeslot.entity.ts index de1e72d..4d8f8db 100644 --- a/api/src/timeslots/timeslot.entity.ts +++ b/api/src/timeslots/timeslot.entity.ts @@ -1,7 +1,6 @@ import { Column, Entity, - JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 41cedb9..a47ed8e 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -14,7 +14,6 @@ import { import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; import { Availability } from 'src/availability/availability.entity'; import { createApp, getAccessToken } from 'test/app.e2e-spec'; -import { CreateTimeSlotDto } from './create-timeslot.dto'; describe('TimeslotsController', () => { let app: INestApplication; @@ -24,414 +23,447 @@ describe('TimeslotsController', () => { let availabilityService: AvailabilityService; let recruitmentSessionService: RecruitmentSessionService; let mockUsers: Person[]; - let mockTimeSlots: CreateTimeSlotDto[]; + let mockTimeSlots: TimeSlot[]; let mockRecruitmentSessions: RecruitmentSession[]; let mockAvailability: Availability[]; beforeAll(async () => { newMemberToken = await getAccessToken('newMember'); - mockTimeSlots = [ + mockRecruitmentSessions = [ { - start: new Date('2024-04-05 10:00:00'), - end: new Date('2024-05-21 11:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-05 11:00:00'), - end: new Date('2024-05-21 12:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-05 15:00:00'), - end: new Date('2024-05-21 16:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-05 16:00:00'), - end: new Date('2024-05-21 17:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-05 17:00:00'), - end: new Date('2024-05-21 18:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-05 18:00:00'), - end: new Date('2024-05-21 19:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-06 10:00:00'), - end: new Date('2024-06-21 11:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-06 11:00:00'), - end: new Date('2024-06-21 12:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-06 15:00:00'), - end: new Date('2024-06-21 16:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-06 16:00:00'), - end: new Date('2024-06-21 17:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-06 17:00:00'), - end: new Date('2024-06-21 18:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-06 18:00:00'), - end: new Date('2024-06-21 19:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-07 10:00:00'), - end: new Date('2024-07-21 11:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-07 11:00:00'), - end: new Date('2024-07-21 12:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-07 15:00:00'), - end: new Date('2024-07-21 16:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-07 16:00:00'), - end: new Date('2024-07-21 17:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-07 17:00:00'), - end: new Date('2024-07-21 18:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-07 18:00:00'), - end: new Date('2024-07-21 19:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-08 10:00:00'), - end: new Date('2024-08-21 11:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-08 11:00:00'), - end: new Date('2024-08-21 12:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-08 15:00:00'), - end: new Date('2024-08-21 16:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-08 16:00:00'), - end: new Date('2024-08-21 17:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-08 17:00:00'), - end: new Date('2024-08-21 18:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-08 18:00:00'), - end: new Date('2024-08-21 19:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-09 10:00:00'), - end: new Date('2024-09-21 11:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-09 11:00:00'), - end: new Date('2024-09-21 12:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-09 15:00:00'), - end: new Date('2024-09-21 16:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-09 16:00:00'), - end: new Date('2024-09-21 17:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-09 17:00:00'), - end: new Date('2024-09-21 18:00:00'), - recruitmentSession: 1, - } as TimeSlot, - { - start: new Date('2024-04-09 18:00:00'), - end: new Date('2024-09-21 19:00:00'), - recruitmentSession: 1, - } as TimeSlot, + id: 1, + state: RecruitmentSessionState.Concluded, + slotDuration: 1, + lastModified: new Date('2024-04-10'), + createdAt: new Date('2024-04-04'), + days: [ + new Date('2024-04-05'), + new Date('2024-04-06'), + new Date('2024-04-07'), + new Date('2024-04-08'), + new Date('2024-04-09'), + ], + interviewStart: new Date('2024-04-05'), + interviewEnd: new Date('2024-04-10'), + }, { - id: 31, - start: new Date('2024-05-05 10:00:00'), - end: new Date('2024-05-05 11:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-05 11:00:00'), - end: new Date('2024-05-05 12:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-05 15:00:00'), - end: new Date('2024-05-05 16:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-05 16:00:00'), - end: new Date('2024-05-05 17:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-05 17:00:00'), - end: new Date('2024-05-05 18:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-05 18:00:00'), - end: new Date('2024-05-05 19:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-06 10:00:00'), - end: new Date('2024-05-06 11:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-06 11:00:00'), - end: new Date('2024-05-06 12:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-06 15:00:00'), - end: new Date('2024-05-06 16:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - end: new Date('2024-05-06 17:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-06 17:00:00'), - end: new Date('2024-05-06 18:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-06 18:00:00'), - end: new Date('2024-05-06 19:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-07 10:00:00'), - end: new Date('2024-05-07 11:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-07 11:00:00'), - end: new Date('2024-05-07 12:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-07 15:00:00'), - end: new Date('2024-05-07 16:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-07 16:00:00'), - end: new Date('2024-05-07 17:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-07 17:00:00'), - end: new Date('2024-05-07 18:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-07 18:00:00'), - end: new Date('2024-05-07 19:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-08 10:00:00'), - end: new Date('2024-05-08 11:00:00'), - recruitmentSession: 2, - } as TimeSlot, - { - start: new Date('2024-05-08 11:00:00'), - end: new Date('2024-05-08 12:00:00'), - recruitmentSession: 2, - } as TimeSlot, + id: 2, + state: RecruitmentSessionState.Concluded, + slotDuration: 1, + lastModified: new Date('2024-05-10'), + createdAt: new Date('2024-05-04'), + days: [ + new Date('2024-05-05'), + new Date('2024-05-06'), + new Date('2024-05-07'), + new Date('2024-05-08'), + ], + interviewStart: new Date('2024-05-05'), + interviewEnd: new Date('2024-05-09'), + }, + { + id: 3, + state: RecruitmentSessionState.Active, + slotDuration: 1, + lastModified: new Date('2024-05-14'), + createdAt: new Date('2024-05-14'), + days: [ + new Date('2024-05-16'), + new Date('2024-05-17'), + new Date('2024-05-18'), + new Date('2024-05-19'), + new Date('2024-05-20'), + ], + interviewStart: new Date('2024-05-16'), + interviewEnd: new Date('2024-05-21'), + }, + ]; + + mockUsers = [ + { + oauthId: '1', + firstName: 'Pasquale', + lastName: 'Bianco', + sex: 'M', + email: 'p.bianco@gmail.com', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '2', + firstName: 'John', + lastName: 'Doe', + sex: 'M', + email: 'j.doe@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '3', + firstName: 'Jane', + lastName: 'Smith', + sex: 'F', + email: 'j.smith@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '4', + firstName: 'Michael', + lastName: 'Johnson', + sex: 'M', + email: 'm.johnson@gmail.com', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '5', + firstName: 'Emily', + lastName: 'Brown', + sex: 'F', + email: 'e.brown@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '6', + firstName: 'David', + lastName: 'Wilson', + sex: 'M', + email: 'd.wilson@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '7', + firstName: 'Olivia', + lastName: 'Johnson', + sex: 'F', + email: 'o.johnson@gmail.com', + role: Role.Member, + is_board: true, + is_expert: false, + }, + { + oauthId: '8', + firstName: 'James', + lastName: 'Smith', + sex: 'M', + email: 'j.smith@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '9', + firstName: 'Sophia', + lastName: 'Miller', + sex: 'F', + email: 's.miller@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '10', + firstName: 'Benjamin', + lastName: 'Davis', + sex: 'M', + email: 'b.davis@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '11', + firstName: 'Ava', + lastName: 'Wilson', + sex: 'female', + email: 'a.wilson@gmail.com', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '12', + firstName: 'William', + lastName: 'Anderson', + sex: 'M', + email: 'w.anderson@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '13', + firstName: 'Mia', + lastName: 'Thomas', + sex: 'F', + email: 'm.thomas@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '14', + firstName: 'Alexander', + lastName: 'Taylor', + sex: 'M', + email: 'a.taylor@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '15', + firstName: 'Charlotte', + lastName: 'Clark', + sex: 'F', + email: 'c.clark@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '16', + firstName: 'Daniel', + lastName: 'Moore', + sex: 'M', + email: 'd.moore@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '17', + firstName: 'Amelia', + lastName: 'Walker', + sex: 'F', + email: 'a.walker@gmail.com', + role: Role.Clerk, + is_board: false, + is_expert: false, + }, { + oauthId: '18', + firstName: 'Matthew', + lastName: 'Lewis', + sex: 'M', + email: 'm.lewis@gmail.com', + role: Role.Admin, + is_board: false, + is_expert: true, + }, + { + oauthId: '19', + firstName: 'Ella', + lastName: 'Harris', + sex: 'F', + email: 'e.harris@gmail.com', + role: Role.Supervisor, + is_board: false, + is_expert: true, + }, + { + oauthId: '20', + firstName: 'Joseph', + lastName: 'King', + sex: 'M', + email: 'j.king@gmail.com', + role: Role.Supervisor, + is_board: true, + is_expert: false, + }, + ]; + + mockTimeSlots = [ + { + id: 51, start: new Date('2024-05-16 10:00:00'), end: new Date('2024-05-16 11:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 52, start: new Date('2024-05-16 11:00:00'), end: new Date('2024-05-16 12:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 53, start: new Date('2024-05-16 15:00:00'), end: new Date('2024-05-16 16:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 54, start: new Date('2024-05-16 16:00:00'), end: new Date('2024-05-16 17:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 55, start: new Date('2024-05-16 17:00:00'), end: new Date('2024-05-16 18:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 56, start: new Date('2024-05-16 18:00:00'), end: new Date('2024-05-16 19:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 57, start: new Date('2024-05-17 10:00:00'), end: new Date('2024-05-17 11:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 58, start: new Date('2024-05-17 11:00:00'), end: new Date('2024-05-17 12:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 59, start: new Date('2024-05-17 15:00:00'), end: new Date('2024-05-17 16:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 60, start: new Date('2024-05-17 16:00:00'), end: new Date('2024-05-17 17:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 61, start: new Date('2024-05-17 17:00:00'), end: new Date('2024-05-17 18:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 62, start: new Date('2024-05-17 18:00:00'), end: new Date('2024-05-17 19:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 63, start: new Date('2024-05-18 10:00:00'), end: new Date('2024-05-18 11:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 64, start: new Date('2024-05-18 11:00:00'), end: new Date('2024-05-18 12:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 65, start: new Date('2024-05-18 15:00:00'), end: new Date('2024-05-18 16:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 66, start: new Date('2024-05-18 16:00:00'), end: new Date('2024-05-18 17:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 67, start: new Date('2024-05-18 17:00:00'), end: new Date('2024-05-18 18:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 68, start: new Date('2024-05-18 18:00:00'), end: new Date('2024-05-18 19:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 69, start: new Date('2024-05-19 10:00:00'), end: new Date('2024-05-19 11:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 70, start: new Date('2024-05-19 11:00:00'), end: new Date('2024-05-19 12:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 71, start: new Date('2024-05-19 15:00:00'), end: new Date('2024-05-19 16:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 72, start: new Date('2024-05-19 16:00:00'), end: new Date('2024-05-19 17:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 73, start: new Date('2024-05-19 17:00:00'), end: new Date('2024-05-19 18:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 74, start: new Date('2024-05-19 18:00:00'), end: new Date('2024-05-19 19:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 75, start: new Date('2024-05-20 10:00:00'), end: new Date('2024-05-20 11:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 76, start: new Date('2024-05-20 11:00:00'), end: new Date('2024-05-20 12:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 77, start: new Date('2024-05-20 15:00:00'), end: new Date('2024-05-20 16:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 78, start: new Date('2024-05-20 16:00:00'), end: new Date('2024-05-20 17:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 79, start: new Date('2024-05-20 17:00:00'), end: new Date('2024-05-20 18:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, { + id: 80, start: new Date('2024-05-20 18:00:00'), end: new Date('2024-05-20 19:00:00'), - recruitmentSession: 3, - } as TimeSlot, + recruitmentSession: mockRecruitmentSessions[2], + }, ]; mockAvailability = [ @@ -675,383 +707,130 @@ describe('TimeslotsController', () => { } as unknown as Availability, { id: 35, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 77, - userOauthId: '17', - } as unknown as Availability, - { - id: 36, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 76, - userOauthId: '19', - } as unknown as Availability, - { - id: 37, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 75, - userOauthId: '16', - } as unknown as Availability, - { - id: 38, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 74, - userOauthId: '15', - } as unknown as Availability, - { - id: 39, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 73, - userOauthId: '14', - } as unknown as Availability, - { - id: 40, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 72, - userOauthId: '13', - } as unknown as Availability, - { - id: 41, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 71, - userOauthId: '12', - } as unknown as Availability, - { - id: 42, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 70, - userOauthId: '11', - } as unknown as Availability, - { - id: 43, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 69, - userOauthId: '10', - } as unknown as Availability, - { - id: 44, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 68, - userOauthId: '9', - } as unknown as Availability, - { - id: 45, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 67, - userOauthId: '8', - } as unknown as Availability, - { - id: 46, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 66, - userOauthId: '7', - } as unknown as Availability, - { - id: 47, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 65, - userOauthId: '6', - } as unknown as Availability, - { - id: 48, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 64, - userOauthId: '5', - } as unknown as Availability, - { - id: 49, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 63, - userOauthId: '8', - } as unknown as Availability, - { - id: 50, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 62, - userOauthId: '14', - } as unknown as Availability, - { - id: 51, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 61, - userOauthId: '4', - } as unknown as Availability, - { - id: 52, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 60, - userOauthId: '13', - } as unknown as Availability, - ]; - - mockRecruitmentSessions = [ - { - id: 1, - state: RecruitmentSessionState.Concluded, - slotDuration: 1, - lastModified: new Date('2024-04-10'), - createdAt: new Date('2024-04-04'), - days: [ - new Date('2024-04-05'), - new Date('2024-04-06'), - new Date('2024-04-07'), - new Date('2024-04-08'), - new Date('2024-04-09'), - ], - interviewStart: new Date('2024-04-05'), - interviewEnd: new Date('2024-04-10'), - }, - { - id: 2, - state: RecruitmentSessionState.Concluded, - slotDuration: 1, - lastModified: new Date('2024-05-10'), - createdAt: new Date('2024-05-04'), - days: [ - new Date('2024-05-05'), - new Date('2024-05-06'), - new Date('2024-05-07'), - new Date('2024-05-08'), - ], - interviewStart: new Date('2024-05-05'), - interviewEnd: new Date('2024-05-09'), - }, - { - id: 3, - state: RecruitmentSessionState.Active, - slotDuration: 1, - lastModified: new Date('2024-05-14'), - createdAt: new Date('2024-05-14'), - days: [ - new Date('2024-05-16'), - new Date('2024-05-17'), - new Date('2024-05-18'), - new Date('2024-05-19'), - new Date('2024-05-20'), - ], - interviewStart: new Date('2024-05-16'), - interviewEnd: new Date('2024-05-21'), - }, - ]; - - mockUsers = [ - { - oauthId: '1', - firstName: 'Pasquale', - lastName: 'Bianco', - sex: 'M', - email: 'p.bianco@gmail.com', - role: Role.Member, - is_board: true, - is_expert: true, - }, - { - oauthId: '2', - firstName: 'John', - lastName: 'Doe', - sex: 'M', - email: 'j.doe@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, - { - oauthId: '3', - firstName: 'Jane', - lastName: 'Smith', - sex: 'F', - email: 'j.smith@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '4', - firstName: 'Michael', - lastName: 'Johnson', - sex: 'M', - email: 'm.johnson@gmail.com', - role: Role.Member, - is_board: true, - is_expert: true, - }, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 77, + userOauthId: '17', + } as unknown as Availability, { - oauthId: '5', - firstName: 'Emily', - lastName: 'Brown', - sex: 'F', - email: 'e.brown@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, + id: 36, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 76, + userOauthId: '19', + } as unknown as Availability, { - oauthId: '6', - firstName: 'David', - lastName: 'Wilson', - sex: 'M', - email: 'd.wilson@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 37, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '16', + } as unknown as Availability, { - oauthId: '7', - firstName: 'Olivia', - lastName: 'Johnson', - sex: 'F', - email: 'o.johnson@gmail.com', - role: Role.Member, - is_board: true, - is_expert: false, - }, + id: 38, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '15', + } as unknown as Availability, { - oauthId: '8', - firstName: 'James', - lastName: 'Smith', - sex: 'M', - email: 'j.smith@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 39, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 73, + userOauthId: '14', + } as unknown as Availability, { - oauthId: '9', - firstName: 'Sophia', - lastName: 'Miller', - sex: 'F', - email: 's.miller@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, + id: 40, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '13', + } as unknown as Availability, { - oauthId: '10', - firstName: 'Benjamin', - lastName: 'Davis', - sex: 'M', - email: 'b.davis@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 41, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '12', + } as unknown as Availability, { - oauthId: '11', - firstName: 'Ava', - lastName: 'Wilson', - sex: 'female', - email: 'a.wilson@gmail.com', - role: Role.Member, - is_board: true, - is_expert: true, - }, + id: 42, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 70, + userOauthId: '11', + } as unknown as Availability, { - oauthId: '12', - firstName: 'William', - lastName: 'Anderson', - sex: 'M', - email: 'w.anderson@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 43, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 69, + userOauthId: '10', + } as unknown as Availability, { - oauthId: '13', - firstName: 'Mia', - lastName: 'Thomas', - sex: 'F', - email: 'm.thomas@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 44, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 68, + userOauthId: '9', + } as unknown as Availability, { - oauthId: '14', - firstName: 'Alexander', - lastName: 'Taylor', - sex: 'M', - email: 'a.taylor@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, + id: 45, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 67, + userOauthId: '8', + } as unknown as Availability, { - oauthId: '15', - firstName: 'Charlotte', - lastName: 'Clark', - sex: 'F', - email: 'c.clark@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 46, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 66, + userOauthId: '7', + } as unknown as Availability, { - oauthId: '16', - firstName: 'Daniel', - lastName: 'Moore', - sex: 'M', - email: 'd.moore@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, + id: 47, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 65, + userOauthId: '6', + } as unknown as Availability, { - oauthId: '17', - firstName: 'Amelia', - lastName: 'Walker', - sex: 'F', - email: 'a.walker@gmail.com', - role: Role.Clerk, - is_board: false, - is_expert: false, - }, + id: 48, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '5', + } as unknown as Availability, { - oauthId: '18', - firstName: 'Matthew', - lastName: 'Lewis', - sex: 'M', - email: 'm.lewis@gmail.com', - role: Role.Admin, - is_board: false, - is_expert: true, - }, + id: 49, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 63, + userOauthId: '8', + } as unknown as Availability, { - oauthId: '19', - firstName: 'Ella', - lastName: 'Harris', - sex: 'F', - email: 'e.harris@gmail.com', - role: Role.Supervisor, - is_board: false, - is_expert: true, - }, + id: 50, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 62, + userOauthId: '14', + } as unknown as Availability, { - oauthId: '20', - firstName: 'Joseph', - lastName: 'King', - sex: 'M', - email: 'j.king@gmail.com', - role: Role.Supervisor, - is_board: true, - is_expert: false, - }, + id: 51, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 61, + userOauthId: '4', + } as unknown as Availability, + { + id: 52, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 60, + userOauthId: '13', + } as unknown as Availability, ]; }); @@ -1071,16 +850,13 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { - for (const rs of mockRecruitmentSessions) { - await recruitmentSessionService.createRecruitmentSession(rs); - } - - for (const u of mockUsers) { - await usersService.create(u); - } - for (const ts of mockTimeSlots) { - await timeSlotsService.createTimeSlot(ts); + const timeSlot = { + ...ts, + recruitmentSession: mockRecruitmentSessions[2], + availabilities: [], + }; + await timeSlotsService.createTimeSlot(timeSlot); } for (const a of mockAvailability) { diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index cb95d62..e47d835 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -200,7 +200,7 @@ describe('TimeSlotsService', () => { it('should correctly call all functions provided for database query', async () => { // Mock the query builder and its methods const mockQueryBuilder = { - leftJoinAndSelect: jest.fn().mockReturnThis(), + innerJoinAndSelect: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), getMany: jest.fn().mockResolvedValue([]), @@ -217,15 +217,15 @@ describe('TimeSlotsService', () => { const result = await timeSlotService.findAvailableTimeSlots(); // Assert that the query builder methods were called correctly - expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith( + expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( 'TimeSlot.availabilities', 'availability', ); - expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith( + expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( 'TimeSlot.recruitmentSession', 'recruitmentSession', ); - expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith( + expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( 'availability.user', 'user', ); diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 0e34c88..3c2562e 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -79,7 +79,7 @@ export class TimeSlotsService { * @param timeSlot - Time slot to create * @returns {Promise} - Created time slot */ - async createTimeSlot(timeSlot: CreateTimeSlotDto): Promise { + async createTimeSlot(timeSlot: TimeSlot): Promise { return await this.timeSlotRepository.save(timeSlot); } @@ -157,9 +157,9 @@ export class TimeSlotsService { async findAvailableTimeSlots(): Promise { const queryBuilder = this.timeSlotRepository.createQueryBuilder('TimeSlot'); queryBuilder - .leftJoinAndSelect('TimeSlot.availabilities', 'availability') - .leftJoinAndSelect('TimeSlot.recruitmentSession', 'recruitmentSession') - .leftJoinAndSelect('availability.user', 'user') + .innerJoinAndSelect('TimeSlot.availabilities', 'availability') + .innerJoinAndSelect('TimeSlot.recruitmentSession', 'recruitmentSession') + .innerJoinAndSelect('availability.user', 'user') // only active recruitment sessions (the current one) .where('recruitmentSession.state = :recruitmentSessionState', { diff --git a/shared/src/timeslot.ts b/shared/src/timeslot.ts index 3665746..aff04cc 100644 --- a/shared/src/timeslot.ts +++ b/shared/src/timeslot.ts @@ -1,14 +1,12 @@ -import { Availability } from "availability"; import { Action, ApplyAbilities } from "./abilities"; import { Role } from "./person"; -import { RecruitmentSession } from "recruitment-session"; +import { RecruitmentSession } from "./recruitment-session"; export interface TimeSlot { id: number; start: Date; end: Date; recruitmentSession: RecruitmentSession; - availabilities: Availability[]; } /* Abilities */ From 23ffd9efee82f71b50f651f320473c4b542dd151 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:00:14 +0200 Subject: [PATCH 24/57] fix: 8 --- api/src/timeslots/timeslots.e2e-spec.ts | 125 +++++++++++++----------- 1 file changed, 70 insertions(+), 55 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index a47ed8e..843a4e5 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -25,7 +25,15 @@ describe('TimeslotsController', () => { let mockUsers: Person[]; let mockTimeSlots: TimeSlot[]; let mockRecruitmentSessions: RecruitmentSession[]; - let mockAvailability: Availability[]; + + class mockAv { + id: number; + state: AvailabilityState; + lastModified: Date; + timeSlot: number; + userOauthId: string; + } + let mockAvailability: mockAv[]; beforeAll(async () => { newMemberToken = await getAccessToken('newMember'); @@ -473,364 +481,364 @@ describe('TimeslotsController', () => { lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 51, userOauthId: '5', - } as unknown as Availability, + } , { id: 2, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '7', - } as unknown as Availability, + } , { id: 3, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 55, userOauthId: '12', - } as unknown as Availability, + } , { id: 4, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '19', - } as unknown as Availability, + } , { id: 5, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '3', - } as unknown as Availability, + } , { id: 6, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '15', - } as unknown as Availability, + } , { id: 7, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 58, userOauthId: '11', - } as unknown as Availability, + } , { id: 8, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '8', - } as unknown as Availability, + } , { id: 9, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5', - } as unknown as Availability, + } , { id: 10, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '17', - } as unknown as Availability, + } , { id: 11, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '2', - } as unknown as Availability, + } , { id: 12, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '9', - } as unknown as Availability, + } , { id: 13, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '14', - } as unknown as Availability, + } , { id: 14, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '6', - } as unknown as Availability, + } , { id: 15, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '13', - } as unknown as Availability, + } , { id: 16, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '10', - } as unknown as Availability, + } , { id: 17, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '4', - } as unknown as Availability, + } , { id: 18, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '16', - } as unknown as Availability, + } , { id: 19, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 59, userOauthId: '18', - } as unknown as Availability, + } { id: 20, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '1', - } as unknown as Availability, + } , { id: 21, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '20', - } as unknown as Availability, + } , { id: 22, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '19', - } as unknown as Availability, + } , { id: 23, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '7', - } as unknown as Availability, + } , { id: 24, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '16', - } as unknown as Availability, + } , { id: 25, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '15', - } as unknown as Availability, + } , { id: 26, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '11', - } as unknown as Availability, + } , { id: 27, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '14', - } as unknown as Availability, + } , { id: 28, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '9', - } as unknown as Availability, + } , { id: 29, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '8', - } as unknown as Availability, + } , { id: 30, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '10', - } as unknown as Availability, + } , { id: 31, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '13', - } as unknown as Availability, + } , { id: 32, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 80, userOauthId: '12', - } as unknown as Availability, + } , { id: 33, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '20', - } as unknown as Availability, + } , { id: 34, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '18', - } as unknown as Availability, + } , { id: 35, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '17', - } as unknown as Availability, + } , { id: 36, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '19', - } as unknown as Availability, + } , { id: 37, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '16', - } as unknown as Availability, + } , { id: 38, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '15', - } as unknown as Availability, + } , { id: 39, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '14', - } as unknown as Availability, + } , { id: 40, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '13', - } as unknown as Availability, + } , { id: 41, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '12', - } as unknown as Availability, + } , { id: 42, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '11', - } as unknown as Availability, + } , { id: 43, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '10', - } as unknown as Availability, + } , { id: 44, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '9', - } as unknown as Availability, + } , { id: 45, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 67, userOauthId: '8', - } as unknown as Availability, + } , { id: 46, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '7', - } as unknown as Availability, + } , { id: 47, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 65, userOauthId: '6', - } as unknown as Availability, + } , { id: 48, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5', - } as unknown as Availability, + } , { id: 49, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 63, userOauthId: '8', - } as unknown as Availability, + } , { id: 50, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '14', - } as unknown as Availability, + } , { id: 51, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '4', - } as unknown as Availability, + } , { id: 52, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 60, userOauthId: '13', - } as unknown as Availability, + } , ]; }); @@ -854,13 +862,20 @@ describe('TimeslotsController', () => { const timeSlot = { ...ts, recruitmentSession: mockRecruitmentSessions[2], - availabilities: [], + availabilities: [] as Availability[], }; await timeSlotsService.createTimeSlot(timeSlot); } for (const a of mockAvailability) { - await availabilityService.createAvailability(a); + const availability = { + id: a.id, + state: a.state, + lastModified: a.lastModified, + timeSlot: timeSlotsService.findById(a.timeSlot), + user: mockUsers.find((u) => u.oauthId === a.userOauthId), + } as unknown as Availability; + await availabilityService.createAvailability(availability); } }); From 395a3211a6da2688fd8883e53e4a154b518ec199 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:00:46 +0200 Subject: [PATCH 25/57] fix: 9 --- api/src/timeslots/timeslots.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 843a4e5..f431eda 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -607,7 +607,7 @@ describe('TimeslotsController', () => { lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 59, userOauthId: '18', - } + } , { id: 20, state: AvailabilityState.Free, From dd3f63ab83262be9931424559d6b211e4a39a6e6 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:02:00 +0200 Subject: [PATCH 26/57] format --- api/src/timeslots/timeslots.e2e-spec.ts | 104 ++++++++++++------------ 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index f431eda..409ee93 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -481,364 +481,364 @@ describe('TimeslotsController', () => { lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 51, userOauthId: '5', - } , + }, { id: 2, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '7', - } , + }, { id: 3, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 55, userOauthId: '12', - } , + }, { id: 4, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '19', - } , + }, { id: 5, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '3', - } , + }, { id: 6, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '15', - } , + }, { id: 7, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 58, userOauthId: '11', - } , + }, { id: 8, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '8', - } , + }, { id: 9, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5', - } , + }, { id: 10, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 53, userOauthId: '17', - } , + }, { id: 11, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '2', - } , + }, { id: 12, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '9', - } , + }, { id: 13, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '14', - } , + }, { id: 14, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '6', - } , + }, { id: 15, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '13', - } , + }, { id: 16, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '10', - } , + }, { id: 17, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '4', - } , + }, { id: 18, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '16', - } , + }, { id: 19, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 59, userOauthId: '18', - } , + }, { id: 20, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 57, userOauthId: '1', - } , + }, { id: 21, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '20', - } , + }, { id: 22, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '19', - } , + }, { id: 23, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '7', - } , + }, { id: 24, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '16', - } , + }, { id: 25, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '15', - } , + }, { id: 26, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '11', - } , + }, { id: 27, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '14', - } , + }, { id: 28, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '9', - } , + }, { id: 29, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '8', - } , + }, { id: 30, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '10', - } , + }, { id: 31, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '13', - } , + }, { id: 32, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 80, userOauthId: '12', - } , + }, { id: 33, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 79, userOauthId: '20', - } , + }, { id: 34, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 78, userOauthId: '18', - } , + }, { id: 35, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 77, userOauthId: '17', - } , + }, { id: 36, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 76, userOauthId: '19', - } , + }, { id: 37, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 75, userOauthId: '16', - } , + }, { id: 38, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 74, userOauthId: '15', - } , + }, { id: 39, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 73, userOauthId: '14', - } , + }, { id: 40, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 72, userOauthId: '13', - } , + }, { id: 41, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 71, userOauthId: '12', - } , + }, { id: 42, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 70, userOauthId: '11', - } , + }, { id: 43, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 69, userOauthId: '10', - } , + }, { id: 44, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 68, userOauthId: '9', - } , + }, { id: 45, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 67, userOauthId: '8', - } , + }, { id: 46, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 66, userOauthId: '7', - } , + }, { id: 47, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 65, userOauthId: '6', - } , + }, { id: 48, state: AvailabilityState.Interviewing, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 64, userOauthId: '5', - } , + }, { id: 49, state: AvailabilityState.Recovering, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 63, userOauthId: '8', - } , + }, { id: 50, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 62, userOauthId: '14', - } , + }, { id: 51, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 61, userOauthId: '4', - } , + }, { id: 52, state: AvailabilityState.Free, lastModified: new Date('2024-05-14 00:00:00'), timeSlot: 60, userOauthId: '13', - } , + }, ]; }); From 0c92b3d5f9e44a02c25f7e8d523b9865c2d4a6fe Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:33:07 +0200 Subject: [PATCH 27/57] fix: 10 --- api/src/timeslots/timeslots.e2e-spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 409ee93..90035f7 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -858,6 +858,14 @@ describe('TimeslotsController', () => { describe(' GET /timeslots', () => { beforeEach(async () => { + for (const user of mockUsers) { + await usersService.create(user); + } + + for (const rs of mockRecruitmentSessions) { + await recruitmentSessionService.createRecruitmentSession(rs); + } + for (const ts of mockTimeSlots) { const timeSlot = { ...ts, From aab3bc97dbfe4f438d0a1c7603e4a3c8730f9d66 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:37:08 +0200 Subject: [PATCH 28/57] fix: 11 --- api/src/timeslots/timeslots.e2e-spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 90035f7..6d2fb9e 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -887,6 +887,11 @@ describe('TimeslotsController', () => { } }); + it('there should be users in the db', async () => { + const allUsers = await usersService.findAll(); + expect(allUsers).toHaveLength(mockUsers.length); + }); + it('should return all available timeslots', async () => { const expected = [ { From b82b00d1f731e4f3f0e4bf1ca16ab48c847da432 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:45:05 +0200 Subject: [PATCH 29/57] debubg: tests --- api/src/timeslots/timeslots.e2e-spec.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 6d2fb9e..519df36 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -892,6 +892,30 @@ describe('TimeslotsController', () => { expect(allUsers).toHaveLength(mockUsers.length); }); + it('there should be recruitment sessions in the db', async () => { + const allRecruitmentSessions = + await recruitmentSessionService.findAllRecruitmentSessions(); + expect(allRecruitmentSessions).toHaveLength(mockRecruitmentSessions.length); + }); + + it('there should be timeslots in the db', async () => { + const allTimeSlots = await timeSlotsService.listTimeSlots(); + expect(allTimeSlots).toHaveLength(mockTimeSlots.length); + }); + + it('there should be availabilities in the db', async () => { + const allAvailabilities = await availabilityService.listAvailabilities(); + expect(allAvailabilities).toHaveLength(mockAvailability.length); + }); + + it("DEBUG: check availabilities for timeslot having id 71", async () => { + const allAvailabilities = await availabilityService.listAvailabilities() + const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71) + const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73) + expect(av71).toHaveLength(4); + expect(av73).toHaveLength(2); + }); + it('should return all available timeslots', async () => { const expected = [ { From dff77a4d9c701f8ad32138024b8ef4e08d7a1f02 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:45:21 +0200 Subject: [PATCH 30/57] format --- api/src/timeslots/timeslots.e2e-spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 519df36..1ede84b 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -895,7 +895,9 @@ describe('TimeslotsController', () => { it('there should be recruitment sessions in the db', async () => { const allRecruitmentSessions = await recruitmentSessionService.findAllRecruitmentSessions(); - expect(allRecruitmentSessions).toHaveLength(mockRecruitmentSessions.length); + expect(allRecruitmentSessions).toHaveLength( + mockRecruitmentSessions.length, + ); }); it('there should be timeslots in the db', async () => { @@ -908,10 +910,10 @@ describe('TimeslotsController', () => { expect(allAvailabilities).toHaveLength(mockAvailability.length); }); - it("DEBUG: check availabilities for timeslot having id 71", async () => { - const allAvailabilities = await availabilityService.listAvailabilities() - const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71) - const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73) + it('DEBUG: check availabilities for timeslot having id 71', async () => { + const allAvailabilities = await availabilityService.listAvailabilities(); + const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); + const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73); expect(av71).toHaveLength(4); expect(av73).toHaveLength(2); }); From 542e5dc51ca423206a62581067b879760eb16cf7 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:48:55 +0200 Subject: [PATCH 31/57] debubg: tests 2 --- api/src/timeslots/timeslots.e2e-spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 1ede84b..4d94600 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -910,10 +910,10 @@ describe('TimeslotsController', () => { expect(allAvailabilities).toHaveLength(mockAvailability.length); }); - it('DEBUG: check availabilities for timeslot having id 71', async () => { + it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { const allAvailabilities = await availabilityService.listAvailabilities(); - const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); - const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73); + const av71 = allAvailabilities.filter((a) => a.timeSlotId === 71); + const av73 = allAvailabilities.filter((a) => a.timeSlotId === 73); expect(av71).toHaveLength(4); expect(av73).toHaveLength(2); }); From 527c65c6584b742dba51604293534880995b411d Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:57:30 +0200 Subject: [PATCH 32/57] debug: test 3 --- api/src/mocks/requests.http | 4 ++++ api/src/timeslots/timeslots.e2e-spec.ts | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/api/src/mocks/requests.http b/api/src/mocks/requests.http index 74e9e9e..56bdf35 100644 --- a/api/src/mocks/requests.http +++ b/api/src/mocks/requests.http @@ -1,2 +1,6 @@ GET http://localhost:3000/v1/timeslots/ +Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc4aFdBRTB0U00tX3JJVUR0WElNMyJ9.eyJlbWFpbCI6InBhc3F1YWxlLmJpYW5jb0Boa25wb2xpdG8ub3JnIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYyMjgyMTU0MzYxNTk5ODcwNTEiLCJhdWQiOlsiaHR0cDovL2hrcmVjcnVpdG1lbnQub3JnIiwiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzE2MTEzNTk3LCJleHAiOjE3MTYxOTk5OTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJaekNWd2R2eUJOUWZKc3R1ZUJPcVh3TW1jazZCa0d4NiJ9.PyyW67LJIWc9NbMibzvzfxlY0Pe1YTsZVsV2--ckBQ5hfYsCKqCUi6aIjmiliz_uZO5qdPAsZ7FYNOtOI8fGhPt9QfKSqA166D-CZWnpuwE4cM_EjbpCoHRRQ49r2Ycv_ZnzVXrO6zCyEVZYgx-USGiuBnRcKbIPDJCS4Tc9bcmklcUdq4ujAPtSDdmid9jcVN3DM5eLtXAQuYIUho3G3NRVuKlUnoNIsK16yrKtx5kgOHKuUP6dFYExpp2ViB4oZV-v_linCsXZEE5x-7GBMntJAGtenCs-1uihB-OGLYJY_6fPsRG2rgw5Fl8Ax0Ioc8LeXkwlJ2L-MO1ms-r8uQ + +### +GET http://localhost:3000/v1/availabilities/ Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc4aFdBRTB0U00tX3JJVUR0WElNMyJ9.eyJlbWFpbCI6InBhc3F1YWxlLmJpYW5jb0Boa25wb2xpdG8ub3JnIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYyMjgyMTU0MzYxNTk5ODcwNTEiLCJhdWQiOlsiaHR0cDovL2hrcmVjcnVpdG1lbnQub3JnIiwiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzE2MTEzNTk3LCJleHAiOjE3MTYxOTk5OTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJaekNWd2R2eUJOUWZKc3R1ZUJPcVh3TW1jazZCa0d4NiJ9.PyyW67LJIWc9NbMibzvzfxlY0Pe1YTsZVsV2--ckBQ5hfYsCKqCUi6aIjmiliz_uZO5qdPAsZ7FYNOtOI8fGhPt9QfKSqA166D-CZWnpuwE4cM_EjbpCoHRRQ49r2Ycv_ZnzVXrO6zCyEVZYgx-USGiuBnRcKbIPDJCS4Tc9bcmklcUdq4ujAPtSDdmid9jcVN3DM5eLtXAQuYIUho3G3NRVuKlUnoNIsK16yrKtx5kgOHKuUP6dFYExpp2ViB4oZV-v_linCsXZEE5x-7GBMntJAGtenCs-1uihB-OGLYJY_6fPsRG2rgw5Fl8Ax0Ioc8LeXkwlJ2L-MO1ms-r8uQ \ No newline at end of file diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 4d94600..d223cfc 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -910,10 +910,22 @@ describe('TimeslotsController', () => { expect(allAvailabilities).toHaveLength(mockAvailability.length); }); + it('DEBUG: check format of an abailability', async () => { + const availability = await availabilityService.findById(1); + expect(availability).toBeDefined(); + expect(availability).toHaveProperty('id', 1); + expect(availability).toHaveProperty('state'); + expect(availability).toHaveProperty('lastModified'); + expect(availability).toHaveProperty('timeSlot'); + expect(availability).toHaveProperty('user'); + expect(availability).toEqual(mockAvailability[0]); + + }); + it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { const allAvailabilities = await availabilityService.listAvailabilities(); - const av71 = allAvailabilities.filter((a) => a.timeSlotId === 71); - const av73 = allAvailabilities.filter((a) => a.timeSlotId === 73); + const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); + const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73); expect(av71).toHaveLength(4); expect(av73).toHaveLength(2); }); From da50afecaf15a93d27f82cb4434151e658de2821 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 21:59:03 +0200 Subject: [PATCH 33/57] format --- api/src/timeslots/timeslots.e2e-spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index d223cfc..7b8abfa 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -919,7 +919,6 @@ describe('TimeslotsController', () => { expect(availability).toHaveProperty('timeSlot'); expect(availability).toHaveProperty('user'); expect(availability).toEqual(mockAvailability[0]); - }); it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { From 37729bbe4c38362574ac4591a9ca006a3480fbf9 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 22:08:12 +0200 Subject: [PATCH 34/57] debug: test 4 --- api/src/mocks/db.sql | 1 - api/src/mocks/requests.http | 4 ---- api/src/timeslots/timeslots.e2e-spec.ts | 13 ++++++++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/src/mocks/db.sql b/api/src/mocks/db.sql index c1cc5f2..cd4bb63 100644 --- a/api/src/mocks/db.sql +++ b/api/src/mocks/db.sql @@ -177,4 +177,3 @@ VALUES (50, 'free' ,'2024-05-14 00:00:00', 62, '14'), (51, 'free' ,'2024-05-14 00:00:00', 61, '4' ), (52, 'free' ,'2024-05-14 00:00:00', 60, '13'); - diff --git a/api/src/mocks/requests.http b/api/src/mocks/requests.http index 56bdf35..c0f0e99 100644 --- a/api/src/mocks/requests.http +++ b/api/src/mocks/requests.http @@ -1,6 +1,2 @@ GET http://localhost:3000/v1/timeslots/ Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc4aFdBRTB0U00tX3JJVUR0WElNMyJ9.eyJlbWFpbCI6InBhc3F1YWxlLmJpYW5jb0Boa25wb2xpdG8ub3JnIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYyMjgyMTU0MzYxNTk5ODcwNTEiLCJhdWQiOlsiaHR0cDovL2hrcmVjcnVpdG1lbnQub3JnIiwiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzE2MTEzNTk3LCJleHAiOjE3MTYxOTk5OTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJaekNWd2R2eUJOUWZKc3R1ZUJPcVh3TW1jazZCa0d4NiJ9.PyyW67LJIWc9NbMibzvzfxlY0Pe1YTsZVsV2--ckBQ5hfYsCKqCUi6aIjmiliz_uZO5qdPAsZ7FYNOtOI8fGhPt9QfKSqA166D-CZWnpuwE4cM_EjbpCoHRRQ49r2Ycv_ZnzVXrO6zCyEVZYgx-USGiuBnRcKbIPDJCS4Tc9bcmklcUdq4ujAPtSDdmid9jcVN3DM5eLtXAQuYIUho3G3NRVuKlUnoNIsK16yrKtx5kgOHKuUP6dFYExpp2ViB4oZV-v_linCsXZEE5x-7GBMntJAGtenCs-1uihB-OGLYJY_6fPsRG2rgw5Fl8Ax0Ioc8LeXkwlJ2L-MO1ms-r8uQ - -### -GET http://localhost:3000/v1/availabilities/ -Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc4aFdBRTB0U00tX3JJVUR0WElNMyJ9.eyJlbWFpbCI6InBhc3F1YWxlLmJpYW5jb0Boa25wb2xpdG8ub3JnIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYyMjgyMTU0MzYxNTk5ODcwNTEiLCJhdWQiOlsiaHR0cDovL2hrcmVjcnVpdG1lbnQub3JnIiwiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzE2MTEzNTk3LCJleHAiOjE3MTYxOTk5OTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJaekNWd2R2eUJOUWZKc3R1ZUJPcVh3TW1jazZCa0d4NiJ9.PyyW67LJIWc9NbMibzvzfxlY0Pe1YTsZVsV2--ckBQ5hfYsCKqCUi6aIjmiliz_uZO5qdPAsZ7FYNOtOI8fGhPt9QfKSqA166D-CZWnpuwE4cM_EjbpCoHRRQ49r2Ycv_ZnzVXrO6zCyEVZYgx-USGiuBnRcKbIPDJCS4Tc9bcmklcUdq4ujAPtSDdmid9jcVN3DM5eLtXAQuYIUho3G3NRVuKlUnoNIsK16yrKtx5kgOHKuUP6dFYExpp2ViB4oZV-v_linCsXZEE5x-7GBMntJAGtenCs-1uihB-OGLYJY_6fPsRG2rgw5Fl8Ax0Ioc8LeXkwlJ2L-MO1ms-r8uQ \ No newline at end of file diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 7b8abfa..fc8f76a 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -910,7 +910,7 @@ describe('TimeslotsController', () => { expect(allAvailabilities).toHaveLength(mockAvailability.length); }); - it('DEBUG: check format of an abailability', async () => { + it('DEBUG: check format of an Availabilities table in db', async () => { const availability = await availabilityService.findById(1); expect(availability).toBeDefined(); expect(availability).toHaveProperty('id', 1); @@ -921,6 +921,17 @@ describe('TimeslotsController', () => { expect(availability).toEqual(mockAvailability[0]); }); + it('DEBUG: check format of a TimeSlots table in db', async () => { + const timeSlot = await timeSlotsService.findById(51); + expect(timeSlot).toBeDefined(); + expect(timeSlot).toHaveProperty('id', 51); + expect(timeSlot).toHaveProperty('start'); + expect(timeSlot).toHaveProperty('end'); + expect(timeSlot).toHaveProperty('recruitmentSession'); + expect(timeSlot).toHaveProperty('availabilities'); + expect(timeSlot).toEqual(mockTimeSlots[0]); + }); + it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { const allAvailabilities = await availabilityService.listAvailabilities(); const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); From 04656b02db4d112e6b9b8f2ec462a464b5a14e3f Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 22:19:56 +0200 Subject: [PATCH 35/57] debug: test 5 --- api/src/timeslots/timeslots.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 3c2562e..3ade8fb 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -185,6 +185,7 @@ export class TimeSlotsService { ); const allMatches = await queryBuilder.getMany(); + return allMatches; let goodTimeSlots: TimeSlot[] = []; allMatches.forEach((timeSlot) => { From 0596a937a813225f24fc64c093de457529149eea Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 22:28:37 +0200 Subject: [PATCH 36/57] debug: test 6 --- api/src/timeslots/timeslots.service.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 3ade8fb..f623b9c 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, LessThan, MoreThan, QueryRunner } from 'typeorm'; +import { Repository, LessThan, MoreThan, QueryRunner, Not, In } from 'typeorm'; import { TimeSlot } from './timeslot.entity'; import { RecruitmentSession, @@ -185,6 +185,21 @@ export class TimeSlotsService { ); const allMatches = await queryBuilder.getMany(); + return this.timeSlotRepository.find({ + relations: [ + 'availabilities', + 'availabilities.user', + 'recruitmentSession', + ], + where: { + availabilities: { + state: AvailabilityState.Free, + user: { + role: Not(In([Role.Applicant, Role.None])), + }, + }, + }, + }); return allMatches; let goodTimeSlots: TimeSlot[] = []; From 7351d023e6863c9c92cd521089b12f31643b504d Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 22:34:05 +0200 Subject: [PATCH 37/57] debug: test 7 --- api/src/timeslots/timeslots.service.spec.ts | 108 ++++++++++---------- api/src/timeslots/timeslots.service.ts | 5 +- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index e47d835..d400597 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -196,64 +196,64 @@ describe('TimeSlotsService', () => { }); }); - describe('findAvailableTimeSlots', () => { - it('should correctly call all functions provided for database query', async () => { - // Mock the query builder and its methods - const mockQueryBuilder = { - innerJoinAndSelect: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - andWhere: jest.fn().mockReturnThis(), - getMany: jest.fn().mockResolvedValue([]), - }; + // describe('findAvailableTimeSlots', () => { + // it('should correctly call all functions provided for database query', async () => { + // // Mock the query builder and its methods + // const mockQueryBuilder = { + // innerJoinAndSelect: jest.fn().mockReturnThis(), + // where: jest.fn().mockReturnThis(), + // andWhere: jest.fn().mockReturnThis(), + // getMany: jest.fn().mockResolvedValue([]), + // }; - // Mock the timeSlotRepository and its methods - const mockTimeSlotRepository = { - createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), - }; + // // Mock the timeSlotRepository and its methods + // const mockTimeSlotRepository = { + // createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), + // }; - const timeSlotService = new TimeSlotsService( - mockTimeSlotRepository as any, - ); - const result = await timeSlotService.findAvailableTimeSlots(); + // const timeSlotService = new TimeSlotsService( + // mockTimeSlotRepository as any, + // ); + // const result = await timeSlotService.findAvailableTimeSlots(); - // Assert that the query builder methods were called correctly - expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( - 'TimeSlot.availabilities', - 'availability', - ); - expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( - 'TimeSlot.recruitmentSession', - 'recruitmentSession', - ); - expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( - 'availability.user', - 'user', - ); - expect(mockQueryBuilder.where).toHaveBeenCalledWith( - 'recruitmentSession.state = :recruitmentSessionState', - { - recruitmentSessionState: RecruitmentSessionState.Active, - }, - ); - expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - 'user.role NOT IN (:...roles)', - { - roles: [Role.None, Role.Applicant], - }, - ); - expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - 'availability.state = :availabilityState AND (user.is_board = true OR user.is_expert = true)', - { - availabilityState: AvailabilityState.Free, - }, - ); - expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', - ); + // // Assert that the query builder methods were called correctly + // expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( + // 'TimeSlot.availabilities', + // 'availability', + // ); + // expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( + // 'TimeSlot.recruitmentSession', + // 'recruitmentSession', + // ); + // expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( + // 'availability.user', + // 'user', + // ); + // expect(mockQueryBuilder.where).toHaveBeenCalledWith( + // 'recruitmentSession.state = :recruitmentSessionState', + // { + // recruitmentSessionState: RecruitmentSessionState.Active, + // }, + // ); + // expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + // 'user.role NOT IN (:...roles)', + // { + // roles: [Role.None, Role.Applicant], + // }, + // ); + // expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + // 'availability.state = :availabilityState AND (user.is_board = true OR user.is_expert = true)', + // { + // availabilityState: AvailabilityState.Free, + // }, + // ); + // expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + // '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', + // ); - expect(result).toEqual([]); - }); - }); + // expect(result).toEqual([]); + // }); + // }); }); function testTimeSlotsGeneration( diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index f623b9c..98ad638 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -184,8 +184,8 @@ export class TimeSlotsService { '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', ); - const allMatches = await queryBuilder.getMany(); - return this.timeSlotRepository.find({ + queryBuilder.getMany(); + const allMatches = await this.timeSlotRepository.find({ relations: [ 'availabilities', 'availabilities.user', @@ -200,6 +200,7 @@ export class TimeSlotsService { }, }, }); + return allMatches; let goodTimeSlots: TimeSlot[] = []; From 22522fc384d1be29ba7b2b606d4d674dbfc58726 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 22:43:10 +0200 Subject: [PATCH 38/57] debug: test 8 --- api/src/timeslots/timeslots.e2e-spec.ts | 6 ++---- api/src/timeslots/timeslots.service.ts | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index fc8f76a..b175296 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -959,11 +959,9 @@ describe('TimeslotsController', () => { .expect(200) .expect((res) => { expect(res.body).toBeInstanceOf(Array); + expect(res.body).toHaveLength(2); + expect(res.body).toEqual(expected); }); - - const availableTimeSlots = - await timeSlotsService.findAvailableTimeSlots(); - expect(availableTimeSlots).toEqual(expected); }); }); }); diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 98ad638..852ebd3 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -9,6 +9,7 @@ import { Role, } from '@hkrecruitment/shared'; import { CreateTimeSlotDto } from './create-timeslot.dto'; +import { count } from 'console'; @Injectable() export class TimeSlotsService { @@ -201,8 +202,6 @@ export class TimeSlotsService { }, }); - return allMatches; - let goodTimeSlots: TimeSlot[] = []; allMatches.forEach((timeSlot) => { let boardMembers = 0; From a4a43adccbc7ec8a4f815bd6f6744790819072d9 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 22:49:30 +0200 Subject: [PATCH 39/57] debug: test 9 --- api/src/timeslots/timeslots.e2e-spec.ts | 22 ---------------------- api/src/timeslots/timeslots.service.ts | 3 ++- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index b175296..a618636 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -910,28 +910,6 @@ describe('TimeslotsController', () => { expect(allAvailabilities).toHaveLength(mockAvailability.length); }); - it('DEBUG: check format of an Availabilities table in db', async () => { - const availability = await availabilityService.findById(1); - expect(availability).toBeDefined(); - expect(availability).toHaveProperty('id', 1); - expect(availability).toHaveProperty('state'); - expect(availability).toHaveProperty('lastModified'); - expect(availability).toHaveProperty('timeSlot'); - expect(availability).toHaveProperty('user'); - expect(availability).toEqual(mockAvailability[0]); - }); - - it('DEBUG: check format of a TimeSlots table in db', async () => { - const timeSlot = await timeSlotsService.findById(51); - expect(timeSlot).toBeDefined(); - expect(timeSlot).toHaveProperty('id', 51); - expect(timeSlot).toHaveProperty('start'); - expect(timeSlot).toHaveProperty('end'); - expect(timeSlot).toHaveProperty('recruitmentSession'); - expect(timeSlot).toHaveProperty('availabilities'); - expect(timeSlot).toEqual(mockTimeSlots[0]); - }); - it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { const allAvailabilities = await availabilityService.listAvailabilities(); const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 852ebd3..7272b64 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -186,7 +186,7 @@ export class TimeSlotsService { ); queryBuilder.getMany(); - const allMatches = await this.timeSlotRepository.find({ + return await this.timeSlotRepository.find({ relations: [ 'availabilities', 'availabilities.user', @@ -201,6 +201,7 @@ export class TimeSlotsService { }, }, }); + let allMatches = await queryBuilder.getMany(); let goodTimeSlots: TimeSlot[] = []; allMatches.forEach((timeSlot) => { From bbb997f9e0fbae18494067eb73b70f6bfd29d880 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 23:05:01 +0200 Subject: [PATCH 40/57] not def --- api/src/timeslots/timeslots.e2e-spec.ts | 4 +- api/src/timeslots/timeslots.service.spec.ts | 108 ++++++++++---------- api/src/timeslots/timeslots.service.ts | 35 +++---- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index a618636..5a02cd6 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -856,7 +856,7 @@ describe('TimeslotsController', () => { await app.close(); }); - describe(' GET /timeslots', () => { + describe('GET /timeslots', () => { beforeEach(async () => { for (const user of mockUsers) { await usersService.create(user); @@ -879,7 +879,7 @@ describe('TimeslotsController', () => { const availability = { id: a.id, state: a.state, - lastModified: a.lastModified, + lastModified: a.lastModified , timeSlot: timeSlotsService.findById(a.timeSlot), user: mockUsers.find((u) => u.oauthId === a.userOauthId), } as unknown as Availability; diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index d400597..e47d835 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -196,64 +196,64 @@ describe('TimeSlotsService', () => { }); }); - // describe('findAvailableTimeSlots', () => { - // it('should correctly call all functions provided for database query', async () => { - // // Mock the query builder and its methods - // const mockQueryBuilder = { - // innerJoinAndSelect: jest.fn().mockReturnThis(), - // where: jest.fn().mockReturnThis(), - // andWhere: jest.fn().mockReturnThis(), - // getMany: jest.fn().mockResolvedValue([]), - // }; + describe('findAvailableTimeSlots', () => { + it('should correctly call all functions provided for database query', async () => { + // Mock the query builder and its methods + const mockQueryBuilder = { + innerJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([]), + }; - // // Mock the timeSlotRepository and its methods - // const mockTimeSlotRepository = { - // createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), - // }; + // Mock the timeSlotRepository and its methods + const mockTimeSlotRepository = { + createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), + }; - // const timeSlotService = new TimeSlotsService( - // mockTimeSlotRepository as any, - // ); - // const result = await timeSlotService.findAvailableTimeSlots(); + const timeSlotService = new TimeSlotsService( + mockTimeSlotRepository as any, + ); + const result = await timeSlotService.findAvailableTimeSlots(); - // // Assert that the query builder methods were called correctly - // expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( - // 'TimeSlot.availabilities', - // 'availability', - // ); - // expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( - // 'TimeSlot.recruitmentSession', - // 'recruitmentSession', - // ); - // expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( - // 'availability.user', - // 'user', - // ); - // expect(mockQueryBuilder.where).toHaveBeenCalledWith( - // 'recruitmentSession.state = :recruitmentSessionState', - // { - // recruitmentSessionState: RecruitmentSessionState.Active, - // }, - // ); - // expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - // 'user.role NOT IN (:...roles)', - // { - // roles: [Role.None, Role.Applicant], - // }, - // ); - // expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - // 'availability.state = :availabilityState AND (user.is_board = true OR user.is_expert = true)', - // { - // availabilityState: AvailabilityState.Free, - // }, - // ); - // expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( - // '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', - // ); + // Assert that the query builder methods were called correctly + expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( + 'TimeSlot.availabilities', + 'availability', + ); + expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( + 'TimeSlot.recruitmentSession', + 'recruitmentSession', + ); + expect(mockQueryBuilder.innerJoinAndSelect).toHaveBeenCalledWith( + 'availability.user', + 'user', + ); + expect(mockQueryBuilder.where).toHaveBeenCalledWith( + 'recruitmentSession.state = :recruitmentSessionState', + { + recruitmentSessionState: RecruitmentSessionState.Active, + }, + ); + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + 'user.role NOT IN (:...roles)', + { + roles: [Role.None, Role.Applicant], + }, + ); + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + 'availability.state = :availabilityState AND (user.is_board = true OR user.is_expert = true)', + { + availabilityState: AvailabilityState.Free, + }, + ); + expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith( + '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', + ); - // expect(result).toEqual([]); - // }); - // }); + expect(result).toEqual([]); + }); + }); }); function testTimeSlotsGeneration( diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 7272b64..b1885ca 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -8,8 +8,6 @@ import { AvailabilityState, Role, } from '@hkrecruitment/shared'; -import { CreateTimeSlotDto } from './create-timeslot.dto'; -import { count } from 'console'; @Injectable() export class TimeSlotsService { @@ -185,23 +183,22 @@ export class TimeSlotsService { '(SELECT COUNT(availability.id) FROM Availability availability WHERE availability.timeSlotId = TimeSlot.id) > 1', ); - queryBuilder.getMany(); - return await this.timeSlotRepository.find({ - relations: [ - 'availabilities', - 'availabilities.user', - 'recruitmentSession', - ], - where: { - availabilities: { - state: AvailabilityState.Free, - user: { - role: Not(In([Role.Applicant, Role.None])), - }, - }, - }, - }); - let allMatches = await queryBuilder.getMany(); + const allMatches = await queryBuilder.getMany(); + // const allMatches = await this.timeSlotRepository.find({ + // relations: [ + // 'availabilities', + // 'availabilities.user', + // 'recruitmentSession', + // ], + // where: { + // availabilities: { + // state: AvailabilityState.Free, + // user: { + // role: Not(In([Role.Applicant, Role.None])), + // }, + // }, + // }, + // }); let goodTimeSlots: TimeSlot[] = []; allMatches.forEach((timeSlot) => { From 1356a787d02569756b42b3dddc20e2ba8979552d Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 23:05:12 +0200 Subject: [PATCH 41/57] not def --- api/src/timeslots/timeslots.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 5a02cd6..8b2a3bf 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -879,7 +879,7 @@ describe('TimeslotsController', () => { const availability = { id: a.id, state: a.state, - lastModified: a.lastModified , + lastModified: a.lastModified, timeSlot: timeSlotsService.findById(a.timeSlot), user: mockUsers.find((u) => u.oauthId === a.userOauthId), } as unknown as Availability; From 4ed9b9d7d009928b86dd55ded241af66ff74bdc6 Mon Sep 17 00:00:00 2001 From: white Date: Sun, 19 May 2024 23:07:10 +0200 Subject: [PATCH 42/57] not def! --- api/src/timeslots/timeslots.e2e-spec.ts | 60 ++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 8b2a3bf..261da44 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -910,36 +910,36 @@ describe('TimeslotsController', () => { expect(allAvailabilities).toHaveLength(mockAvailability.length); }); - it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { - const allAvailabilities = await availabilityService.listAvailabilities(); - const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); - const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73); - expect(av71).toHaveLength(4); - expect(av73).toHaveLength(2); - }); + // it('DEBUG: check availabilities for timeslot having id 71 & 73', async () => { + // const allAvailabilities = await availabilityService.listAvailabilities(); + // const av71 = allAvailabilities.filter((a) => a.timeSlot.id === 71); + // const av73 = allAvailabilities.filter((a) => a.timeSlot.id === 73); + // expect(av71).toHaveLength(4); + // expect(av73).toHaveLength(2); + // }); - it('should return all available timeslots', async () => { - const expected = [ - { - end: '2024-05-19T14:00:00.000Z', - id: 71, - start: '2024-05-19T13:00:00.000Z', - }, - { - end: '2024-05-19T16:00:00.000Z', - id: 73, - start: '2024-05-19T15:00:00.000Z', - }, - ]; - await request(app.getHttpServer()) - .get('/timeslots') - .set('Authorization', `Bearer ${newMemberToken}`) - .expect(200) - .expect((res) => { - expect(res.body).toBeInstanceOf(Array); - expect(res.body).toHaveLength(2); - expect(res.body).toEqual(expected); - }); - }); + // it('should return all available timeslots', async () => { + // const expected = [ + // { + // end: '2024-05-19T14:00:00.000Z', + // id: 71, + // start: '2024-05-19T13:00:00.000Z', + // }, + // { + // end: '2024-05-19T16:00:00.000Z', + // id: 73, + // start: '2024-05-19T15:00:00.000Z', + // }, + // ]; + // await request(app.getHttpServer()) + // .get('/timeslots') + // .set('Authorization', `Bearer ${newMemberToken}`) + // .expect(200) + // .expect((res) => { + // expect(res.body).toBeInstanceOf(Array); + // expect(res.body).toHaveLength(2); + // expect(res.body).toEqual(expected); + // }); + // }); }); }); From 7d6690c1b3dc2a66cc04dd7f2890d0aafd767f2c Mon Sep 17 00:00:00 2001 From: white Date: Wed, 22 May 2024 09:26:04 +0200 Subject: [PATCH 43/57] fix: pull requested changes --- api/src/mocks/db-data.ts | 810 ++++++++++++++++++++++ api/src/mocks/requests.http | 2 +- api/src/timeslots/create-timeslot.dto.ts | 2 +- api/src/timeslots/timeslots.e2e-spec.ts | 829 +---------------------- api/src/timeslots/timeslots.service.ts | 15 - 5 files changed, 818 insertions(+), 840 deletions(-) create mode 100644 api/src/mocks/db-data.ts diff --git a/api/src/mocks/db-data.ts b/api/src/mocks/db-data.ts new file mode 100644 index 0000000..e54310e --- /dev/null +++ b/api/src/mocks/db-data.ts @@ -0,0 +1,810 @@ +import { + Person, + Role, + TimeSlot, + AvailabilityState, + RecruitmentSessionState, +} from '@hkrecruitment/shared'; + +export let mockRecruitmentSessions = [ + { + id: 1, + state: RecruitmentSessionState.Concluded, + slotDuration: 1, + lastModified: new Date('2024-04-10'), + createdAt: new Date('2024-04-04'), + days: [ + new Date('2024-04-05'), + new Date('2024-04-06'), + new Date('2024-04-07'), + new Date('2024-04-08'), + new Date('2024-04-09'), + ], + interviewStart: new Date('2024-04-05'), + interviewEnd: new Date('2024-04-10'), + }, + { + id: 2, + state: RecruitmentSessionState.Concluded, + slotDuration: 1, + lastModified: new Date('2024-05-10'), + createdAt: new Date('2024-05-04'), + days: [ + new Date('2024-05-05'), + new Date('2024-05-06'), + new Date('2024-05-07'), + new Date('2024-05-08'), + ], + interviewStart: new Date('2024-05-05'), + interviewEnd: new Date('2024-05-09'), + }, + { + id: 3, + state: RecruitmentSessionState.Active, + slotDuration: 1, + lastModified: new Date('2024-05-14'), + createdAt: new Date('2024-05-14'), + days: [ + new Date('2024-05-16'), + new Date('2024-05-17'), + new Date('2024-05-18'), + new Date('2024-05-19'), + new Date('2024-05-20'), + ], + interviewStart: new Date('2024-05-16'), + interviewEnd: new Date('2024-05-21'), + }, +]; + +export let mockUsers = [ + { + oauthId: '1', + firstName: 'Pasquale', + lastName: 'Bianco', + sex: 'M', + email: 'p.bianco@gmail.com', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '2', + firstName: 'John', + lastName: 'Doe', + sex: 'M', + email: 'j.doe@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '3', + firstName: 'Jane', + lastName: 'Smith', + sex: 'F', + email: 'j.smith@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '4', + firstName: 'Michael', + lastName: 'Johnson', + sex: 'M', + email: 'm.johnson@gmail.com', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '5', + firstName: 'Emily', + lastName: 'Brown', + sex: 'F', + email: 'e.brown@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '6', + firstName: 'David', + lastName: 'Wilson', + sex: 'M', + email: 'd.wilson@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '7', + firstName: 'Olivia', + lastName: 'Johnson', + sex: 'F', + email: 'o.johnson@gmail.com', + role: Role.Member, + is_board: true, + is_expert: false, + }, + { + oauthId: '8', + firstName: 'James', + lastName: 'Smith', + sex: 'M', + email: 'j.smith@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '9', + firstName: 'Sophia', + lastName: 'Miller', + sex: 'F', + email: 's.miller@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '10', + firstName: 'Benjamin', + lastName: 'Davis', + sex: 'M', + email: 'b.davis@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '11', + firstName: 'Ava', + lastName: 'Wilson', + sex: 'female', + email: 'a.wilson@gmail.com', + role: Role.Member, + is_board: true, + is_expert: true, + }, + { + oauthId: '12', + firstName: 'William', + lastName: 'Anderson', + sex: 'M', + email: 'w.anderson@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '13', + firstName: 'Mia', + lastName: 'Thomas', + sex: 'F', + email: 'm.thomas@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '14', + firstName: 'Alexander', + lastName: 'Taylor', + sex: 'M', + email: 'a.taylor@gmail.com', + role: Role.Member, + is_board: false, + is_expert: true, + }, + { + oauthId: '15', + firstName: 'Charlotte', + lastName: 'Clark', + sex: 'F', + email: 'c.clark@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '16', + firstName: 'Daniel', + lastName: 'Moore', + sex: 'M', + email: 'd.moore@gmail.com', + role: Role.Member, + is_board: false, + is_expert: false, + }, + { + oauthId: '17', + firstName: 'Amelia', + lastName: 'Walker', + sex: 'F', + email: 'a.walker@gmail.com', + role: Role.Clerk, + is_board: false, + is_expert: false, + }, + { + oauthId: '18', + firstName: 'Matthew', + lastName: 'Lewis', + sex: 'M', + email: 'm.lewis@gmail.com', + role: Role.Admin, + is_board: false, + is_expert: true, + }, + { + oauthId: '19', + firstName: 'Ella', + lastName: 'Harris', + sex: 'F', + email: 'e.harris@gmail.com', + role: Role.Supervisor, + is_board: false, + is_expert: true, + }, + { + oauthId: '20', + firstName: 'Joseph', + lastName: 'King', + sex: 'M', + email: 'j.king@gmail.com', + role: Role.Supervisor, + is_board: true, + is_expert: false, + }, +]; + +export let mockTimeSlots = [ + { + id: 51, + start: new Date('2024-05-16 10:00:00'), + end: new Date('2024-05-16 11:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 52, + start: new Date('2024-05-16 11:00:00'), + end: new Date('2024-05-16 12:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 53, + start: new Date('2024-05-16 15:00:00'), + end: new Date('2024-05-16 16:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 54, + start: new Date('2024-05-16 16:00:00'), + end: new Date('2024-05-16 17:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 55, + start: new Date('2024-05-16 17:00:00'), + end: new Date('2024-05-16 18:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 56, + start: new Date('2024-05-16 18:00:00'), + end: new Date('2024-05-16 19:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 57, + start: new Date('2024-05-17 10:00:00'), + end: new Date('2024-05-17 11:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 58, + start: new Date('2024-05-17 11:00:00'), + end: new Date('2024-05-17 12:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 59, + start: new Date('2024-05-17 15:00:00'), + end: new Date('2024-05-17 16:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 60, + start: new Date('2024-05-17 16:00:00'), + end: new Date('2024-05-17 17:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 61, + start: new Date('2024-05-17 17:00:00'), + end: new Date('2024-05-17 18:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 62, + start: new Date('2024-05-17 18:00:00'), + end: new Date('2024-05-17 19:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 63, + start: new Date('2024-05-18 10:00:00'), + end: new Date('2024-05-18 11:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 64, + start: new Date('2024-05-18 11:00:00'), + end: new Date('2024-05-18 12:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 65, + start: new Date('2024-05-18 15:00:00'), + end: new Date('2024-05-18 16:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 66, + start: new Date('2024-05-18 16:00:00'), + end: new Date('2024-05-18 17:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 67, + start: new Date('2024-05-18 17:00:00'), + end: new Date('2024-05-18 18:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 68, + start: new Date('2024-05-18 18:00:00'), + end: new Date('2024-05-18 19:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 69, + start: new Date('2024-05-19 10:00:00'), + end: new Date('2024-05-19 11:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 70, + start: new Date('2024-05-19 11:00:00'), + end: new Date('2024-05-19 12:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 71, + start: new Date('2024-05-19 15:00:00'), + end: new Date('2024-05-19 16:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 72, + start: new Date('2024-05-19 16:00:00'), + end: new Date('2024-05-19 17:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 73, + start: new Date('2024-05-19 17:00:00'), + end: new Date('2024-05-19 18:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 74, + start: new Date('2024-05-19 18:00:00'), + end: new Date('2024-05-19 19:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 75, + start: new Date('2024-05-20 10:00:00'), + end: new Date('2024-05-20 11:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 76, + start: new Date('2024-05-20 11:00:00'), + end: new Date('2024-05-20 12:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 77, + start: new Date('2024-05-20 15:00:00'), + end: new Date('2024-05-20 16:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 78, + start: new Date('2024-05-20 16:00:00'), + end: new Date('2024-05-20 17:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 79, + start: new Date('2024-05-20 17:00:00'), + end: new Date('2024-05-20 18:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, + { + id: 80, + start: new Date('2024-05-20 18:00:00'), + end: new Date('2024-05-20 19:00:00'), + recruitmentSession: mockRecruitmentSessions[2], + }, +]; + +export let mockAvailability = [ + { + id: 1, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 51, + userOauthId: '5', + }, + { + id: 2, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 53, + userOauthId: '7', + }, + { + id: 3, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 55, + userOauthId: '12', + }, + { + id: 4, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 61, + userOauthId: '19', + }, + { + id: 5, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '3', + }, + { + id: 6, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 78, + userOauthId: '15', + }, + { + id: 7, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 58, + userOauthId: '11', + }, + { + id: 8, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 57, + userOauthId: '8', + }, + { + id: 9, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '5', + }, + { + id: 10, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 53, + userOauthId: '17', + }, + { + id: 11, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 62, + userOauthId: '2', + }, + { + id: 12, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '9', + }, + { + id: 13, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '14', + }, + { + id: 14, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '6', + }, + { + id: 15, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '13', + }, + { + id: 16, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '10', + }, + { + id: 17, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '4', + }, + { + id: 18, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '16', + }, + { + id: 19, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 59, + userOauthId: '18', + }, + { + id: 20, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 57, + userOauthId: '1', + }, + { + id: 21, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 66, + userOauthId: '20', + }, + { + id: 22, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 68, + userOauthId: '19', + }, + { + id: 23, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 69, + userOauthId: '7', + }, + { + id: 24, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 70, + userOauthId: '16', + }, + { + id: 25, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '15', + }, + { + id: 26, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 73, + userOauthId: '11', + }, + { + id: 27, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '14', + }, + { + id: 28, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 76, + userOauthId: '9', + }, + { + id: 29, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 77, + userOauthId: '8', + }, + { + id: 30, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 78, + userOauthId: '10', + }, + { + id: 31, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 79, + userOauthId: '13', + }, + { + id: 32, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 80, + userOauthId: '12', + }, + { + id: 33, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 79, + userOauthId: '20', + }, + { + id: 34, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 78, + userOauthId: '18', + }, + { + id: 35, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 77, + userOauthId: '17', + }, + { + id: 36, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 76, + userOauthId: '19', + }, + { + id: 37, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 75, + userOauthId: '16', + }, + { + id: 38, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 74, + userOauthId: '15', + }, + { + id: 39, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 73, + userOauthId: '14', + }, + { + id: 40, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 72, + userOauthId: '13', + }, + { + id: 41, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 71, + userOauthId: '12', + }, + { + id: 42, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 70, + userOauthId: '11', + }, + { + id: 43, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 69, + userOauthId: '10', + }, + { + id: 44, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 68, + userOauthId: '9', + }, + { + id: 45, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 67, + userOauthId: '8', + }, + { + id: 46, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 66, + userOauthId: '7', + }, + { + id: 47, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 65, + userOauthId: '6', + }, + { + id: 48, + state: AvailabilityState.Interviewing, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 64, + userOauthId: '5', + }, + { + id: 49, + state: AvailabilityState.Recovering, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 63, + userOauthId: '8', + }, + { + id: 50, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 62, + userOauthId: '14', + }, + { + id: 51, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 61, + userOauthId: '4', + }, + { + id: 52, + state: AvailabilityState.Free, + lastModified: new Date('2024-05-14 00:00:00'), + timeSlot: 60, + userOauthId: '13', + }, +]; diff --git a/api/src/mocks/requests.http b/api/src/mocks/requests.http index c0f0e99..1dcbb14 100644 --- a/api/src/mocks/requests.http +++ b/api/src/mocks/requests.http @@ -1,2 +1,2 @@ GET http://localhost:3000/v1/timeslots/ -Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc4aFdBRTB0U00tX3JJVUR0WElNMyJ9.eyJlbWFpbCI6InBhc3F1YWxlLmJpYW5jb0Boa25wb2xpdG8ub3JnIiwiaXNzIjoiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYyMjgyMTU0MzYxNTk5ODcwNTEiLCJhdWQiOlsiaHR0cDovL2hrcmVjcnVpdG1lbnQub3JnIiwiaHR0cHM6Ly9kZXYtYzhyb29jZGw3NjNsbDVxZi5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzE2MTEzNTk3LCJleHAiOjE3MTYxOTk5OTcsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJaekNWd2R2eUJOUWZKc3R1ZUJPcVh3TW1jazZCa0d4NiJ9.PyyW67LJIWc9NbMibzvzfxlY0Pe1YTsZVsV2--ckBQ5hfYsCKqCUi6aIjmiliz_uZO5qdPAsZ7FYNOtOI8fGhPt9QfKSqA166D-CZWnpuwE4cM_EjbpCoHRRQ49r2Ycv_ZnzVXrO6zCyEVZYgx-USGiuBnRcKbIPDJCS4Tc9bcmklcUdq4ujAPtSDdmid9jcVN3DM5eLtXAQuYIUho3G3NRVuKlUnoNIsK16yrKtx5kgOHKuUP6dFYExpp2ViB4oZV-v_linCsXZEE5x-7GBMntJAGtenCs-1uihB-OGLYJY_6fPsRG2rgw5Fl8Ax0Ioc8LeXkwlJ2L-MO1ms-r8uQ +Authorization: Bearer diff --git a/api/src/timeslots/create-timeslot.dto.ts b/api/src/timeslots/create-timeslot.dto.ts index 0b50071..2d40f64 100644 --- a/api/src/timeslots/create-timeslot.dto.ts +++ b/api/src/timeslots/create-timeslot.dto.ts @@ -4,7 +4,7 @@ import { Availability } from 'src/availability/availability.entity'; import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; export class CreateTimeSlotDto implements Partial { - @ApiProperty() // + @ApiProperty() start: Date; @ApiProperty() diff --git a/api/src/timeslots/timeslots.e2e-spec.ts b/api/src/timeslots/timeslots.e2e-spec.ts index 261da44..065e852 100644 --- a/api/src/timeslots/timeslots.e2e-spec.ts +++ b/api/src/timeslots/timeslots.e2e-spec.ts @@ -4,16 +4,14 @@ import { TimeSlotsService } from './timeslots.service'; import { UsersService } from '../users/users.service'; import { AvailabilityService } from '../availability/availability.service'; import { RecruitmentSessionService } from '../recruitment-session/recruitment-session.service'; -import { - Person, - Role, - TimeSlot, - AvailabilityState, - RecruitmentSessionState, -} from '@hkrecruitment/shared'; -import { RecruitmentSession } from 'src/recruitment-session/recruitment-session.entity'; import { Availability } from 'src/availability/availability.entity'; import { createApp, getAccessToken } from 'test/app.e2e-spec'; +import { + mockAvailability, + mockRecruitmentSessions, + mockTimeSlots, + mockUsers, +} from '../mocks/db-data'; describe('TimeslotsController', () => { let app: INestApplication; @@ -22,824 +20,9 @@ describe('TimeslotsController', () => { let usersService: UsersService; let availabilityService: AvailabilityService; let recruitmentSessionService: RecruitmentSessionService; - let mockUsers: Person[]; - let mockTimeSlots: TimeSlot[]; - let mockRecruitmentSessions: RecruitmentSession[]; - - class mockAv { - id: number; - state: AvailabilityState; - lastModified: Date; - timeSlot: number; - userOauthId: string; - } - let mockAvailability: mockAv[]; beforeAll(async () => { newMemberToken = await getAccessToken('newMember'); - - mockRecruitmentSessions = [ - { - id: 1, - state: RecruitmentSessionState.Concluded, - slotDuration: 1, - lastModified: new Date('2024-04-10'), - createdAt: new Date('2024-04-04'), - days: [ - new Date('2024-04-05'), - new Date('2024-04-06'), - new Date('2024-04-07'), - new Date('2024-04-08'), - new Date('2024-04-09'), - ], - interviewStart: new Date('2024-04-05'), - interviewEnd: new Date('2024-04-10'), - }, - { - id: 2, - state: RecruitmentSessionState.Concluded, - slotDuration: 1, - lastModified: new Date('2024-05-10'), - createdAt: new Date('2024-05-04'), - days: [ - new Date('2024-05-05'), - new Date('2024-05-06'), - new Date('2024-05-07'), - new Date('2024-05-08'), - ], - interviewStart: new Date('2024-05-05'), - interviewEnd: new Date('2024-05-09'), - }, - { - id: 3, - state: RecruitmentSessionState.Active, - slotDuration: 1, - lastModified: new Date('2024-05-14'), - createdAt: new Date('2024-05-14'), - days: [ - new Date('2024-05-16'), - new Date('2024-05-17'), - new Date('2024-05-18'), - new Date('2024-05-19'), - new Date('2024-05-20'), - ], - interviewStart: new Date('2024-05-16'), - interviewEnd: new Date('2024-05-21'), - }, - ]; - - mockUsers = [ - { - oauthId: '1', - firstName: 'Pasquale', - lastName: 'Bianco', - sex: 'M', - email: 'p.bianco@gmail.com', - role: Role.Member, - is_board: true, - is_expert: true, - }, - { - oauthId: '2', - firstName: 'John', - lastName: 'Doe', - sex: 'M', - email: 'j.doe@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, - { - oauthId: '3', - firstName: 'Jane', - lastName: 'Smith', - sex: 'F', - email: 'j.smith@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '4', - firstName: 'Michael', - lastName: 'Johnson', - sex: 'M', - email: 'm.johnson@gmail.com', - role: Role.Member, - is_board: true, - is_expert: true, - }, - { - oauthId: '5', - firstName: 'Emily', - lastName: 'Brown', - sex: 'F', - email: 'e.brown@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, - { - oauthId: '6', - firstName: 'David', - lastName: 'Wilson', - sex: 'M', - email: 'd.wilson@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '7', - firstName: 'Olivia', - lastName: 'Johnson', - sex: 'F', - email: 'o.johnson@gmail.com', - role: Role.Member, - is_board: true, - is_expert: false, - }, - { - oauthId: '8', - firstName: 'James', - lastName: 'Smith', - sex: 'M', - email: 'j.smith@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '9', - firstName: 'Sophia', - lastName: 'Miller', - sex: 'F', - email: 's.miller@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, - { - oauthId: '10', - firstName: 'Benjamin', - lastName: 'Davis', - sex: 'M', - email: 'b.davis@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '11', - firstName: 'Ava', - lastName: 'Wilson', - sex: 'female', - email: 'a.wilson@gmail.com', - role: Role.Member, - is_board: true, - is_expert: true, - }, - { - oauthId: '12', - firstName: 'William', - lastName: 'Anderson', - sex: 'M', - email: 'w.anderson@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '13', - firstName: 'Mia', - lastName: 'Thomas', - sex: 'F', - email: 'm.thomas@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '14', - firstName: 'Alexander', - lastName: 'Taylor', - sex: 'M', - email: 'a.taylor@gmail.com', - role: Role.Member, - is_board: false, - is_expert: true, - }, - { - oauthId: '15', - firstName: 'Charlotte', - lastName: 'Clark', - sex: 'F', - email: 'c.clark@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '16', - firstName: 'Daniel', - lastName: 'Moore', - sex: 'M', - email: 'd.moore@gmail.com', - role: Role.Member, - is_board: false, - is_expert: false, - }, - { - oauthId: '17', - firstName: 'Amelia', - lastName: 'Walker', - sex: 'F', - email: 'a.walker@gmail.com', - role: Role.Clerk, - is_board: false, - is_expert: false, - }, - { - oauthId: '18', - firstName: 'Matthew', - lastName: 'Lewis', - sex: 'M', - email: 'm.lewis@gmail.com', - role: Role.Admin, - is_board: false, - is_expert: true, - }, - { - oauthId: '19', - firstName: 'Ella', - lastName: 'Harris', - sex: 'F', - email: 'e.harris@gmail.com', - role: Role.Supervisor, - is_board: false, - is_expert: true, - }, - { - oauthId: '20', - firstName: 'Joseph', - lastName: 'King', - sex: 'M', - email: 'j.king@gmail.com', - role: Role.Supervisor, - is_board: true, - is_expert: false, - }, - ]; - - mockTimeSlots = [ - { - id: 51, - start: new Date('2024-05-16 10:00:00'), - end: new Date('2024-05-16 11:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 52, - start: new Date('2024-05-16 11:00:00'), - end: new Date('2024-05-16 12:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 53, - start: new Date('2024-05-16 15:00:00'), - end: new Date('2024-05-16 16:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 54, - start: new Date('2024-05-16 16:00:00'), - end: new Date('2024-05-16 17:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 55, - start: new Date('2024-05-16 17:00:00'), - end: new Date('2024-05-16 18:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 56, - start: new Date('2024-05-16 18:00:00'), - end: new Date('2024-05-16 19:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 57, - start: new Date('2024-05-17 10:00:00'), - end: new Date('2024-05-17 11:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 58, - start: new Date('2024-05-17 11:00:00'), - end: new Date('2024-05-17 12:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 59, - start: new Date('2024-05-17 15:00:00'), - end: new Date('2024-05-17 16:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 60, - start: new Date('2024-05-17 16:00:00'), - end: new Date('2024-05-17 17:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 61, - start: new Date('2024-05-17 17:00:00'), - end: new Date('2024-05-17 18:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 62, - start: new Date('2024-05-17 18:00:00'), - end: new Date('2024-05-17 19:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 63, - start: new Date('2024-05-18 10:00:00'), - end: new Date('2024-05-18 11:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 64, - start: new Date('2024-05-18 11:00:00'), - end: new Date('2024-05-18 12:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 65, - start: new Date('2024-05-18 15:00:00'), - end: new Date('2024-05-18 16:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 66, - start: new Date('2024-05-18 16:00:00'), - end: new Date('2024-05-18 17:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 67, - start: new Date('2024-05-18 17:00:00'), - end: new Date('2024-05-18 18:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 68, - start: new Date('2024-05-18 18:00:00'), - end: new Date('2024-05-18 19:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 69, - start: new Date('2024-05-19 10:00:00'), - end: new Date('2024-05-19 11:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 70, - start: new Date('2024-05-19 11:00:00'), - end: new Date('2024-05-19 12:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 71, - start: new Date('2024-05-19 15:00:00'), - end: new Date('2024-05-19 16:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 72, - start: new Date('2024-05-19 16:00:00'), - end: new Date('2024-05-19 17:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 73, - start: new Date('2024-05-19 17:00:00'), - end: new Date('2024-05-19 18:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 74, - start: new Date('2024-05-19 18:00:00'), - end: new Date('2024-05-19 19:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 75, - start: new Date('2024-05-20 10:00:00'), - end: new Date('2024-05-20 11:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 76, - start: new Date('2024-05-20 11:00:00'), - end: new Date('2024-05-20 12:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 77, - start: new Date('2024-05-20 15:00:00'), - end: new Date('2024-05-20 16:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 78, - start: new Date('2024-05-20 16:00:00'), - end: new Date('2024-05-20 17:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 79, - start: new Date('2024-05-20 17:00:00'), - end: new Date('2024-05-20 18:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - { - id: 80, - start: new Date('2024-05-20 18:00:00'), - end: new Date('2024-05-20 19:00:00'), - recruitmentSession: mockRecruitmentSessions[2], - }, - ]; - - mockAvailability = [ - { - id: 1, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 51, - userOauthId: '5', - }, - { - id: 2, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 53, - userOauthId: '7', - }, - { - id: 3, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 55, - userOauthId: '12', - }, - { - id: 4, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 61, - userOauthId: '19', - }, - { - id: 5, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 71, - userOauthId: '3', - }, - { - id: 6, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 78, - userOauthId: '15', - }, - { - id: 7, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 58, - userOauthId: '11', - }, - { - id: 8, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 57, - userOauthId: '8', - }, - { - id: 9, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 64, - userOauthId: '5', - }, - { - id: 10, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 53, - userOauthId: '17', - }, - { - id: 11, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 62, - userOauthId: '2', - }, - { - id: 12, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 71, - userOauthId: '9', - }, - { - id: 13, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 72, - userOauthId: '14', - }, - { - id: 14, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 74, - userOauthId: '6', - }, - { - id: 15, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 75, - userOauthId: '13', - }, - { - id: 16, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 74, - userOauthId: '10', - }, - { - id: 17, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 71, - userOauthId: '4', - }, - { - id: 18, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 64, - userOauthId: '16', - }, - { - id: 19, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 59, - userOauthId: '18', - }, - { - id: 20, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 57, - userOauthId: '1', - }, - { - id: 21, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 66, - userOauthId: '20', - }, - { - id: 22, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 68, - userOauthId: '19', - }, - { - id: 23, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 69, - userOauthId: '7', - }, - { - id: 24, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 70, - userOauthId: '16', - }, - { - id: 25, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 72, - userOauthId: '15', - }, - { - id: 26, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 73, - userOauthId: '11', - }, - { - id: 27, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 75, - userOauthId: '14', - }, - { - id: 28, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 76, - userOauthId: '9', - }, - { - id: 29, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 77, - userOauthId: '8', - }, - { - id: 30, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 78, - userOauthId: '10', - }, - { - id: 31, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 79, - userOauthId: '13', - }, - { - id: 32, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 80, - userOauthId: '12', - }, - { - id: 33, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 79, - userOauthId: '20', - }, - { - id: 34, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 78, - userOauthId: '18', - }, - { - id: 35, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 77, - userOauthId: '17', - }, - { - id: 36, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 76, - userOauthId: '19', - }, - { - id: 37, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 75, - userOauthId: '16', - }, - { - id: 38, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 74, - userOauthId: '15', - }, - { - id: 39, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 73, - userOauthId: '14', - }, - { - id: 40, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 72, - userOauthId: '13', - }, - { - id: 41, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 71, - userOauthId: '12', - }, - { - id: 42, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 70, - userOauthId: '11', - }, - { - id: 43, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 69, - userOauthId: '10', - }, - { - id: 44, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 68, - userOauthId: '9', - }, - { - id: 45, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 67, - userOauthId: '8', - }, - { - id: 46, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 66, - userOauthId: '7', - }, - { - id: 47, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 65, - userOauthId: '6', - }, - { - id: 48, - state: AvailabilityState.Interviewing, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 64, - userOauthId: '5', - }, - { - id: 49, - state: AvailabilityState.Recovering, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 63, - userOauthId: '8', - }, - { - id: 50, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 62, - userOauthId: '14', - }, - { - id: 51, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 61, - userOauthId: '4', - }, - { - id: 52, - state: AvailabilityState.Free, - lastModified: new Date('2024-05-14 00:00:00'), - timeSlot: 60, - userOauthId: '13', - }, - ]; }); beforeEach(async () => { diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index b1885ca..075ba9c 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -184,21 +184,6 @@ export class TimeSlotsService { ); const allMatches = await queryBuilder.getMany(); - // const allMatches = await this.timeSlotRepository.find({ - // relations: [ - // 'availabilities', - // 'availabilities.user', - // 'recruitmentSession', - // ], - // where: { - // availabilities: { - // state: AvailabilityState.Free, - // user: { - // role: Not(In([Role.Applicant, Role.None])), - // }, - // }, - // }, - // }); let goodTimeSlots: TimeSlot[] = []; allMatches.forEach((timeSlot) => { From 90e0cb594f36b0706ac8fa126edf3bc87264e9c3 Mon Sep 17 00:00:00 2001 From: white Date: Wed, 22 May 2024 09:29:52 +0200 Subject: [PATCH 44/57] upd: sonar properties --- sonar-project.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/sonar-project.properties b/sonar-project.properties index edb0d90..8ec4d92 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,3 +5,4 @@ sonar.projectName=HKrecruitment sonar.javascript.lcov.reportPaths=./coverage/shared/lcov.info, ./coverage/api/lcov.info, ./coverage/api-e2e/lcov.info sonar.sources=api/, frontend/, shared/ sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md +sonar.cpd.exclusions=**/mocks/**/* \ No newline at end of file From 58dc3f14874bb19078263e6d566cb060b2bce3ff Mon Sep 17 00:00:00 2001 From: white Date: Wed, 22 May 2024 10:12:04 +0200 Subject: [PATCH 45/57] =?UTF-8?q?test:=20find=20available=20time=20slots?= =?UTF-8?q?=C3=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/timeslots/timeslots.service.spec.ts | 76 +++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index e47d835..bdac22f 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -252,6 +252,82 @@ describe('TimeSlotsService', () => { ); expect(result).toEqual([]); + + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + it("should return only time slots with at least 2 available people, one of which is a board member", async () => { + const mockQueryBuilder = { + innerJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([ + { + start: new Date('2022-01-01T09:00:00'), + end: new Date('2022-01-01T10:00:00'), + id: 1, + recruitmentSession: 3, + availabilities: [ + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: true, + is_expert: false, + }, + }, + { + state: AvailabilityState.Free, + user: { + role: Role.Applicant, + is_board: false, + is_expert: false, + }, + }, + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: false, + is_expert: true, + }, + }, + { + state: AvailabilityState.Interviewing, + user: { + role: Role.Member, + is_board: false, + is_expert: true, + }, + }, + { + state: AvailabilityState.Interviewing, + user: { + role: Role.Admin, + is_board: false, + is_expert: true, + }, + }, + ]} + ]), + }; + + // Mock the timeSlotRepository and its methods + const mockTimeSlotRepository = { + createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), + }; + + const timeSlotService = new TimeSlotsService( + mockTimeSlotRepository as any, + ); + + const result = await timeSlotService.findAvailableTimeSlots(); + expect(JSON.stringify(result)).toBe(JSON.stringify([{ + id: 1, + start: new Date('2022-01-01T09:00:00'), + end: new Date('2022-01-01T10:00:00'), + }])); }); }); }); From 72e2679d1bb337d6bc686d3e296a6d9e24dafcf2 Mon Sep 17 00:00:00 2001 From: white Date: Wed, 22 May 2024 10:13:30 +0200 Subject: [PATCH 46/57] fix: format --- api/src/timeslots/timeslots.service.spec.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index bdac22f..24284e3 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -257,7 +257,7 @@ describe('TimeSlotsService', () => { jest.resetAllMocks(); }); - it("should return only time slots with at least 2 available people, one of which is a board member", async () => { + it('should return only time slots with at least 2 available people, one of which is a board member', async () => { const mockQueryBuilder = { innerJoinAndSelect: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), @@ -309,7 +309,8 @@ describe('TimeSlotsService', () => { is_expert: true, }, }, - ]} + ], + }, ]), }; @@ -323,11 +324,15 @@ describe('TimeSlotsService', () => { ); const result = await timeSlotService.findAvailableTimeSlots(); - expect(JSON.stringify(result)).toBe(JSON.stringify([{ - id: 1, - start: new Date('2022-01-01T09:00:00'), - end: new Date('2022-01-01T10:00:00'), - }])); + expect(JSON.stringify(result)).toBe( + JSON.stringify([ + { + id: 1, + start: new Date('2022-01-01T09:00:00'), + end: new Date('2022-01-01T10:00:00'), + }, + ]), + ); }); }); }); From d8a4b8e8da7e2ccb2fd0c4372562dcb9283b9f35 Mon Sep 17 00:00:00 2001 From: white Date: Wed, 22 May 2024 10:23:54 +0200 Subject: [PATCH 47/57] test: improvements --- api/src/timeslots/timeslots.service.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index 24284e3..32d6d8d 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -311,6 +311,22 @@ describe('TimeSlotsService', () => { }, ], }, + { + start: new Date('2022-01-01T9:00:00'), + end: new Date('2022-01-01T20:00:00'), + id: 2, + recruitmentSession: 3, + availabilities: [ + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: true, + is_expert: true, + }, + }, + ], + }, ]), }; From 3c82ffaf7b67db42b51ebaba078a6bec09548367 Mon Sep 17 00:00:00 2001 From: white Date: Fri, 24 May 2024 01:38:10 +0200 Subject: [PATCH 48/57] fix: improve coverage --- .../availability/availability.service.spec.ts | 29 +++++++ api/src/mocks/repositories.ts | 3 + .../recruitment-session.service.spec.ts | 83 ++++++++++++++++++- api/src/timeslots/timeslots.service.spec.ts | 75 +++++++++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) diff --git a/api/src/availability/availability.service.spec.ts b/api/src/availability/availability.service.spec.ts index 9f62ba2..43121e6 100644 --- a/api/src/availability/availability.service.spec.ts +++ b/api/src/availability/availability.service.spec.ts @@ -78,6 +78,28 @@ describe('AvailabilityService', () => { }); }); + describe('updateAvailability', () => { + it('should update the availability with the specified id', async () => { + const mockUpdatedAvailability = { + ...mockAvailability, + state: AvailabilityState.Interviewing, + }; + jest + .spyOn(mockedRepository, 'save') + .mockResolvedValue(mockUpdatedAvailability); + const result = await service.updateAvailability( + mockAvailability.id, + mockUpdatedAvailability, + ); + expect(result).toEqual(mockUpdatedAvailability); + expect(mockedRepository.save).toHaveBeenCalledTimes(1); + expect(mockedRepository.save).toHaveBeenCalledWith({ + ...mockUpdatedAvailability, + id: mockAvailability.id, + }); + }); + }); + describe('deleteAvailability', () => { it('should remove the specified availability from the database', async () => { const mockAvailabilityRepository = { @@ -142,6 +164,13 @@ describe('AvailabilityService', () => { expect(result).toEqual(mockAvailability); expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); }); + + it('should return null if no availability is found', async () => { + jest.spyOn(mockedRepository, 'findBy').mockResolvedValue([]); + const result = await service.findById(mockAvailability.id); + expect(result).toBeNull(); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + }); }); describe('findByUserAndTimeSlot', () => { diff --git a/api/src/mocks/repositories.ts b/api/src/mocks/repositories.ts index e418727..7ac0b52 100644 --- a/api/src/mocks/repositories.ts +++ b/api/src/mocks/repositories.ts @@ -1,6 +1,9 @@ +import { count } from 'console'; + export const mockedRepository = { find: jest.fn(), findBy: jest.fn(), remove: jest.fn(), save: jest.fn(), + count: jest.fn(), }; diff --git a/api/src/recruitment-session/recruitment-session.service.spec.ts b/api/src/recruitment-session/recruitment-session.service.spec.ts index 6df16dc..888ba10 100644 --- a/api/src/recruitment-session/recruitment-session.service.spec.ts +++ b/api/src/recruitment-session/recruitment-session.service.spec.ts @@ -5,7 +5,7 @@ import { getRepositoryToken, getDataSourceToken } from '@nestjs/typeorm'; import { RecruitmentSession } from './recruitment-session.entity'; import { RecruitmentSessionService } from './recruitment-session.service'; import { mockedTimeSlotsService as mockedTimeSlotsServiceClass } from '@mocks/services'; -import { mockDataSource } from 'src/mocks/data-sources'; +import { MockedDataSource, mockDataSource } from 'src/mocks/data-sources'; import { TimeSlotsService } from 'src/timeslots/timeslots.service'; import { RecruitmentSessionState } from '@hkrecruitment/shared'; @@ -118,4 +118,85 @@ describe('Recruitment Session Service', () => { ); }); }); + + describe('findAllRecruitmentSessions', () => { + it('should return all recruitment sessions', async () => { + const recruitmentSessions = [mockRecruitmentSession]; + jest + .spyOn(mockedRepository, 'find') + .mockResolvedValue(recruitmentSessions); + const result = + await recruitmentSessionService.findAllRecruitmentSessions(); + expect(result).toEqual(recruitmentSessions); + expect(mockedRepository.find).toHaveBeenCalledTimes(1); + }); + + it('should return an empty array if there are no recruitment sessions', async () => { + jest.spyOn(mockedRepository, 'find').mockResolvedValue([]); + const result = + await recruitmentSessionService.findAllRecruitmentSessions(); + expect(result).toEqual([]); + expect(mockedRepository.find).toHaveBeenCalledTimes(1); + }); + }); + + describe('findRecruitmentSessionById', () => { + it('should return the recruitment session with the given ID', async () => { + jest + .spyOn(mockedRepository, 'findBy') + .mockResolvedValue([mockRecruitmentSession]); + const result = await recruitmentSessionService.findRecruitmentSessionById( + 1, + ); + expect(result).toEqual(mockRecruitmentSession); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + expect(mockedRepository.findBy).toHaveBeenCalledWith({ id: 1 }); + }); + + it('should return null if the recruitment session does not exist', async () => { + jest.spyOn(mockedRepository, 'findBy').mockResolvedValue([]); + const result = await recruitmentSessionService.findRecruitmentSessionById( + 1, + ); + expect(result).toBeNull(); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + expect(mockedRepository.findBy).toHaveBeenCalledWith({ id: 1 }); + }); + }); + + describe('findActiveRecruitmentSession', () => { + it('should return the active recruitment session', async () => { + jest + .spyOn(mockedRepository, 'findBy') + .mockResolvedValue([mockRecruitmentSession]); + const result = + await recruitmentSessionService.findActiveRecruitmentSession(); + expect(result).toEqual(mockRecruitmentSession); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + expect(mockedRepository.findBy).toHaveBeenCalledWith({ + state: RecruitmentSessionState.Active, + }); + }); + + it('should return null if there is no active recruitment session', async () => { + jest.spyOn(mockedRepository, 'findBy').mockResolvedValue([]); + const result = + await recruitmentSessionService.findActiveRecruitmentSession(); + expect(result).toBeNull(); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + expect(mockedRepository.findBy).toHaveBeenCalledWith({ + state: RecruitmentSessionState.Active, + }); + }); + }); + + describe('sessionHasPendingInterviews', () => { + it('should return error', async () => { + await recruitmentSessionService + .sessionHasPendingInterviews(mockRecruitmentSession) + .catch((error) => { + expect(error.message).toBe('Method not implemented.'); + }); + }); + }); }); diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index 32d6d8d..24af579 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -45,6 +45,20 @@ describe('TimeSlotsService', () => { expect(timeSlotService).toBeDefined(); }); + describe('countOverlappingTimeSlots', () => { + it('should return the number of overlapping time slots', async () => { + jest.spyOn(mockedRepository, 'count').mockResolvedValue(2); + const startDate = new Date('2022-01-01T09:00:00'); + const endDate = new Date('2022-01-01T10:00:00'); + const result = await timeSlotService.countOverlappingTimeSlots( + startDate, + endDate, + ); + expect(result).toBe(2); + expect(mockedRepository.count).toHaveBeenCalledTimes(1); + }); + }); + describe('deleteTimeSlot', () => { it('should remove the specified timeslot from the database', async () => { jest.spyOn(mockedRepository, 'remove').mockResolvedValue(mockTimeSlot); @@ -72,6 +86,13 @@ describe('TimeSlotsService', () => { expect(result).toEqual(mockTimeSlot); expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); }); + + it("should return null if the timeslot doesn't exist", async () => { + jest.spyOn(mockedRepository, 'findBy').mockResolvedValue([]); + const result = await timeSlotService.findById(mockTimeSlot.id); + expect(result).toBeNull(); + expect(mockedRepository.findBy).toHaveBeenCalledTimes(1); + }); }); describe('createTimeSlot', () => { @@ -269,6 +290,14 @@ describe('TimeSlotsService', () => { id: 1, recruitmentSession: 3, availabilities: [ + { + state: AvailabilityState.Interviewing, + user: { + role: Role.None, + is_board: false, + is_expert: true, + }, + }, { state: AvailabilityState.Free, user: { @@ -350,6 +379,52 @@ describe('TimeSlotsService', () => { ]), ); }); + + it("should return an empty array if there aren't any time slots with at least 2 available people", async () => { + const mockQueryBuilder = { + innerJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([ + { + start: new Date('2022-01-01T09:00:00'), + end: new Date('2022-01-01T10:00:00'), + id: 1, + recruitmentSession: 3, + availabilities: [ + { + state: AvailabilityState.Interviewing, + user: { + role: Role.None, + is_board: false, + is_expert: true, + }, + }, + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: false, + is_expert: true, + }, + }, + ], + }, + ]), + }; + + // Mock the timeSlotRepository and its methods + const mockTimeSlotRepository = { + createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder), + }; + + const timeSlotService = new TimeSlotsService( + mockTimeSlotRepository as any, + ); + + const result = await timeSlotService.findAvailableTimeSlots(); + expect(result).toEqual([]); + }); }); }); From 76bd43c32698c83d50f36509401df6b2ffd4e9ac Mon Sep 17 00:00:00 2001 From: white Date: Fri, 24 May 2024 02:02:23 +0200 Subject: [PATCH 49/57] fix: improve coverage --- api/src/availability/availability.service.ts | 5 +++++ sonar-project.properties | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/src/availability/availability.service.ts b/api/src/availability/availability.service.ts index 6caa4aa..c03e6af 100644 --- a/api/src/availability/availability.service.ts +++ b/api/src/availability/availability.service.ts @@ -36,6 +36,11 @@ export class AvailabilityService { return matches.length > 0 ? matches[0] : null; } + /** + * Find all availabilities for a given user + * @param user - User to find availabilities for + * @returns {Promise} - List of availabilities for the user + */ async findByUserAndTimeSlot(user: User, timeSlot: TimeSlot) { const matches = await this.availabilityRepository.findBy({ user: user as any, diff --git a/sonar-project.properties b/sonar-project.properties index 8ec4d92..57da252 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,4 +5,5 @@ sonar.projectName=HKrecruitment sonar.javascript.lcov.reportPaths=./coverage/shared/lcov.info, ./coverage/api/lcov.info, ./coverage/api-e2e/lcov.info sonar.sources=api/, frontend/, shared/ sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md -sonar.cpd.exclusions=**/mocks/**/* \ No newline at end of file +sonar.cpd.exclusions=**/mocks/**/* +sonar.exclusions**/mocks/**/*, =**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md \ No newline at end of file From 0e82629d7128fea767e6eb48933b322fec05a908 Mon Sep 17 00:00:00 2001 From: white Date: Fri, 24 May 2024 02:13:59 +0200 Subject: [PATCH 50/57] fix: improve coverage --- api/src/timeslots/timeslots.controller.spec.ts | 16 ++++++++++++++++ sonar-project.properties | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/api/src/timeslots/timeslots.controller.spec.ts b/api/src/timeslots/timeslots.controller.spec.ts index 2ab30d8..f67e9fb 100644 --- a/api/src/timeslots/timeslots.controller.spec.ts +++ b/api/src/timeslots/timeslots.controller.spec.ts @@ -20,6 +20,7 @@ describe('TimeSlotController', () => { expect(service).toBeDefined(); }); + describe('findAvailableTimeSlots', () => { it('should return an array of available time slots', async () => { const expectedTimeSlots: TimeSlot[] = [ @@ -46,5 +47,20 @@ describe('TimeSlotController', () => { expect(result).toEqual(expectedTimeSlots); expect(service.findAvailableTimeSlots).toHaveBeenCalledTimes(1); }); + + it("should return an empty array if there are no available time slots", async () => { + const expectedTimeSlots: TimeSlot[] = []; + + jest + .spyOn(service, 'findAvailableTimeSlots') + .mockResolvedValue(expectedTimeSlots); + + // Act + const result = await controller.findAvailableTimeSlots(); + + // Assert + expect(result).toEqual(expectedTimeSlots); + expect(service.findAvailableTimeSlots).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/sonar-project.properties b/sonar-project.properties index 57da252..8ec4d92 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,5 +5,4 @@ sonar.projectName=HKrecruitment sonar.javascript.lcov.reportPaths=./coverage/shared/lcov.info, ./coverage/api/lcov.info, ./coverage/api-e2e/lcov.info sonar.sources=api/, frontend/, shared/ sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md -sonar.cpd.exclusions=**/mocks/**/* -sonar.exclusions**/mocks/**/*, =**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md \ No newline at end of file +sonar.cpd.exclusions=**/mocks/**/* \ No newline at end of file From defd47b911faa30ab30a0dc6560256b9968cc195 Mon Sep 17 00:00:00 2001 From: white Date: Fri, 24 May 2024 02:16:57 +0200 Subject: [PATCH 51/57] fix: improve coverage - format --- api/src/timeslots/timeslots.controller.spec.ts | 3 +-- sonar-project.properties | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/timeslots/timeslots.controller.spec.ts b/api/src/timeslots/timeslots.controller.spec.ts index f67e9fb..f39ddcd 100644 --- a/api/src/timeslots/timeslots.controller.spec.ts +++ b/api/src/timeslots/timeslots.controller.spec.ts @@ -20,7 +20,6 @@ describe('TimeSlotController', () => { expect(service).toBeDefined(); }); - describe('findAvailableTimeSlots', () => { it('should return an array of available time slots', async () => { const expectedTimeSlots: TimeSlot[] = [ @@ -48,7 +47,7 @@ describe('TimeSlotController', () => { expect(service.findAvailableTimeSlots).toHaveBeenCalledTimes(1); }); - it("should return an empty array if there are no available time slots", async () => { + it('should return an empty array if there are no available time slots', async () => { const expectedTimeSlots: TimeSlot[] = []; jest diff --git a/sonar-project.properties b/sonar-project.properties index 8ec4d92..a2f1d75 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,4 +5,5 @@ sonar.projectName=HKrecruitment sonar.javascript.lcov.reportPaths=./coverage/shared/lcov.info, ./coverage/api/lcov.info, ./coverage/api-e2e/lcov.info sonar.sources=api/, frontend/, shared/ sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md -sonar.cpd.exclusions=**/mocks/**/* \ No newline at end of file +sonar.cpd.exclusions=**/mocks/**/* +sonar.ex \ No newline at end of file From dad499284c554f65f6a2aa631596fa4cd8f90e5b Mon Sep 17 00:00:00 2001 From: white Date: Sat, 25 May 2024 19:56:07 +0200 Subject: [PATCH 52/57] fix: minor --- api/src/mocks/data.ts | 75 +++++++++++++++++++ api/src/timeslots/timeslots.service.spec.ts | 82 ++------------------- api/src/timeslots/timeslots.service.ts | 35 ++++----- 3 files changed, 96 insertions(+), 96 deletions(-) diff --git a/api/src/mocks/data.ts b/api/src/mocks/data.ts index c4c0bd3..9c7ea0a 100644 --- a/api/src/mocks/data.ts +++ b/api/src/mocks/data.ts @@ -229,3 +229,78 @@ export const mockAvailability = { export const mockCreateAvailabilityDto = { timeSlotId: mockTimeSlot.id, } as CreateAvailabilityDto; + +export const mockTimeSlotsJoined = [ + { + start: new Date('2022-01-01T09:00:00'), + end: new Date('2022-01-01T10:00:00'), + id: 1, + recruitmentSession: 3, + availabilities: [ + { + state: AvailabilityState.Interviewing, + user: { + role: Role.None, + is_board: false, + is_expert: true, + }, + }, + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: true, + is_expert: false, + }, + }, + { + state: AvailabilityState.Free, + user: { + role: Role.Applicant, + is_board: false, + is_expert: false, + }, + }, + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: false, + is_expert: true, + }, + }, + { + state: AvailabilityState.Interviewing, + user: { + role: Role.Member, + is_board: false, + is_expert: true, + }, + }, + { + state: AvailabilityState.Interviewing, + user: { + role: Role.Admin, + is_board: false, + is_expert: true, + }, + }, + ], + }, + { + start: new Date('2022-01-01T9:00:00'), + end: new Date('2022-01-01T20:00:00'), + id: 2, + recruitmentSession: 3, + availabilities: [ + { + state: AvailabilityState.Free, + user: { + role: Role.Member, + is_board: true, + is_expert: true, + }, + }, + ], + }, +]; diff --git a/api/src/timeslots/timeslots.service.spec.ts b/api/src/timeslots/timeslots.service.spec.ts index 24af579..1c2e7e1 100644 --- a/api/src/timeslots/timeslots.service.spec.ts +++ b/api/src/timeslots/timeslots.service.spec.ts @@ -1,4 +1,9 @@ -import { mockGenerateTimeSlots, mockTimeSlot, testDate } from 'src/mocks/data'; +import { + mockGenerateTimeSlots, + mockTimeSlot, + mockTimeSlotsJoined, + testDate, +} from 'src/mocks/data'; import { mockedRepository } from 'src/mocks/repositories'; import { TestingModule, Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; @@ -283,80 +288,7 @@ describe('TimeSlotsService', () => { innerJoinAndSelect: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), - getMany: jest.fn().mockResolvedValue([ - { - start: new Date('2022-01-01T09:00:00'), - end: new Date('2022-01-01T10:00:00'), - id: 1, - recruitmentSession: 3, - availabilities: [ - { - state: AvailabilityState.Interviewing, - user: { - role: Role.None, - is_board: false, - is_expert: true, - }, - }, - { - state: AvailabilityState.Free, - user: { - role: Role.Member, - is_board: true, - is_expert: false, - }, - }, - { - state: AvailabilityState.Free, - user: { - role: Role.Applicant, - is_board: false, - is_expert: false, - }, - }, - { - state: AvailabilityState.Free, - user: { - role: Role.Member, - is_board: false, - is_expert: true, - }, - }, - { - state: AvailabilityState.Interviewing, - user: { - role: Role.Member, - is_board: false, - is_expert: true, - }, - }, - { - state: AvailabilityState.Interviewing, - user: { - role: Role.Admin, - is_board: false, - is_expert: true, - }, - }, - ], - }, - { - start: new Date('2022-01-01T9:00:00'), - end: new Date('2022-01-01T20:00:00'), - id: 2, - recruitmentSession: 3, - availabilities: [ - { - state: AvailabilityState.Free, - user: { - role: Role.Member, - is_board: true, - is_expert: true, - }, - }, - ], - }, - ]), + getMany: jest.fn().mockResolvedValue(mockTimeSlotsJoined), }; // Mock the timeSlotRepository and its methods diff --git a/api/src/timeslots/timeslots.service.ts b/api/src/timeslots/timeslots.service.ts index 075ba9c..729d8e7 100644 --- a/api/src/timeslots/timeslots.service.ts +++ b/api/src/timeslots/timeslots.service.ts @@ -185,30 +185,23 @@ export class TimeSlotsService { const allMatches = await queryBuilder.getMany(); - let goodTimeSlots: TimeSlot[] = []; - allMatches.forEach((timeSlot) => { - let boardMembers = 0; - let expertMembers = 0; - for (let availability of timeSlot.availabilities) { - // redundant checks - if (availability.state !== AvailabilityState.Free) continue; - if (availability.user.role === Role.None) continue; - if (availability.user.role === Role.Applicant) continue; - - if (availability.user.is_board) ++boardMembers; - else if (availability.user.is_expert) ++expertMembers; - } - - if ((boardMembers && expertMembers) || boardMembers > 1) { - const timeslotToPush = { + return allMatches + .filter((timeSlot) => { + let [boardMembers, expertMembers] = [0, 0]; + for (let av of timeSlot.availabilities) { + if (av.state !== AvailabilityState.Free) continue; + if ([Role.None, Role.Applicant].includes(av.user.role)) continue; + if (av.user.is_board) ++boardMembers; + else if (av.user.is_expert) ++expertMembers; + } + return (boardMembers && expertMembers) || boardMembers > 1; + }) + .map((timeSlot) => { + return { id: timeSlot.id, start: timeSlot.start, end: timeSlot.end, } as TimeSlot; - goodTimeSlots.push(timeslotToPush); - } - }); - - return goodTimeSlots; + }); } } From f2f4a4ada97d359311bcd1dabc3dffd8d3de3e8a Mon Sep 17 00:00:00 2001 From: white Date: Sat, 25 May 2024 20:01:02 +0200 Subject: [PATCH 53/57] fix: formatting comments --- .../recruitment-session.service.ts | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/api/src/recruitment-session/recruitment-session.service.ts b/api/src/recruitment-session/recruitment-session.service.ts index 8a982f2..525dbef 100644 --- a/api/src/recruitment-session/recruitment-session.service.ts +++ b/api/src/recruitment-session/recruitment-session.service.ts @@ -17,11 +17,6 @@ export class RecruitmentSessionService { private dataSource: DataSource, ) {} - /** - * Create a recruitment session - * @param recruitmentSession - Recruitment session to create - * @returns {Promise} - Created recruitment session - */ async createRecruitmentSession( recruitmentSession: CreateRecruitmentSessionDto, ): Promise { @@ -45,19 +40,10 @@ export class RecruitmentSessionService { }); } - /** - * List all recruitment sessions - * @returns {Promise} - List of recruitment sessions - */ async findAllRecruitmentSessions(): Promise { return await this.recruitmentSessionRepository.find(); } - /** - * Find a recruitment session by its ID - * @param RSid - ID of the recruitment session - * @returns {Promise} - Recruitment session with the given ID - */ async findRecruitmentSessionById( RSid: number, ): Promise { @@ -67,10 +53,6 @@ export class RecruitmentSessionService { return matches.length > 0 ? matches[0] : null; } - /** - * Find an active recruitment session - * @returns {Promise} - Active recruitment session - */ async findActiveRecruitmentSession(): Promise { const matches = await this.recruitmentSessionRepository.findBy({ state: RecruitmentSessionState.Active, @@ -78,11 +60,6 @@ export class RecruitmentSessionService { return matches.length > 0 ? matches[0] : null; } - /** - * Delete a recruitment session - * @param recruitmentSession - Recruitment session to delete - * @returns {Promise} - Deleted recruitment session - */ async deleteRecruitmentSession( recruitmentSession: RecruitmentSession, ): Promise { @@ -100,11 +77,6 @@ export class RecruitmentSessionService { return await this.recruitmentSessionRepository.save(recruitmentSession); } - /** - * Check if a recruitment session has pending interviews - * @param recruitmentSession - Recruitment session to check - * @returns {Promise} - True if the recruitment session has pending interviews, false otherwise - */ async sessionHasPendingInterviews( recruitmentSession: RecruitmentSession, ): Promise { From 87a2eb16605cb76c54a181f1789298c276f81fce Mon Sep 17 00:00:00 2001 From: white Date: Sat, 25 May 2024 20:02:43 +0200 Subject: [PATCH 54/57] fix: formatting comments --- api/src/recruitment-session/recruitment-session.service.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/src/recruitment-session/recruitment-session.service.ts b/api/src/recruitment-session/recruitment-session.service.ts index 525dbef..670a9e6 100644 --- a/api/src/recruitment-session/recruitment-session.service.ts +++ b/api/src/recruitment-session/recruitment-session.service.ts @@ -66,11 +66,6 @@ export class RecruitmentSessionService { return await this.recruitmentSessionRepository.remove(recruitmentSession); } - /** - * Update a recruitment session - * @param recruitmentSession - Recruitment session to update - * @returns {Promise} - Updated recruitment session - */ async updateRecruitmentSession( recruitmentSession: RecruitmentSession, ): Promise { From 6dd26b60fc1526cd1b58f2455a4c0e3ed99e733c Mon Sep 17 00:00:00 2001 From: white Date: Sat, 25 May 2024 20:06:12 +0200 Subject: [PATCH 55/57] fix: formatting comments --- sonar-project.properties | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index a2f1d75..a288a22 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,6 +4,5 @@ sonar.projectName=HKrecruitment sonar.javascript.lcov.reportPaths=./coverage/shared/lcov.info, ./coverage/api/lcov.info, ./coverage/api-e2e/lcov.info sonar.sources=api/, frontend/, shared/ -sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md -sonar.cpd.exclusions=**/mocks/**/* -sonar.ex \ No newline at end of file +sonar.coverage.exclusions=**/node_modules/**/*, **/*.spec.*, **/*.e2e-spec.*, **/documentation/**/*, **/frontend/**/*, **/test/**/*, **/tests/**/*, **/*.json, **/*.yaml, **/*.yml, **/*.md, **/mocks/**/* +sonar.cpd.exclusions=**/mocks/**/* \ No newline at end of file From 1afe26942d63881dc9529c4ab476b5a523dabfc9 Mon Sep 17 00:00:00 2001 From: white Date: Sat, 25 May 2024 20:06:56 +0200 Subject: [PATCH 56/57] fix: formatting comments --- api/src/users/users.service.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/src/users/users.service.ts b/api/src/users/users.service.ts index 8eb1fd8..e85a453 100644 --- a/api/src/users/users.service.ts +++ b/api/src/users/users.service.ts @@ -28,11 +28,6 @@ export class UsersService { return this.userRepository.findOne({ where: { oauthId } }); } - /** - * Delete a user - * @param {User} user - User to delete - * @returns {Promise} - Deleted user - */ async delete(user: User): Promise { return this.userRepository.remove(user); } From c9c30fbd83e2bb018da3dd6c6919f8ad1217d029 Mon Sep 17 00:00:00 2001 From: white Date: Thu, 30 May 2024 11:29:30 +0200 Subject: [PATCH 57/57] feat: check ability --- api/src/timeslots/timeslots.controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/src/timeslots/timeslots.controller.ts b/api/src/timeslots/timeslots.controller.ts index 90c5b84..6a3af4a 100644 --- a/api/src/timeslots/timeslots.controller.ts +++ b/api/src/timeslots/timeslots.controller.ts @@ -6,6 +6,8 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { TimeSlot } from './timeslot.entity'; +import { CheckPolicies } from 'src/authorization/check-policies.decorator'; +import { Action } from '@hkrecruitment/shared'; @ApiBearerAuth() @ApiTags('timeslots') @@ -14,6 +16,7 @@ export class TimeSlotsController { constructor(private readonly timeSlotsService: TimeSlotsService) {} @ApiUnauthorizedResponse() + @CheckPolicies((ability) => ability.can(Action.Read, 'TimeSlot')) @Get() async findAvailableTimeSlots(): Promise { return await this.timeSlotsService.findAvailableTimeSlots();