Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server
1 result
Show changes
Showing
with 1412 additions and 55 deletions
import { HttpModule } from '@nestjs/common';
import { HttpModule, HttpStatus } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { Types } from 'mongoose';
import { ConfigurationModule } from '../../configuration/configuration.module';
import { CreateJobDto } from '../dto/create-job.dto';
import { Job } from '../schemas/job.schema';
import { User } from '../schemas/user.schema';
import { JobsService } from '../services/jobs.service';
import { UsersService } from '../services/users.service';
import { JobsController } from './jobs.controller';
describe('JobsController', () => {
......@@ -15,6 +17,19 @@ describe('JobsController', () => {
findAll: jest.fn(),
findByName: jest.fn(),
create: jest.fn(),
findOne: jest.fn(),
deleteOneId: jest.fn(),
findAllUnvalidated: jest.fn(),
createJobFromAdmin: jest.fn(),
validate: jest.fn(),
update: jest.fn(),
mergeJob: jest.fn(),
deleteInvalidJob: jest.fn(),
};
const userServiceMock = {
populateJobswithUsers: jest.fn(),
isJobLinkedtoUser: jest.fn(),
};
beforeEach(async () => {
......@@ -25,10 +40,18 @@ describe('JobsController', () => {
provide: JobsService,
useValue: jobServiceMock,
},
{
provide: UsersService,
useValue: userServiceMock,
},
{
provide: getModelToken('Job'),
useValue: Job,
},
{
provide: getModelToken('User'),
useValue: User,
},
],
controllers: [JobsController],
}).compile();
......@@ -65,6 +88,7 @@ describe('JobsController', () => {
it('should create job `Dev`', async () => {
const newJob: CreateJobDto = {
name: 'Dev',
hasPersonalOffer: false,
};
const newCreatedJob: Job = {
name: 'Dev',
......@@ -73,12 +97,14 @@ describe('JobsController', () => {
};
jobServiceMock.findByName.mockResolvedValueOnce(null);
jobServiceMock.create.mockResolvedValueOnce(newCreatedJob);
const createReply = await controller.createJob(newJob);
const req = { user: { _id: '6036721022462b001334c4bb' } };
const createReply = await controller.createJob(req, newJob);
expect(createReply).toEqual(newCreatedJob);
});
it('should throw error on already existing job `Dev`', async () => {
const newJob: CreateJobDto = {
name: 'Dev',
hasPersonalOffer: false,
};
jobServiceMock.findByName.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'),
......@@ -87,7 +113,8 @@ describe('JobsController', () => {
hasPersonalOffer: false,
});
try {
await controller.createJob(newJob);
const req = { user: { _id: '6036721022462b001334c4bb' } };
await controller.createJob(req, newJob);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Job already exist');
......@@ -95,4 +122,159 @@ describe('JobsController', () => {
}
});
});
describe('Create Job', () => {
it('should create a job', async () => {
jobServiceMock.findByName.mockResolvedValueOnce(null);
jobServiceMock.create.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
});
const req = { user: { _id: '6036721022462b001334c4bb', role: 0 } };
const reply = await controller.createJob(req, { name: 'Dev', hasPersonalOffer: true });
expect(reply).toBeTruthy();
});
it('should not create if job already exists', async () => {
jobServiceMock.findByName.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
});
const req = { user: { _id: '6036721022462b001334c4bb', role: 0 } };
try {
await controller.createJob(req, { name: 'Dev', hasPersonalOffer: true });
} catch (e) {
expect(e.message).toBe('Job already exist');
expect(e.status).toBe(HttpStatus.UNPROCESSABLE_ENTITY);
}
});
it('should call create with send notification to true if admin', async () => {
jobServiceMock.findByName.mockResolvedValueOnce(null);
jobServiceMock.create.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
});
const req = { user: { _id: '6036721022462b001334c4bb', role: 1 } };
const job = { name: 'Dev', hasPersonalOffer: true };
const reply = await controller.createJob(req, job);
expect(reply).toBeTruthy();
expect(jobServiceMock.create).toHaveBeenCalledWith(job, true, true, false);
});
});
describe('Validate Job', () => {
it('should validate a given job', async () => {
jobServiceMock.validate.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
});
expect(await controller.validateJob({ id: '6231aefe76598527c8d0b5bc' })).toBeTruthy();
});
});
describe('Get Jobs', () => {
it('should call all validated jobs and populate them with users attached to it', async () => {
const spyer = jest.spyOn(userServiceMock, 'populateJobswithUsers');
jobServiceMock.findAll.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Dev',
validated: true,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Scrum',
validated: true,
},
]);
await controller.findValidatedJobs();
expect(jobServiceMock.findAll.mock.calls.length).toBe(2);
expect(spyer.mock.calls.length).toBe(1);
});
it('should call all unvalidated jobs and populate them with users attached to it', async () => {
const spyer = jest.spyOn(userServiceMock, 'populateJobswithUsers');
jobServiceMock.findAllUnvalidated.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Dev',
validated: false,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Scrum',
validated: false,
},
]);
await controller.findUnvalidatedJobs();
expect(jobServiceMock.findAllUnvalidated.mock.calls.length).toBe(1);
expect(spyer.mock.calls.length).toBe(2);
});
});
describe('Edit Job', () => {
it('should update job', async () => {
jobServiceMock.update.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'DevMod',
validated: true,
});
expect(
await controller.updateJob('6231aefe76598527c8d0b5bc', { name: 'DevMod', hasPersonalOffer: false })
).toBeTruthy();
});
it('should delete an unvalidated job and replace all its occurence with a chosen validated job', async () => {
jobServiceMock.mergeJob.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
});
jobServiceMock.deleteInvalidJob.mockResolvedValueOnce({
n: 1,
ok: 1,
deletedCount: 1,
});
const reply = await controller.mergeJob({
sourceJobId: '6231aefe76598527c8d0b5ba',
targetJobId: '6231aefe76598527c8d0b5bc',
});
expect(reply).toBeTruthy();
});
});
describe('Delete Job', () => {
jobServiceMock.deleteOneId.mockResolvedValueOnce({
n: 1,
ok: 1,
deletedCount: 1,
});
it('should delete job', async () => {
jobServiceMock.findOne.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
});
userServiceMock.isJobLinkedtoUser.mockResolvedValueOnce(false);
const reply = await controller.deleteJob({ id: '6231aefe76598527c8d0b5ba' });
expect(reply).toBeTruthy();
});
it('should not delete job if a user is linked', async () => {
jobServiceMock.findOne.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Dev',
validated: true,
});
userServiceMock.isJobLinkedtoUser.mockResolvedValueOnce(true);
try {
await controller.deleteJob({ id: '6231aefe76598527c8d0b5bc' });
expect(true).toBe(false);
} catch (e) {
expect(e.message).toEqual('Cannot delete job. It has user(s) attached to it.');
expect(e.status).toEqual(HttpStatus.FORBIDDEN);
}
});
});
});
import { Body, Controller, Get, HttpException, HttpStatus, Logger, Post } from '@nestjs/common';
import { ApiParam } from '@nestjs/swagger';
import { CreateEmployerDto } from '../dto/create-employer.dto';
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Logger,
Param,
Post,
Put,
Request,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam } from '@nestjs/swagger';
import { MergeJobDto } from '../../admin/dto/merge-job.dto';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { hasAdminRole } from '../../shared/utils';
import { Roles } from '../decorators/roles.decorator';
import { CreateJobDto } from '../dto/create-job.dto';
import { RolesGuard } from '../guards/roles.guard';
import { IJob } from '../interfaces/job.interface';
import { Job } from '../schemas/job.schema';
import { JobsService } from '../services/jobs.service';
import { UsersService } from '../services/users.service';
@Controller('jobs')
export class JobsController {
private readonly logger = new Logger(JobsController.name);
constructor(private jobsService: JobsService) {}
constructor(private jobsService: JobsService, private usersService: UsersService) {}
/**
* Return every jobs
......@@ -22,18 +41,95 @@ export class JobsController {
/**
* Create a new job
* @param job {CreateEmployerDto}
* @param job {CreateJobDto}
* @returns {Job}
*/
@Post()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT')
@ApiParam({ name: 'job', type: CreateJobDto, required: true })
public async createJob(@Body() job: CreateEmployerDto): Promise<Job> {
public async createJob(@Request() req, @Body() job: CreateJobDto): Promise<Job> {
this.logger.debug(`createJob: ${job.name}`);
const existingJob = await this.jobsService.findByName(job.name);
if (existingJob) {
this.logger.warn(`Job already exist: ${job.name}`);
throw new HttpException('Job already exist', HttpStatus.UNPROCESSABLE_ENTITY);
}
// if user is admin, do not send notification
if (hasAdminRole(req.user)) {
return this.jobsService.create(job, true, job.hasPersonalOffer, false);
}
return this.jobsService.create(job);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Get('validated')
@ApiOperation({ description: 'Get validated jobs populated with users attached to it' })
public async findValidatedJobs() {
return this.jobsService.findAll().then(async (jobs: IJob[]) => {
return this.usersService.populateJobswithUsers(jobs);
});
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Get('unvalidated')
@ApiOperation({ description: 'Get unvalidated jobs populated with users attached to it' })
public async findUnvalidatedJobs() {
return this.jobsService.findAllUnvalidated().then(async (jobs: IJob[]) => {
return this.usersService.populateJobswithUsers(jobs);
});
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Post('validate/:id')
@ApiParam({ name: 'id', type: String, required: true })
@ApiOperation({ description: 'Validate job' })
public async validateJob(@Param() params) {
return this.jobsService.validate(params.id);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Put('merge')
public async mergeJob(@Body() mergeJob: MergeJobDto) {
this.logger.debug('mergeJob');
return this.jobsService.mergeJob(mergeJob);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Put(':id')
public async updateJob(@Param('id') id: string, @Body() body: CreateJobDto) {
this.logger.debug('updateJob');
return this.jobsService.update(id, body);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
@ApiBearerAuth('JWT')
@ApiParam({ name: 'id', type: String, required: true })
public async deleteJob(@Param() params) {
// look for job
const researchedJob = await this.jobsService.findOne(params.id);
// look for any relations within user collection, reject action if found
if (researchedJob !== null) {
const isJobLinked = await this.usersService.isJobLinkedtoUser(researchedJob._id);
if (!isJobLinked) {
return this.jobsService.deleteOneId(params.id);
} else {
throw new HttpException('Cannot delete job. It has user(s) attached to it.', HttpStatus.FORBIDDEN);
}
} else {
throw new HttpException('Job does not exists', HttpStatus.NOT_FOUND);
}
}
}
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateEmployerDto {
@ApiProperty({ type: String, example: 'PIMMS Vaise' })
......
import { IsNotEmpty, IsString } from 'class-validator';
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateJobDto {
......@@ -6,4 +6,8 @@ export class CreateJobDto {
@IsNotEmpty()
@IsString()
readonly name: string;
@ApiProperty({ type: Boolean, example: false })
@IsNotEmpty()
@IsBoolean()
readonly hasPersonalOffer: boolean;
}
import { Document } from 'mongoose';
import { Employer } from '../schemas/employer.schema';
export type IEmployer = Employer & Document;
import { Document } from 'mongoose';
import { Job } from '../schemas/job.schema';
export type IJob = Job & Document;
import { HttpModule, HttpStatus } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { AxiosResponse } from 'axios';
import { Types } from 'mongoose';
import { ConfigurationModule } from '../../configuration/configuration.module';
import { MailerService } from '../../mailer/mailer.service';
import { CreateEmployerDto } from '../dto/create-employer.dto';
import { EmployerDocument } from '../schemas/employer.schema';
import { User } from '../schemas/user.schema';
import { EmployerSearchService } from './employer-search.service';
import { EmployerService } from './employer.service';
import { UsersService } from './users.service';
describe('EmployerService', () => {
let service: EmployerService;
let mailer: MailerService;
const mockEmployerSearchService = {
indexEmployer: jest.fn(),
......@@ -20,13 +26,18 @@ describe('EmployerService', () => {
const mockEmployerModel = {
create: jest.fn(),
findOne: jest.fn(),
findById: jest.fn(),
deleteOne: jest.fn(),
find: jest.fn(() => mockEmployerModel),
sort: jest.fn(() => mockEmployerModel),
};
const mockUserService = {
replaceEmployers: jest.fn(),
getAdmins: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigurationModule],
imports: [ConfigurationModule, HttpModule],
providers: [
EmployerService,
{
......@@ -37,9 +48,19 @@ describe('EmployerService', () => {
provide: getModelToken('Employer'),
useValue: mockEmployerModel,
},
{
provide: UsersService,
useValue: mockUserService,
},
{
provide: getModelToken('User'),
useValue: User,
},
MailerService,
],
}).compile();
service = module.get<EmployerService>(EmployerService);
mailer = module.get<MailerService>(MailerService);
});
it('should be defined', () => {
......@@ -62,35 +83,118 @@ describe('EmployerService', () => {
const reply = await service.findAll();
expect(reply.length).toBe(2);
});
it('findByName', async () => {
mockEmployerModel.findOne.mockResolvedValue({
it('findOne', async () => {
mockEmployerModel.findById.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: true,
});
const reply = await service.findByName('Sopra');
const reply = await service.findOne('6231aefe76598527c8d0b5bc');
expect(reply).toBeTruthy();
});
it('findAllValidated', async () => {
mockEmployerModel.find.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: true,
},
]);
const reply = await service.findAllValidated();
expect(reply.length).toBe(1);
});
it('create', async () => {
mockEmployerModel.create.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: true,
});
it('findAllUnvalidated', async () => {
mockEmployerModel.find.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: false,
},
]);
const reply = await service.findAllValidated();
expect(reply.length).toBe(1);
});
it('finds all unvalidated employers', async () => {
mockEmployerModel.find.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Metro',
validated: false,
},
]);
const reply = await service.findAllUnvalidated();
expect(reply[0].validated).toBeFalsy;
});
it('finds all validated employers', async () => {
mockEmployerModel.find.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Metro',
validated: true,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5b2'),
name: 'Sopra',
validated: true,
},
]);
const reply = await service.findAllUnvalidated();
expect(reply.length).toBe(2);
});
it('findByName', async () => {
mockEmployerModel.findOne.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: true,
});
const createJob: CreateEmployerDto = {
name: 'Sopra',
};
const reply = await service.create(createJob);
const reply = await service.findByName('Sopra');
expect(reply).toBeTruthy();
});
describe('createEmployer', () => {
it('create', async () => {
mockEmployerModel.create.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: false,
});
mockEmployerModel.findOne.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: false,
});
const createEmployer: CreateEmployerDto = {
name: 'Sopra',
};
jest.spyOn(service, 'sendAdminCreateNotification').mockResolvedValueOnce();
const reply = await service.create(createEmployer);
expect(reply).toBeTruthy();
expect(service.sendAdminCreateNotification).toBeCalledTimes(1);
});
it('should create validated employer and not send email to admins', async () => {
mockEmployerModel.create.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: false,
});
mockEmployerModel.findOne.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: false,
});
const createEmployer: CreateEmployerDto = {
name: 'Sopra',
};
const reply = await service.create(createEmployer, true, false);
expect(reply).toBeTruthy();
});
});
it('delete', async () => {
mockEmployerModel.findOne.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
......@@ -145,4 +249,218 @@ describe('EmployerService', () => {
expect(reply.length).toBe(2);
});
});
describe('mergeEmployer', () => {
it('should delete source employer', async () => {
const reply = {
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e61'),
name: 'Metro',
validated: true,
};
mockEmployerModel.findById
.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Sopra',
validated: false,
})
.mockResolvedValueOnce(reply);
jest.spyOn(service, 'deleteInvalidEmployer').mockRejectedValueOnce({});
mockUserService.replaceEmployers.mockResolvedValueOnce(null);
expect(
await service.mergeEmployer({
sourceEmployerId: '623aed68c5d45b6fbbaa7e60',
targetEmployerId: '623aed68c5d45b6fbbaa7e61',
})
).toEqual(reply);
});
it('should delete source employer', async () => {
mockEmployerModel.findById.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
jest.spyOn(service, 'deleteInvalidEmployer').mockRejectedValueOnce({});
mockUserService.replaceEmployers.mockResolvedValueOnce(null);
try {
await service.mergeEmployer({
sourceEmployerId: '623aed68c5d45b6fbbaa7e60',
targetEmployerId: '623aed68c5d45b6fbbaa7e61',
});
} catch (e) {
expect(e.message).toBe('Cannot operate on employer.');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
});
describe('sendAdminCreateNotification', () => {
it('should sendAdminCreateNotification', async () => {
const employer = {
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Metro',
validated: true,
};
const result: AxiosResponse = {
data: {
status: 200,
content: {
success: true,
response: [7010],
},
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
};
jest.spyOn(mailer, 'send').mockResolvedValueOnce(result);
const spyer = jest.spyOn(mailer, 'send');
mockUserService.getAdmins.mockResolvedValueOnce([
{
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
},
]);
await service.sendAdminCreateNotification(
employer as EmployerDocument,
'adminEmployerCreate.ejs',
'adminEmployerCreate.json'
);
expect(spyer.mock.calls.length).toBe(1);
});
});
describe('validate', () => {
it('should validate employer', async () => {
mockEmployerModel.findById.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Sopra',
validated: false,
save: jest.fn().mockResolvedValueOnce(null),
});
const employer = await service.validate('623aed68c5d45b6fbbaa7e60');
expect(employer.validated).toBe(true);
});
it('should throw exception', async () => {
mockEmployerModel.findById.mockResolvedValueOnce(null);
try {
await service.validate('623aed68c5d45b6fbbaa7e60');
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot validate employer. It might have been already validate');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
});
describe('update', () => {
it('should update employer', async () => {
mockEmployerModel.findById.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Sopraaa',
validated: false,
save: jest.fn().mockResolvedValueOnce(null),
});
const employer = await service.update('623aed68c5d45b6fbbaa7e60', { name: 'Sopra' });
expect(employer.name).toBe('Sopra');
});
it('should throw exception', async () => {
mockEmployerModel.findById.mockResolvedValueOnce(null);
try {
await service.update('623aed68c5d45b6fbbaa7e60', { name: 'Sopra' });
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot edit employer. It was not found in database.');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
});
describe('deleteInvalidEmployer', () => {
it('should delete invalid employer', async () => {
mockEmployerModel.findById.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Sopra',
validated: false,
save: jest.fn().mockResolvedValueOnce(null),
});
mockEmployerSearchService.deleteIndex.mockResolvedValueOnce(null);
mockEmployerModel.deleteOne.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Sopra',
validated: false,
save: jest.fn().mockResolvedValueOnce(null),
});
await service.deleteInvalidEmployer('6231aefe76598527c8d0b5bc');
expect(mockEmployerSearchService.deleteIndex).toBeCalled();
expect(mockEmployerModel.deleteOne).toBeCalled();
});
});
describe('deleteOneId', () => {
it('should delete ', async () => {
mockEmployerModel.findOne.mockResolvedValueOnce({
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
deleteOne: jest.fn().mockResolvedValueOnce({
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
}),
});
expect(await service.deleteOneId('6231aefe76598527c8d0b5bc')).toStrictEqual({
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
});
});
it('should throw an error ', async () => {
mockEmployerModel.findOne.mockResolvedValueOnce(null);
try {
expect(await service.deleteOneId('6231aefe76598527c8d0b5bc')).toStrictEqual({
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
});
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Invalid employer id');
expect(e.status).toBe(HttpStatus.BAD_REQUEST);
}
});
});
});
import { Injectable, Logger } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Query } from 'mongoose';
import * as ejs from 'ejs';
import { Model, Query, Types } from 'mongoose';
import { MergeEmployerDto } from '../../admin/dto/merge-employer.dto';
import { MailerService } from '../../mailer/mailer.service';
import { CreateEmployerDto } from '../dto/create-employer.dto';
import { IEmployer } from '../interfaces/employer.interface';
import { Employer, EmployerDocument } from '../schemas/employer.schema';
import { EmployerSearchService } from './employer-search.service';
import { UsersService } from './users.service';
@Injectable()
export class EmployerService {
......@@ -11,6 +16,8 @@ export class EmployerService {
constructor(
@InjectModel(Employer.name) private employerModel: Model<EmployerDocument>,
private readonly userService: UsersService,
private readonly mailerService: MailerService,
private readonly employerSearchService: EmployerSearchService
) {}
......@@ -19,19 +26,124 @@ export class EmployerService {
return this.employerModel.find().sort({ name: 1 });
}
public async findOne(idParam: string): Promise<EmployerDocument> {
this.logger.debug('findOne');
return await this.employerModel.findById(Types.ObjectId(idParam));
}
public async findAllUnvalidated(): Promise<Employer[]> {
this.logger.debug('findAllUnvalidated');
return this.employerModel.find({ validated: false });
}
public async findAllValidated(): Promise<IEmployer[]> {
this.logger.debug(`findAllValidated`);
return this.employerModel.find({ validated: true });
}
public async findByName(name: string): Promise<EmployerDocument> {
this.logger.debug('findByName');
return this.employerModel.findOne({ name });
}
public async create(employer: CreateEmployerDto, validated = false): Promise<Employer> {
public async create(employer: CreateEmployerDto, validated = false, sendAdminNotification = true): Promise<Employer> {
this.logger.debug(`createEmployer: ${employer.name}`);
await this.employerModel.create({ name: employer.name, validated: validated });
await this.employerModel.create({ name: employer.name, validated });
const document = await this.findByName(employer.name);
this.employerSearchService.indexEmployer(document);
if (sendAdminNotification) {
this.sendAdminCreateNotification(
document,
this.mailerService.config.templates.adminEmployerCreate.ejs,
this.mailerService.config.templates.adminEmployerCreate.json
);
}
return document;
}
public async sendAdminCreateNotification(
employer: EmployerDocument,
templateLocation: any,
jsonConfigLocation: any
): Promise<void> {
const config = this.mailerService.config;
const ejsPath = this.mailerService.getTemplateLocation(templateLocation);
const jsonConfig = this.mailerService.loadJsonConfig(jsonConfigLocation);
const html = await ejs.renderFile(ejsPath, {
config,
employerName: employer ? employer.name : '',
});
const admins = await this.userService.getAdmins();
admins.forEach((admin) => {
this.mailerService.send(admin.email, jsonConfig.subject, html);
});
}
public async validate(employerId: string): Promise<Employer> {
this.logger.debug(`validateEmployer: ${employerId}`);
const employer = await this.employerModel.findById(Types.ObjectId(employerId));
if (employer) {
employer.validated = true;
employer.save();
return employer;
} else {
throw new HttpException('Cannot validate employer. It might have been already validate', HttpStatus.NOT_FOUND);
}
}
public async update(employerId: string, newEmployer: CreateEmployerDto): Promise<Employer> {
this.logger.debug(`editEmployer: ${employerId}`);
const employer = await this.employerModel.findById(Types.ObjectId(employerId));
if (employer) {
employer.name = newEmployer.name;
employer.save();
return employer;
} else {
throw new HttpException('Cannot edit employer. It was not found in database.', HttpStatus.NOT_FOUND);
}
}
public async mergeEmployer({
sourceEmployerId,
targetEmployerId,
}: MergeEmployerDto): Promise<Employer | HttpException> {
this.logger.debug(`mergeEmployer: ${sourceEmployerId} into ${targetEmployerId}`);
const sourceEmployer = await this.employerModel.findById(Types.ObjectId(sourceEmployerId));
const targetEmployer = await this.employerModel.findById(Types.ObjectId(targetEmployerId));
if (targetEmployer && sourceEmployer) {
this.userService.replaceEmployers(sourceEmployer, targetEmployer);
if (!sourceEmployer.validated) {
this.deleteInvalidEmployer(sourceEmployerId);
}
return targetEmployer;
} else {
throw new HttpException('Cannot operate on employer.', HttpStatus.NOT_FOUND);
}
}
public async deleteInvalidEmployer(
id: string
): Promise<
Query<{
ok?: number;
n?: number;
deletedCount?: number;
}>
> {
this.logger.debug(`deleteInvalidEmployer: ${id}`);
const document = await this.employerModel.findById(Types.ObjectId(id));
this.employerSearchService.deleteIndex(document, document._id);
return this.employerModel.deleteOne({ _id: id });
}
public async deleteOneId(id: string): Promise<Employer> {
const employer = await this.employerModel.findOne({ _id: id });
if (!employer) {
throw new HttpException('Invalid employer id', HttpStatus.BAD_REQUEST);
}
return employer.deleteOne();
}
public async deleteByName(
name: string
): Promise<
......
import { HttpModule, HttpService, HttpStatus } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { AxiosResponse } from 'axios';
import { Types } from 'mongoose';
import { of } from 'rxjs';
import { ConfigurationModule } from '../../configuration/configuration.module';
import { MailerService } from '../../mailer/mailer.service';
import { CreateJobDto } from '../dto/create-job.dto';
import { JobDocument } from '../schemas/job.schema';
import { JobsService } from './jobs.service';
import { UsersService } from './users.service';
describe('JobsService', () => {
let service: JobsService;
let httpService: HttpService;
let mailer: MailerService;
const mockJobModel = {
create: jest.fn(),
find: jest.fn(),
findOne: jest.fn(),
findById: jest.fn(),
deleteOne: jest.fn(),
};
const mockUserModel = {
find: jest.fn(),
findOne: jest.fn(),
findById: jest.fn(),
};
const mockUserService = {
replaceJobs: jest.fn(),
getAdmins: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigurationModule],
imports: [ConfigurationModule, HttpModule],
providers: [
JobsService,
{
provide: getModelToken('Job'),
useValue: mockJobModel,
},
{
provide: UsersService,
useValue: mockUserService,
},
{
provide: getModelToken('User'),
useValue: mockUserModel,
},
MailerService,
],
}).compile();
service = module.get<JobsService>(JobsService);
mailer = module.get<MailerService>(MailerService);
httpService = module.get<HttpService>(HttpService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('findAll', async () => {
describe('findAll', () => {
it('should findAll validated jobs', async () => {
mockJobModel.find.mockResolvedValue([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'CNFS',
validated: true,
hasPersonalOffer: true,
},
]);
const reply = await service.findAll();
expect(reply.length).toBe(1);
});
it('should findAll all jobs', async () => {
mockJobModel.find.mockResolvedValue([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'CNFSssss',
validated: false,
hasPersonalOffer: true,
},
]);
const reply = await service.findAll(false);
expect(reply.length).toBe(1);
});
});
it('findUnvalidated', async () => {
mockJobModel.find.mockResolvedValue([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'CNFS',
validated: true,
validated: false,
hasPersonalOffer: true,
},
]);
const reply = await service.findAll();
expect(reply.length).toBe(1);
const reply = await service.findAllUnvalidated();
expect(reply[0].validated).toBeFalsy;
});
it('findByName', async () => {
......@@ -55,18 +113,255 @@ describe('JobsService', () => {
const reply = await service.findByName('CNFS');
expect(reply).toBeTruthy();
});
it('create', async () => {
mockJobModel.create.mockResolvedValue({
it('findOne', async () => {
mockJobModel.findById.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'CNFS',
validated: true,
hasPersonalOffer: true,
});
const reply = await service.findOne('6231aefe76598527c8d0b5bc');
expect(reply).toBeTruthy();
});
describe('createJob', () => {
it('create', async () => {
mockJobModel.create.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Dev',
validated: false,
hasPersonalOffer: false,
});
const createJob: CreateJobDto = {
name: 'Dev',
hasPersonalOffer: false,
};
const reply = await service.create(createJob);
expect(reply).toBeTruthy();
});
it('should send an email to admins', async () => {
//todo fetch admin infos and check httpservice is called further
const result: AxiosResponse = {
data: {
status: 200,
content: {
success: true,
response: [7010],
},
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
};
jest.spyOn(httpService, 'post').mockImplementationOnce(() => of(result));
expect(await mailer.send('a@a.com', 'test', '<p>This is a test</p>')).toBe(result);
});
});
it('createFromAdmin', async () => {
mockJobModel.create.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Dev',
validated: true,
hasPersonalOffer: false,
});
const createJob: CreateJobDto = {
name: 'Dev',
hasPersonalOffer: false,
};
const reply = await service.create(createJob);
expect(reply).toBeTruthy();
});
describe('sendAdminCreateNotification', () => {
it('should sendAdminCreateNotification', async () => {
const job = {
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Metro',
validated: true,
};
const result: AxiosResponse = {
data: {
status: 200,
content: {
success: true,
response: [7010],
},
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
};
jest.spyOn(mailer, 'send').mockResolvedValueOnce(result);
const spyer = jest.spyOn(mailer, 'send');
mockUserService.getAdmins.mockResolvedValueOnce([
{
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
},
]);
await service.sendAdminCreateNotification(job as JobDocument, 'adminJobCreate.ejs', 'adminJobCreate.json');
expect(spyer.mock.calls.length).toBe(1);
});
});
describe('validate', () => {
it('should validate job', async () => {
mockJobModel.findById.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Dev',
validated: false,
save: jest.fn().mockResolvedValueOnce(null),
});
const job = await service.validate('623aed68c5d45b6fbbaa7e60');
expect(job.validated).toBe(true);
});
it('should throw exception', async () => {
mockJobModel.findById.mockResolvedValueOnce(null);
try {
await service.validate('623aed68c5d45b6fbbaa7e60');
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot validate job. It might have been already validate');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
});
describe('mergeJob', () => {
it('should delete source job', async () => {
const reply = {
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e61'),
name: 'Dev 2',
validated: true,
hasPersonalOffer: false,
};
mockJobModel.findById
.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Dev',
validated: false,
hasPersonalOffer: false,
})
.mockResolvedValueOnce(reply);
jest.spyOn(service, 'deleteInvalidJob').mockRejectedValueOnce({});
expect(
await service.mergeJob({ sourceJobId: '623aed68c5d45b6fbbaa7e60', targetJobId: '623aed68c5d45b6fbbaa7e61' })
).toEqual(reply);
});
it('should throw error if target job is not validated', async () => {
mockJobModel.findById
.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Dev',
validated: false,
hasPersonalOffer: false,
})
.mockResolvedValueOnce(null);
try {
await service.mergeJob({ sourceJobId: '623aed68c5d45b6fbbaa7e60', targetJobId: '623aed68c5d45b6fbbaa7e61' });
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot operate on job.');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
it('should throw error if no source or target job', async () => {
mockJobModel.findById.mockResolvedValueOnce(null).mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Dev',
validated: false,
hasPersonalOffer: false,
});
try {
await service.mergeJob({ sourceJobId: '623aed68c5d45b6fbbaa7e60', targetJobId: '623aed68c5d45b6fbbaa7e61' });
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot operate on job.');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
});
it('deleteInvalidJob', async () => {
mockJobModel.deleteOne.mockResolvedValueOnce(null);
const spyer = jest.spyOn(service, 'deleteInvalidJob');
await service.deleteInvalidJob('623aed68c5d45b6fbbaa7e60');
expect(spyer.mock.calls.length).toEqual(1);
});
describe('deleteOneId', () => {
it('should delete ', async () => {
mockJobModel.findOne.mockResolvedValueOnce({
_id: Types.ObjectId('623aed68c5d45b6fbbaa7e60'),
name: 'Dev',
validated: false,
deleteOne: jest.fn().mockResolvedValueOnce('toto'),
});
const reply = await service.deleteOneId('623aed68c5d45b6fbbaa7e60');
expect(reply).toBe('toto');
});
it('should delete ', async () => {
mockJobModel.findOne.mockResolvedValueOnce(null);
try {
await service.deleteOneId('623aed68c5d45b6fbbaa7e60');
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Invalid job id');
expect(e.status).toBe(HttpStatus.BAD_REQUEST);
}
});
});
describe('update', () => {
it('should update', async () => {
const newJobDto = {
name: 'CNFS',
hasPersonalOffer: true,
};
const newJob = {
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'CNFS',
validated: true,
hasPersonalOffer: true,
};
mockJobModel.findById.mockResolvedValue({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'CNFS old',
validated: true,
hasPersonalOffer: false,
save: jest.fn().mockResolvedValueOnce(newJob),
});
const reply = await service.update('623aed68c5d45b6fbbaa7e60', newJobDto);
expect(reply.name).toBe('CNFS');
expect(reply.hasPersonalOffer).toBe(true);
});
});
it('should throw error', async () => {
const newJobDto = {
name: 'CNFS',
hasPersonalOffer: true,
};
mockJobModel.findById.mockResolvedValue(null);
try {
await service.update('623aed68c5d45b6fbbaa7e60', newJobDto);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot edit job. It was not found in database.');
expect(e.status).toBe(HttpStatus.NOT_FOUND);
}
});
});
import { Injectable, Logger } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import * as ejs from 'ejs';
import { Model, Query, Types } from 'mongoose';
import { MergeJobDto } from '../../admin/dto/merge-job.dto';
import { MailerService } from '../../mailer/mailer.service';
import { CreateJobDto } from '../dto/create-job.dto';
import { IJob } from '../interfaces/job.interface';
import { Job, JobDocument } from '../schemas/job.schema';
import { UsersService } from './users.service';
@Injectable()
export class JobsService {
private readonly logger = new Logger(JobsService.name);
constructor(@InjectModel(Job.name) private jobModel: Model<JobDocument>) {}
constructor(
@InjectModel(Job.name) private jobModel: Model<JobDocument>,
private readonly userService: UsersService,
private readonly mailerService: MailerService
) {}
public async findAll(filterValidetedJobs = true): Promise<Job[]> {
this.logger.debug(`findAll with filterValidetedJobs: ${filterValidetedJobs}`);
if (!filterValidetedJobs) {
public async findAll(filterValidatedJobs = true): Promise<IJob[]> {
this.logger.debug(`findAll with filterValidetedJobs: ${filterValidatedJobs}`);
if (!filterValidatedJobs) {
return this.jobModel.find();
} else {
return this.jobModel.find({ validated: true });
......@@ -24,8 +33,115 @@ export class JobsService {
return this.jobModel.findOne({ name });
}
public async create(job: CreateJobDto, validated = false, hasPersonalOffer = true): Promise<Job> {
this.logger.debug(`createJob: ${job.name}`);
return this.jobModel.create({ name: job.name, validated, hasPersonalOffer });
public async findOne(idParam: string): Promise<JobDocument> {
this.logger.debug('findOne');
return this.jobModel.findById(Types.ObjectId(idParam));
}
public async findAllUnvalidated(): Promise<IJob[]> {
this.logger.debug('findAllUnvalidated');
return this.jobModel.find({ validated: false });
}
public async create(
job: CreateJobDto,
validated = false,
hasPersonalOffer = false,
sendNotification = true
): Promise<Job> {
this.logger.debug(`createJob: ${job.name} | notificationSending ${sendNotification}`);
const result = this.jobModel
.create({ name: job.name, validated, hasPersonalOffer })
.then(async (postCreate: JobDocument) => {
if (sendNotification) {
this.sendAdminCreateNotification(
postCreate,
this.mailerService.config.templates.adminJobCreate.ejs,
this.mailerService.config.templates.adminJobCreate.json
);
}
return postCreate;
});
return result;
}
public async sendAdminCreateNotification(
job: JobDocument,
templateLocation: any,
jsonConfigLocation: any
): Promise<void> {
const config = this.mailerService.config;
const ejsPath = this.mailerService.getTemplateLocation(templateLocation);
const jsonConfig = this.mailerService.loadJsonConfig(jsonConfigLocation);
const html = await ejs.renderFile(ejsPath, {
config,
jobName: job ? job.name : '',
});
const admins = await this.userService.getAdmins();
admins.forEach((admin) => {
this.mailerService.send(admin.email, jsonConfig.subject, html);
});
}
public async validate(jobId: string): Promise<Job> {
this.logger.debug(`validateJob: ${jobId}`);
const job = await this.jobModel.findById(Types.ObjectId(jobId));
if (job) {
job.validated = true;
job.save();
return job;
} else {
throw new HttpException('Cannot validate job. It might have been already validate', HttpStatus.NOT_FOUND);
}
}
public async mergeJob({ sourceJobId, targetJobId }: MergeJobDto): Promise<Job | HttpException> {
this.logger.debug(`mergeJob: ${sourceJobId} into ${targetJobId}`);
const sourceJob = await this.jobModel.findById(Types.ObjectId(sourceJobId));
const targetJob = await this.jobModel.findById(Types.ObjectId(targetJobId));
if (targetJob && sourceJob) {
this.logger.debug(`Both jobs : ${sourceJob}, ${targetJob}`);
this.userService.replaceJobs(sourceJob, targetJob);
if (!sourceJob.validated) {
this.deleteInvalidJob(sourceJobId);
}
return targetJob;
} else {
throw new HttpException('Cannot operate on job.', HttpStatus.NOT_FOUND);
}
}
public async deleteInvalidJob(
id: string
): Promise<
Query<{
ok?: number;
n?: number;
deletedCount?: number;
}>
> {
this.logger.debug(`deleteInvalidJob: ${id}`);
return this.jobModel.deleteOne({ _id: id });
}
public async deleteOneId(id: string): Promise<Job> {
const job = await this.jobModel.findOne({ _id: id });
if (!job) {
throw new HttpException('Invalid job id', HttpStatus.BAD_REQUEST);
}
return job.deleteOne();
}
public async update(jobId: string, newJob: CreateJobDto): Promise<Job> {
this.logger.debug(`editJob: ${jobId}`);
const job = await this.jobModel.findById(Types.ObjectId(jobId));
if (job) {
job.name = newJob.name;
job.hasPersonalOffer = newJob.hasPersonalOffer;
job.save();
return job;
} else {
throw new HttpException('Cannot edit job. It was not found in database.', HttpStatus.NOT_FOUND);
}
}
}
......@@ -7,6 +7,7 @@ import * as bcrypt from 'bcrypt';
import { personalOffersDataMock } from '../../../test/mock/data/personalOffers.mock.data';
import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
import { usersMockData } from '../../../test/mock/data/users.mock.data';
import { employersMockData } from '../../../test/mock/data/employers.mock.data';
import { LoginDto } from '../../auth/login-dto';
import { ConfigurationModule } from '../../configuration/configuration.module';
import { MailerModule } from '../../mailer/mailer.module';
......@@ -19,6 +20,17 @@ function hashPassword() {
return bcrypt.hashSync(process.env.USER_PWD, process.env.SALT);
}
const mockUserModel = {
create: jest.fn(),
findOne: jest.fn(),
findById: jest.fn(),
deleteOne: jest.fn(),
updateMany: jest.fn(),
exec: jest.fn(),
find: jest.fn(() => mockUserModel),
sort: jest.fn(() => mockUserModel),
};
describe('UsersService', () => {
let service: UsersService;
......@@ -29,7 +41,7 @@ describe('UsersService', () => {
UsersService,
{
provide: getModelToken('User'),
useValue: User,
useValue: mockUserModel,
},
],
}).compile();
......@@ -405,4 +417,29 @@ describe('UsersService', () => {
}
});
});
describe('user employer', () => {
it('should replace employer with a new one', async () => {
const spyer = jest.spyOn(mockUserModel, 'updateMany');
mockUserModel.updateMany.mockReturnThis();
mockUserModel.exec.mockResolvedValueOnce([usersMockData[0]]);
await service.replaceEmployers(employersMockData[0], employersMockData[1]);
expect(spyer).toBeCalledTimes(1);
});
it('should return true if a user is linked to a given employer', async () => {});
it('should fetch users attached to a given employer', async () => {});
it('should populate an employer with a list of attached users', async () => {});
it("should update user's employer ", async () => {});
});
describe('user job', () => {
it('should replace job with a new one', async () => {});
it('should return true if a user is linked to a given job', async () => {});
it('should fetch users attached to a given job', async () => {});
it('should populate an job with a list of attached users', async () => {});
it("should update user's job ", async () => {});
});
});
......@@ -14,8 +14,8 @@ import { PendingStructureDto } from '../../admin/dto/pending-structure.dto';
import { OwnerDto } from '../dto/owner.dto';
import { StructureDocument } from '../../structures/schemas/structure.schema';
import { ConfigurationService } from '../../configuration/configuration.service';
import { EmployerDocument } from '../schemas/employer.schema';
import { JobDocument } from '../schemas/job.schema';
import { Employer, EmployerDocument } from '../schemas/employer.schema';
import { Job, JobDocument } from '../schemas/job.schema';
import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
@Injectable()
......@@ -87,7 +87,7 @@ export class UsersService {
}
public findAll(): Promise<User[]> {
return this.userModel.find().exec();
return this.userModel.find().populate('employer').populate('job').select('-password').exec();
}
public findAllUnattached(): Promise<IUser[]> {
......@@ -98,6 +98,9 @@ export class UsersService {
.where('structuresLink')
.size(0)
.sort({ surname: 1 })
.populate('employer')
.populate('job')
.select('-password')
.exec();
}
......@@ -107,18 +110,29 @@ export class UsersService {
.where('emailVerified')
.equals(true)
.sort({ surname: 1 })
.populate('employer')
.populate('job')
.select('-password')
.exec();
}
public findAllUnVerified(): Promise<IUser[]> {
return this.userModel.find().where('emailVerified').equals(false).sort({ surname: 1 }).exec();
return this.userModel
.find()
.where('emailVerified')
.equals(false)
.sort({ surname: 1 })
.populate('employer')
.populate('job')
.select('-password')
.exec();
}
public async findById(id: string, passwordQuery?: boolean): Promise<IUser | undefined> {
if (passwordQuery) {
return this.userModel.findById(id).exec();
return this.userModel.findById(id).populate('employer').populate('job').exec();
}
return this.userModel.findById(id).select('-password').exec();
return this.userModel.findById(id).populate('employer').populate('job').select('-password').exec();
}
public async removeStructureIdFromUsers(structureId: Types.ObjectId): Promise<IUser[] | undefined> {
......@@ -127,6 +141,19 @@ export class UsersService {
.exec();
}
public async replaceEmployers(
sourceEmployer: EmployerDocument,
targetEmployer: EmployerDocument
): Promise<IUser[] | undefined> {
return this.userModel
.updateMany({ employer: sourceEmployer._id }, { $set: { employer: targetEmployer._id } })
.exec();
}
public async replaceJobs(sourceJob: JobDocument, targetJob: JobDocument): Promise<IUser[] | undefined> {
return this.userModel.updateMany({ job: sourceJob._id }, { $set: { job: targetJob._id } }).exec();
}
/**
* Return a user after credential checking.
* Use for login action
......@@ -359,6 +386,63 @@ export class UsersService {
return this.userModel.findOne({ structuresLink: Types.ObjectId(structureId) }).exec();
}
public async isEmployerLinkedtoUser(id: string): Promise<boolean> {
const users = await this.userModel.find().exec();
let hasLinkedUser = false;
users.map((x) => {
if (String(x.employer) === id.toString()) {
hasLinkedUser = true;
}
});
return hasLinkedUser;
}
public async isJobLinkedtoUser(id: string): Promise<boolean> {
const users = await this.userModel.find().exec();
let hasLinkedUser = false;
users.map((x) => {
if (String(x.job) === id.toString()) {
hasLinkedUser = true;
}
});
return hasLinkedUser;
}
public getJobAttachedUsers(jobId: JobDocument): Promise<IUser[]> {
return this.userModel.find({ job: jobId._id }).select('-password -job').exec();
}
public getEmployerAttachedUsers(employerId: EmployerDocument): Promise<IUser[]> {
return this.userModel.find({ employer: employerId._id }).select('-password -employer').exec();
}
public async populateJobswithUsers(jobs: JobDocument[]): Promise<any[]> {
return Promise.all(
jobs.map(async (job) => {
return {
_id: job._id,
name: job.name,
validated: job.validated,
hasPersonalOffer: job.hasPersonalOffer,
users: await this.getJobAttachedUsers(job),
};
})
);
}
public async populateEmployerswithUsers(employers: EmployerDocument[]): Promise<any[]> {
return Promise.all(
employers.map(async (employer) => {
return {
_id: employer._id,
name: employer.name,
validated: employer.validated,
users: await this.getEmployerAttachedUsers(employer),
};
})
);
}
public getStructureOwners(structureId: string): Promise<IUser[]> {
return this.userModel.find({ structuresLink: Types.ObjectId(structureId) }).exec();
}
......@@ -529,7 +613,7 @@ export class UsersService {
return this.getPendingStructures();
} else {
throw new HttpException(
'Cannot validate strucutre. It might have been already validate, or the structure does`nt belong to the user',
'Cannot validate strucutre. It might have been already validate, or the structure doesn`t belong to the user',
HttpStatus.NOT_FOUND
);
}
......@@ -608,6 +692,7 @@ export class UsersService {
}
/**
*
* @param profile
* @param userId
*/
......@@ -615,4 +700,24 @@ export class UsersService {
this.logger.debug(`updateUserProfile | ${userId}`);
return this.userModel.updateOne({ _id: userId }, { $set: { employer: employer._id, job: job._id } });
}
/**
*
* @param job
* @param userId
*/
public async updateUserJob(userId: Types.ObjectId, job: JobDocument): Promise<any> {
this.logger.debug(`updateUserProfile - Job | ${userId}`);
return this.userModel.updateOne({ _id: userId }, { $set: { job: job._id } });
}
/**
*
* @param employer
* @param userId
*/
public async updateUserEmployer(userId: Types.ObjectId, employer: EmployerDocument): Promise<any> {
this.logger.debug(`updateUserProfile - Employer | ${userId}`);
return this.userModel.updateOne({ _id: userId }, { $set: { employer: employer._id } });
}
}
import { IEmployer } from '../../../src/users/interfaces/employer.interface';
export const employersMockData: IEmployer[] = [
{
_id: '6036721022462b001334c4ba',
name: 'Metro',
validated: true,
} as any,
{
_id: '6036721022462b001334c4bb',
name: 'Sopra',
validated: true,
} as any,
] as IEmployer[];
import { HttpException, HttpStatus } from '@nestjs/common';
import { isValidObjectId } from 'mongoose';
import { PendingStructureDto } from '../../../src/admin/dto/pending-structure.dto';
import { LoginDto } from '../../../src/auth/login-dto';
import { PersonalOffer, PersonalOfferDocument } from '../../../src/personal-offers/schemas/personal-offer.schema';
......@@ -66,17 +67,66 @@ export class UsersServiceMock {
return user;
}
findById(id: string) {
if (isValidObjectId(id)) {
return {
_id: id,
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'pauline.dupont@mii.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'DUPONT',
surname: 'Pauline',
personalOffers: [],
};
} else {
return null;
}
}
isEmployerLinkedtoUser(id: string) {
if (id == '6231aefe76598527c8d0b5bc') return true;
else {
return false;
}
}
isJobLinkedtoUser(id: string) {
if (id == '6231aefe76598527c8d0b5bc') return true;
else {
return false;
}
}
getAdmins() {
return {
_id: '6231aefe76598527c8d0b5bc',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: true,
email: 'admin@admin.com',
password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
role: 0,
name: 'admin',
surname: 'admin',
personalOffers: [],
};
}
getPendingStructures() {
return [
{
structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'a',
userEmail: 'paula.dubois@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
},
{
structureId: '6903ba0e2ab5775cfc01ed4d',
structureName: "L'Atelier Numérique",
userEmail: 'jacques.dupont@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
},
];
}
......@@ -211,6 +261,18 @@ export class UsersServiceMock {
return await [];
}
async populateEmployerswithUsers(): Promise<User[]> {
return await [];
}
async populateJobswithUsers(): Promise<User[]> {
return await [];
}
replaceEmployers(source, tar): IUser[] {
return [];
}
public async addPersonalOffer(userId: string, personalOfferDocument: PersonalOfferDocument): Promise<IUser> {
if (userId === '6036721022462b001334c4bb') {
const personalOffer: PersonalOffer = { ...personalOfferDocument } as PersonalOffer;
......@@ -240,4 +302,12 @@ export class UsersServiceMock {
}
throw new HttpException('User not found for the personal offer attachment', HttpStatus.BAD_REQUEST);
}
async updateUserJob() {
return await [];
}
async updateUserEmployer() {
return await [];
}
}