Skip to content

Commit

Permalink
Availability: controller, service, entity, tests (#21)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: Marco De Luca <[email protected]>
Co-authored-by: whiteOFF <[email protected]>
Co-authored-by: Vincenzo Pellegrini <[email protected]>
Co-authored-by: Mugna0990 <[email protected]>
  • Loading branch information
6 people committed Apr 12, 2024
1 parent 9f1badb commit 215406e
Show file tree
Hide file tree
Showing 41 changed files with 1,374 additions and 528 deletions.
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -34,6 +35,7 @@ import { AuthorizationGuard } from './authorization/authorization.guard';
ApplicationsModule,
AuthenticationModule,
AuthorizationModule,
AvailabilityModule,
RecruitmentSessionModule,
TimeSlotsModule,
UsersModule,
Expand Down
2 changes: 1 addition & 1 deletion api/src/application/applications.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
mockMscApplication,
updateApplicationDTO,
testDate,
} from '@mocks/data';
} from 'src/mocks/data';
import {
BadRequestException,
ConflictException,
Expand Down
8 changes: 4 additions & 4 deletions api/src/application/applications.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -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:
Expand Down
209 changes: 209 additions & 0 deletions api/src/availability/availability.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -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<AuthenticatedRequest>();
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<AuthenticatedRequest>();
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<AuthenticatedRequest>();
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<AuthenticatedRequest>();
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<AuthenticatedRequest>();
mockRequest.user.sub = '123';
return mockRequest;
}
Loading

0 comments on commit 215406e

Please sign in to comment.