diff --git a/src/mailer/mail-templates/userAddedToStructure.ejs b/src/mailer/mail-templates/userAddedToStructure.ejs index 77c821cb277870fd89399ca4271cb44fd5ff0d0c..03aa6b961add1fb36074a7a3a878ec161d16a8a4 100644 --- a/src/mailer/mail-templates/userAddedToStructure.ejs +++ b/src/mailer/mail-templates/userAddedToStructure.ejs @@ -2,6 +2,5 @@ Bonjour,<br /> <br /> Vous recevez ce message car vous avez été relié à la stucture <strong><%= name %></strong> sur RES'in, le réseau des acteurs de l'inclusion numérique de la Métropole de Lyon. Vous pouvez dès maintenant la consulter sur -<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profile" - >votre profil.</a +<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profile">votre profil</a >. diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts index 95a41a15c3de0097d4f2decc01baa458b36d1c2b..7e74151e7f56c2047a3d698a92f2931afdc2fafa 100644 --- a/src/structures/structures.controller.ts +++ b/src/structures/structures.controller.ts @@ -247,9 +247,11 @@ export class StructuresController { this.tempUserService.sendUserMail(user as ITempUser, structure.structureName, true); return this.userService.updateStructureLinked(user.email, id); } - // If temp user exist, update it - if (await this.tempUserService.findOne(user.email)) { - this.tempUserService.sendUserMail(user as ITempUser, structure.structureName, true); + // If temp user exists, update it + const existingTempUser = await this.tempUserService.findOne(user.email); + if (existingTempUser) { + user['_id'] = existingTempUser._id; + this.tempUserService.sendUserMail(user as ITempUser, structure.structureName); return this.tempUserService.updateStructureLinked(user); } // If not, create diff --git a/src/temp-user/temp-user.controller.spec.ts b/src/temp-user/temp-user.controller.spec.ts index 5bd3e66888865875e086fc36c376f02de54a5eed..aa3ba86935f1a477c842b1c75f430ef94b786254 100644 --- a/src/temp-user/temp-user.controller.spec.ts +++ b/src/temp-user/temp-user.controller.spec.ts @@ -10,6 +10,7 @@ describe('TempUserService', () => { const mockTempUserService = { findById: jest.fn(), + findAll: jest.fn(), }; beforeEach(async () => { @@ -27,12 +28,21 @@ describe('TempUserService', () => { }); describe('getTempUser', () => { - it('should get temporary users', async () => { - const tmpUser = { email: 'test@test.com', pendingStructuresLink: [] }; + it('should get a temporary user by ID', async () => { + const tmpUser = { email: 'test@test.com', structuresLink: [] }; mockTempUserService.findById.mockReturnValueOnce(tmpUser); expect(await tempUserController.getTempUser('addq651')).toEqual(tmpUser); }); - it('should throw error in cas of no users', async () => { + it('should get all temporary users', async () => { + const tempUsers = [ + { email: 'test@test.com', structuresLink: [] }, + { email: 'test2@test.com', structuresLink: [] }, + ]; + mockTempUserService.findAll.mockReturnValueOnce(tempUsers); + const findAllTempUsers = await tempUserController.findAll(); + expect(findAllTempUsers.length).toBe(2); + }); + it('should throw error in case of no users', async () => { const tmpUser = null; mockTempUserService.findById.mockReturnValueOnce(tmpUser); try { @@ -40,7 +50,7 @@ describe('TempUserService', () => { // Fail test if above expression doesn't throw anything. expect(true).toBe(false); } catch (e) { - expect(e.message).toEqual('Temp user does not exists'); + expect(e.message).toEqual('Temp user does not exist'); expect(e.status).toEqual(HttpStatus.BAD_REQUEST); } }); diff --git a/src/temp-user/temp-user.controller.ts b/src/temp-user/temp-user.controller.ts index bfdc230bf39083a706d2d34fd408159ac9f9c848..ffcf386187226480f79de1afce6fb147df96ec2c 100644 --- a/src/temp-user/temp-user.controller.ts +++ b/src/temp-user/temp-user.controller.ts @@ -5,14 +5,24 @@ import { TempUserService } from './temp-user.service'; @Controller('temp-user') export class TempUserController { - constructor(private readonly tempUserSercice: TempUserService) {} + constructor(private readonly tempUserService: TempUserService) {} + + @Get() + public async findAll(): Promise<TempUser[]> { + try { + const tempUsers: TempUser[] = await this.tempUserService.findAll(); + return tempUsers; + } catch (error) { + throw new HttpException('An error occurred while fetching temp users.', HttpStatus.INTERNAL_SERVER_ERROR); + } + } @Get(':id') @ApiParam({ name: 'id', type: String, required: true }) public async getTempUser(@Param('id') id: string): Promise<TempUser> { - const user = await this.tempUserSercice.findById(id); + const user = await this.tempUserService.findById(id); if (!user) { - throw new HttpException('Temp user does not exists', HttpStatus.BAD_REQUEST); + throw new HttpException('Temp user does not exist', HttpStatus.BAD_REQUEST); } return user; } diff --git a/src/temp-user/temp-user.schema.ts b/src/temp-user/temp-user.schema.ts index 4f8d266cded6d9cd2df5dfb431f10cc0c8bace48..a58db8d0b9f9444f56e68cc12d5bf996b67022e2 100644 --- a/src/temp-user/temp-user.schema.ts +++ b/src/temp-user/temp-user.schema.ts @@ -3,12 +3,12 @@ import { Document, Types } from 'mongoose'; export type TempUserDocument = TempUser & Document; -@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }) +@Schema({ timestamps: true }) export class TempUser { @Prop({ required: true }) email: string; - @Prop({ default: null }) + @Prop({ type: [{ type: Types.ObjectId, ref: 'Structure', default: null }] }) structuresLink?: Types.ObjectId[]; } diff --git a/src/temp-user/temp-user.service.spec.ts b/src/temp-user/temp-user.service.spec.ts index 227c55a73afef958c92334f231bca6e51a20471b..6bd08c06cd590b9acc4c001614152037ffe3b0de 100644 --- a/src/temp-user/temp-user.service.spec.ts +++ b/src/temp-user/temp-user.service.spec.ts @@ -6,6 +6,7 @@ import * as ejs from 'ejs'; import { MailerModule } from '../mailer/mailer.module'; import { MailerService } from '../mailer/mailer.service'; import { TempUserService } from './temp-user.service'; +import { Types } from 'mongoose'; const ejsSpy = jest.spyOn(ejs, 'renderFile'); @@ -18,9 +19,10 @@ describe('TempUserService', () => { findById: jest.fn(), findByIdAndUpdate: jest.fn(), deleteOne: jest.fn(), - find: jest.fn(), + find: jest.fn().mockReturnThis(), exec: jest.fn(), updateOne: jest.fn(), + populate: jest.fn().mockReturnThis(), }; const mockMailService = { @@ -41,6 +43,7 @@ describe('TempUserService', () => { }, }; + // Set up the test module before each test beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [HttpModule, MailerModule], @@ -57,12 +60,33 @@ describe('TempUserService', () => { tempUserService = module.get<TempUserService>(TempUserService); }); + // Test whether the service is defined it('should be defined', () => { expect(tempUserService).toBeDefined(); }); + + // Test related to the 'findAll' method + describe('findAll', () => { + it('should return a list of temp users', async () => { + const tempUsers = [ + { email: 'user1@test.com', structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')] }, + { email: 'user2@test.com', structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')] }, + ]; + + tempUserModelMock.exec.mockResolvedValueOnce(tempUsers); + + expect(await tempUserService.findAll()).toEqual(tempUsers); + }); + }); + + // Tests related to the 'create' method describe('create', () => { - const tmpUser = { email: 'test@test.com', pendingStructuresLink: [] }; - it('should not create temporary user: already exist', async () => { + const tmpUser = { + email: 'test@test.com', + structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')], + }; + + it('should not create temporary user if it already exists', async () => { tempUserModelMock.findOne.mockReturnThis(); tempUserModelMock.exec.mockResolvedValueOnce(tmpUser); try { @@ -73,7 +97,8 @@ describe('TempUserService', () => { expect(e.status).toEqual(HttpStatus.BAD_REQUEST); } }); - it('should create temporary user', async () => { + + it('should create a temporary user', async () => { tempUserModelMock.findOne.mockReturnThis(); tempUserModelMock.exec.mockResolvedValueOnce(null); tempUserModelMock.create.mockResolvedValueOnce(tmpUser); @@ -83,59 +108,101 @@ describe('TempUserService', () => { expect(mockMailService.send).toHaveBeenCalled(); }); }); + + // Test whether the 'findOne' method works it('should find one', async () => { - const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + const tmpUser = { + email: 'test2@test.com', + structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')], + }; tempUserModelMock.findOne.mockReturnThis(); tempUserModelMock.exec.mockResolvedValueOnce(tmpUser); expect(await tempUserService.findOne('test2@test.com')).toEqual(tmpUser); }); + // Test whether the 'findById' method works it('should find one by id', async () => { - const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + const tmpUser = { + email: 'test2@test.com', + structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')], + }; tempUserModelMock.findById.mockReturnThis(); tempUserModelMock.exec.mockResolvedValueOnce(tmpUser); expect(await tempUserService.findById('5fbb92e480a5c257dc0161f0')).toEqual(tmpUser); }); + // Tests related to the 'delete' method describe('delete', () => { - it('should delete a temp user', async () => { - const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + it('should delete a temporary user', async () => { + const tmpUser = { + email: 'test2@test.com', + structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')], + }; tempUserModelMock.findOne.mockReturnThis(); tempUserModelMock.exec.mockResolvedValueOnce(tmpUser); tempUserModelMock.deleteOne.mockReturnThis(); tempUserModelMock.exec.mockImplementationOnce(() => ({})); expect(await tempUserService.delete('toto@test.com')).toEqual(tmpUser); }); - it('should return an error : user does not exist', async () => { + + it('should return an error if the user does not exist', async () => { tempUserModelMock.findOne.mockReturnThis(); tempUserModelMock.exec.mockResolvedValueOnce(null); try { await tempUserService.delete('toto@test.com'); expect(true).toBe(false); } catch (e) { - expect(e.message).toEqual('User does not exists'); + expect(e.message).toEqual('User does not exist'); expect(e.status).toEqual(HttpStatus.BAD_REQUEST); } }); }); + // Tests related to the 'updateStructureLinked' method describe('updateStructureLinked', () => { - it('should update structure linked', async () => { - const tmpUser = { email: 'test2@test.com', structuresLink: [] }; - tempUserModelMock.find.mockReturnThis(); - tempUserModelMock.exec.mockResolvedValueOnce([]).mockResolvedValueOnce(tmpUser); + it('should update the linked structure', async () => { + const tmpUser = { + email: 'test2@test.com', + structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')], + }; + tempUserModelMock.findOne.mockReturnThis(); + tempUserModelMock.exec.mockResolvedValueOnce(tmpUser); + + const userInDb = { + structuresLink: [], + save: jest.fn().mockResolvedValueOnce(tmpUser), + }; + + jest.spyOn(tempUserModelMock, 'findOne').mockReturnValue({ + exec: jest.fn().mockResolvedValueOnce(userInDb), + }); + tempUserModelMock.findByIdAndUpdate.mockReturnThis(); expect(await tempUserService.updateStructureLinked(tmpUser)).toEqual(tmpUser); }); - it('should not update structure linked: User already linked', async () => { - const tmpUser = { email: 'test2@test.com', structuresLink: [] }; - tempUserModelMock.find.mockReturnThis(); + + it('should not update the linked structure if the user is already linked', async () => { + const tmpUser = { + email: 'test2@test.com', + structuresLink: [new Types.ObjectId('653015f722f6c63019ab038d')], + }; + tempUserModelMock.findOne.mockReturnThis(); + + const userInDb = { + structuresLink: [new Types.ObjectId()], + save: jest.fn().mockResolvedValueOnce(tmpUser), + }; + + jest.spyOn(tempUserModelMock, 'findOne').mockReturnValue({ + exec: jest.fn().mockResolvedValueOnce(userInDb), + }); + tempUserModelMock.findByIdAndUpdate.mockReturnThis(); - tempUserModelMock.exec.mockResolvedValueOnce([tmpUser]); + try { await tempUserService.updateStructureLinked(tmpUser); } catch (e) { - expect(e.message).toEqual('User already linked'); + expect(e.message).toEqual('User is already linked'); expect(e.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); } }); diff --git a/src/temp-user/temp-user.service.ts b/src/temp-user/temp-user.service.ts index 0059bb434618a5d66cf5a3d9f06867b13e00502a..20da703198fd1ec6a240dd9bc3d4ad0f41b52503 100644 --- a/src/temp-user/temp-user.service.ts +++ b/src/temp-user/temp-user.service.ts @@ -29,6 +29,22 @@ export class TempUserService { return this.findOne(createTempUser.email); } + public async findAll(): Promise<ITempUser[]> { + try { + const tempUsers = await this.tempUserModel + .find() + .populate({ + path: 'structuresLink', + select: 'structureName', + }) + .exec(); + + return tempUsers; + } catch (error) { + throw new Error('An error occurred while fetching temp users: ' + error.message); + } + } + public async findOne(mail: string): Promise<ITempUser> { return this.tempUserModel.findOne({ email: mail }).exec(); } @@ -40,39 +56,36 @@ export class TempUserService { public async delete(mail: string): Promise<TempUser> { const userInDb = await this.findOne(mail); if (!userInDb) { - throw new HttpException('User does not exists', HttpStatus.BAD_REQUEST); + throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST); } await this.tempUserModel.deleteOne({ email: mail }).exec(); return userInDb; } public async updateStructureLinked(createTempUser: CreateTempUserDto): Promise<TempUser> { - const userInDb = await this.tempUserModel - .find({ - $and: [ - { - email: createTempUser.email, - }, - { - structuresLink: { $in: [createTempUser.structuresLink[0]] }, - }, - ], - }) - .exec(); + // Find the user by email + const userInDb = await this.tempUserModel.findOne({ email: createTempUser.email }).exec(); - if (userInDb.length > 0) { + if (!userInDb) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + + // Check if the structuresLink already contains the provided structure + if (userInDb.structuresLink.includes(createTempUser.structuresLink[0])) { throw new HttpException('User already linked', HttpStatus.UNPROCESSABLE_ENTITY); } - return this.tempUserModel - .findByIdAndUpdate( - { email: createTempUser.email }, - { $push: { structuresLink: new Types.ObjectId(createTempUser.structuresLink[0]) } } - ) - .exec(); + + // Add the new structure to structuresLink + userInDb.structuresLink.push(createTempUser.structuresLink[0]); + + // Save the updated user + const updatedUser = await userInDb.save(); + + return updatedUser; } /** - * Send email in order to tell the user that an account is already fill with his structure info. + * Send email in order to tell the user that an account is already filled with his structure info. * @param user User */ public async sendUserMail(user: ITempUser, structureName: string, existingUser?: boolean): Promise<void> { @@ -109,10 +122,20 @@ export class TempUserService { if (!user.structuresLink.includes(new Types.ObjectId(idStructure))) { throw new HttpException("Temp user doesn't belong to this structure", HttpStatus.NOT_FOUND); } + + // Remove the specified structureId from the user's structuresLink user.structuresLink = user.structuresLink.filter((structureId) => { return !structureId.equals(idStructure); }); - await user.save(); + + if (user.structuresLink.length === 0) { + // If there are no more structures linked, delete the user document + await this.tempUserModel.findByIdAndDelete(user._id).exec(); + } else { + // Save the updated user if there are remaining structures + await user.save(); + } + return user.structuresLink; } }