Skip to content

Commit

Permalink
Merge pull request #19 from fga-eps-mds/dev
Browse files Browse the repository at this point in the history
[FEAT] Adiciona rotas para a edição de um user (fga-eps-mds/2024.2-ARANDU-DOC#59)
  • Loading branch information
GabrielCostaDeOliveira authored Jan 10, 2025
2 parents 0d3e3db + 39ef0a9 commit 66fbff9
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/auth/guards/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Injectable,
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
Expand Down
38 changes: 35 additions & 3 deletions src/dtos/update-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsEmail, IsOptional, IsString, MinLength } from 'class-validator';

export class UpdateUserDto extends PartialType(CreateUserDto) {}
export class UpdateUserDto {

@ApiProperty({
example: 'Nome',
required: false
})
@IsString()
@MinLength(3)
@IsOptional()
name?: string

@ApiProperty({
example: 'Username',
required: false
})
@IsString()
@MinLength(3)
@IsOptional()
username?: string

@ApiProperty({
example: 'email@email.com',
required: false
})
@IsEmail()
@IsOptional()
email?: string

@IsBoolean()
@IsOptional()
isVerified?: boolean

}
23 changes: 23 additions & 0 deletions src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Patch,
Post,
Query,
Req,
UseGuards,
UsePipes,
ValidationPipe,
Expand All @@ -18,6 +19,7 @@ import { Roles } from 'src/auth/guards/roles.decorator';
import { RolesGuard } from 'src/auth/guards/roles.guard';
import { CreateUserDto } from '../dtos/create-user.dto';
import { UpdateRoleDto } from '../dtos/update-role.dto';
import { UpdateUserDto } from '../dtos/update-user.dto';
import { UserRole } from '../dtos/user-role.enum';
import { UsersService } from './users.service';

Expand All @@ -38,6 +40,27 @@ export class UsersController {
}
}

@Patch('')
@UseGuards(JwtAuthGuard)
@UsePipes(ValidationPipe)
async updateUser(
@Req() req,
@Body() body: UpdateUserDto
) {
try {
const user = await this.usersService.updateUser(req.userId, body)

return {
message: `User ${user.username} updated successfully!`
}
} catch ( error ) {
if (error instanceof NotFoundException) {
throw new NotFoundException(`User with ID ${req.userId} not found`);
}
throw error;
}
}

@Get('verify')
async verifyUser(@Query('token') token: string) {
const user = await this.usersService.verifyUser(token);
Expand Down
10 changes: 5 additions & 5 deletions src/users/users.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { JwtModule } from '@nestjs/jwt';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UserSchema } from './interface/user.schema';
import { MongooseModule } from '@nestjs/mongoose';
import { AuthService } from 'src/auth/auth.service';
import { EmailService } from './email.service';
import { RefreshTokenSchema } from './interface/refresh-token.schema';
import { ResetTokenSchema } from './interface/reset-token.schema';
import { AuthService } from 'src/auth/auth.service';
import { UserSchema } from './interface/user.schema';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
imports: [
Expand Down
38 changes: 35 additions & 3 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {
ConflictException,
Injectable,
NotFoundException,
ConflictException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { MongoError } from 'mongodb';
import { Model, Types } from 'mongoose';
import { CreateUserDtoFederated } from '../dtos/create-user-federated.dto';
import { CreateUserDto } from '../dtos/create-user.dto';
import { UpdateRoleDto } from '../dtos/update-role.dto';
import { UpdateUserDto } from '../dtos/update-user.dto';
import { UserRole } from '../dtos/user-role.enum';
import { EmailService } from './email.service';
import { User } from './interface/user.interface';
Expand Down Expand Up @@ -44,6 +45,37 @@ export class UsersService {
}
}

async updateUser ( userId: string, updateUserDto: UpdateUserDto ): Promise<User> {
await this.getUserById(userId);

if ( updateUserDto.email ) updateUserDto.isVerified = false;

var updateAttr = Object.fromEntries(
Object.entries(updateUserDto).filter(([value]) => value !== null)
)

try {
const updatedUser = await this.userModel.findByIdAndUpdate(
userId,
{ $set: updateAttr },
{
upsert: false,
new: true
}
)

if ( updateAttr['isVerified'] === false ) await this.emailService.sendVerificationEmail(updateAttr['email']);

return updatedUser;

} catch ( error ) {
if ( error instanceof MongoError ) {
throw new NotFoundException(`User with ID ${userId} not found!`)
}
throw error
}
}

async createFederatedUser(
createFederatedUserDto: CreateUserDtoFederated,
): Promise<any> {
Expand Down
40 changes: 40 additions & 0 deletions test/user.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { UpdateRoleDto } from 'src/dtos/update-role.dto';
import { UserRole } from 'src/dtos/user-role.enum';
import { UsersController } from 'src/users/users.controller';
import { UsersService } from 'src/users/users.service';
import { UpdateUserDto } from '../src/dtos/update-user.dto';

describe('UsersController', () => {
let controller: UsersController;
Expand All @@ -18,9 +19,18 @@ describe('UsersController', () => {
role: UserRole.ALUNO,
};

const mockUpdatedUser = {
_id: 'mockUserId',
name: 'Mock User',
username: 'mockusername',
email: 'mock@example.com',
role: UserRole.ALUNO,
}

const mockUserService = {
createUser: jest.fn().mockResolvedValue(mockUser),
verifyUser: jest.fn().mockResolvedValue(mockUser),
updateUser: jest.fn().mockResolvedValue(mockUpdatedUser),
getSubscribedJourneys: jest.fn().mockResolvedValue([]),
getUsers: jest.fn().mockResolvedValue([mockUser]),
addPointToUser: jest.fn().mockResolvedValue(mockUser),
Expand Down Expand Up @@ -63,6 +73,36 @@ describe('UsersController', () => {
});
});

it('should update an user', async () => {
const userId = 'mockUserId';

const updateUserDto: UpdateUserDto = {
name: 'Mock User',
username: 'mockusername',
email: 'mock@example.com'
}

const req = { userId: userId } as any;

expect(await controller.updateUser(req, updateUserDto)).toEqual({
message: `User ${mockUpdatedUser.username} updated successfully!`
})
})

it('should return an error while trying to update user', async () => {
const updateUserDto: UpdateUserDto = {
name: 'Mock User',
username: 'mockusername',
email: 'mock@example.com'
}

const req = { userId: 'idInexistente' } as any;

mockUserService.updateUser.mockRejectedValueOnce(new NotFoundException("User with ID 'idInexistente' not found"));

await expect(controller.updateUser(req, updateUserDto)).rejects.toBeInstanceOf(NotFoundException)
})

it('should verify a user', async () => {
const token = 'validToken';
await expect(controller.verifyUser(token)).resolves.toEqual({
Expand Down
66 changes: 66 additions & 0 deletions test/user.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConflictException, NotFoundException } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { MongoError } from 'mongodb';
import { Model, Types } from 'mongoose';
import { UpdateRoleDto } from '../src/dtos/update-role.dto';
import { UserRole } from '../src/dtos/user-role.enum';
Expand All @@ -26,6 +27,20 @@ describe('UsersService', () => {
save: jest.fn().mockResolvedValue(this),
};

const mockUpdatedUser = {
_id: 'mockId',
name: 'Updated Mock Name',
email: 'updatedmock@example.com',
username: 'updatedMockUsername',
password: 'mockPassword',
role: UserRole.ALUNO,
verificationToken: 'mockToken',
isVerified: false,
subscribedJourneys: [new Types.ObjectId(), new Types.ObjectId()],
completedTrails: [new Types.ObjectId(), new Types.ObjectId()],
save: jest.fn().mockResolvedValue(this),
};

const mockUserList = [
mockUser,
{
Expand Down Expand Up @@ -53,6 +68,7 @@ describe('UsersService', () => {
findById: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue(mockUser),
}),
findByIdAndUpdate: jest.fn().mockReturnValue(mockUpdatedUser),
find: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue(mockUserList),
}),
Expand Down Expand Up @@ -88,6 +104,56 @@ describe('UsersService', () => {
expect(result).toEqual(mockUserList);
});

it('should update an user', async () => {
const updateUserDto = {
name: 'Updated Mock Name',
email: 'updatedmock@example.com',
username: 'updatedMockUsername'
}

const result = await service.updateUser('mockId', updateUserDto);

expect(result).toEqual(mockUpdatedUser);
})

it('should return an error while trying to find an user to update', async () => {
const updateUserDto = {
name: 'Updated Mock Name',
email: 'updatedmock@example.com',
username: 'updatedMockUsername'
}

jest.spyOn(model, 'findById').mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(null),
} as any);

await expect(service.updateUser('inexistentId', updateUserDto)).rejects.toBeInstanceOf(NotFoundException)
})

it('should return an error while trying to update an user', async () => {
const updateUserDto = {
name: 'Updated Mock Name',
email: 'updatedmock@example.com',
username: 'updatedMockUsername'
}

jest.spyOn(model, 'findByIdAndUpdate').mockRejectedValueOnce(new MongoError(``));

await expect(service.updateUser('mockId', updateUserDto)).rejects.toBeInstanceOf(NotFoundException)
})

it('should return a not found error while trying to update an user', async () => {
const updateUserDto = {
name: 'Updated Mock Name',
email: 'updatedmock@example.com',
username: 'updatedMockUsername'
}

jest.spyOn(model, 'findByIdAndUpdate').mockRejectedValueOnce(new NotFoundException());

await expect(service.updateUser('mockId', updateUserDto)).rejects.toBeInstanceOf(NotFoundException)
})

it('should verify a user', async () => {
const result = await service.verifyUser('mockToken');
expect(result).toEqual(mockUser);
Expand Down

0 comments on commit 66fbff9

Please sign in to comment.