Skip to content
3 changes: 3 additions & 0 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { SignInResponseDto } from './dtos/sign-in-response.dto';
import { RefreshTokenDto } from './dtos/refresh-token.dto';
import { ConfirmPasswordDto } from './dtos/confirm-password.dto';
import { ForgotPasswordDto } from './dtos/forgot-password.dto';
import { Role } from '../users/types';

@Controller('auth')
export class AuthController {
Expand All @@ -32,6 +33,8 @@ export class AuthController {
signUpDto.email,
signUpDto.firstName,
signUpDto.lastName,
signUpDto.phone,
Role.STANDARD_VOLUNTEER,
);

return user;
Expand Down
10 changes: 9 additions & 1 deletion apps/backend/src/auth/dtos/sign-up.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IsEmail, IsString } from 'class-validator';
import { IsEmail, IsNotEmpty, IsString, IsPhoneNumber } from 'class-validator';

export class SignUpDto {
@IsString()
Expand All @@ -12,4 +12,12 @@ export class SignUpDto {

@IsString()
password: string;

@IsString()
@IsNotEmpty()
@IsPhoneNumber('US', {
message:
'phone must be a valid phone number (make sure all the digits are correct)',
})
phone: string;
}
34 changes: 34 additions & 0 deletions apps/backend/src/users/dtos/userSchema.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
IsEmail,
IsEnum,
IsNotEmpty,
IsString,
IsOptional,
IsPhoneNumber,
} from 'class-validator';
import { Role } from '../types';

export class userSchemaDto {
@IsEmail()
@IsNotEmpty()
email: string;

@IsString()
@IsNotEmpty()
firstName: string;

@IsString()
@IsNotEmpty()
lastName: string;

@IsString()
@IsNotEmpty()
@IsPhoneNumber('US', {
message:
'phone must be a valid phone number (make sure all the digits are correct)',
})
phone: string;

@IsEnum(Role)
role: Role;
}
161 changes: 161 additions & 0 deletions apps/backend/src/users/users.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { BadRequestException } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './user.entity';
import { Role } from './types';
import { userSchemaDto } from './dtos/userSchema.dto';

import { Test, TestingModule } from '@nestjs/testing';
import { mock } from 'jest-mock-extended';

const mockUserService = mock<UsersService>();

const mockUser1: User = {
id: 1,
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
phone: '1234567890',
role: Role.STANDARD_VOLUNTEER,
};

const mockUser2: User = {
id: 2543210,
email: '[email protected]',
firstName: 'Bob',
lastName: 'Smith',
phone: '9876',
role: Role.LEAD_VOLUNTEER,
};

describe('UsersController', () => {
let controller: UsersController;

beforeEach(async () => {
mockUserService.findUsersByRoles.mockReset();
mockUserService.findOne.mockReset();
mockUserService.remove.mockReset();
mockUserService.update.mockReset();
mockUserService.create.mockReset();

const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [
{
provide: UsersService,
useValue: mockUserService,
},
],
}).compile();

controller = module.get<UsersController>(UsersController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('GET /volunteers', () => {
it('should return all volunteers', async () => {
const volunteers = [mockUser1, mockUser2];
mockUserService.findUsersByRoles.mockResolvedValue(volunteers);

const result = await controller.getAllVolunteers();

const hasAdmin = result.some((user) => user.role === Role.ADMIN);
expect(hasAdmin).toBe(false);

expect(result).toEqual(volunteers);
expect(mockUserService.findUsersByRoles).toHaveBeenCalledWith([
Role.LEAD_VOLUNTEER,
Role.STANDARD_VOLUNTEER,
]);
});
});

describe('GET /:id', () => {
it('should return a user by id', async () => {
mockUserService.findOne.mockResolvedValue(mockUser1);

const result = await controller.getUser(1);

expect(result).toEqual(mockUser1);
expect(mockUserService.findOne).toHaveBeenCalledWith(1);
});
});

describe('DELETE /:id', () => {
it('should remove a user by id', async () => {
mockUserService.remove.mockResolvedValue(mockUser1);

const result = await controller.removeUser(1);

expect(result).toEqual(mockUser1);
expect(mockUserService.remove).toHaveBeenCalledWith(1);
});
});

describe('PUT :id/role', () => {
it('should update user role with valid role', async () => {
const updatedUser = { ...mockUser1, role: Role.ADMIN };
mockUserService.update.mockResolvedValue(updatedUser);

const result = await controller.updateRole(1, Role.ADMIN);

expect(result).toEqual(updatedUser);
expect(mockUserService.update).toHaveBeenCalledWith(1, {
role: Role.ADMIN,
});
});

it('should throw BadRequestException for invalid role', async () => {
await expect(controller.updateRole(1, 'invalid_role')).rejects.toThrow(
BadRequestException,
);
expect(mockUserService.update).not.toHaveBeenCalled();
});
});

describe('POST /api/users', () => {
it('should create a new user with all required fields', async () => {
const createUserSchema: userSchemaDto = {
email: '[email protected]',
firstName: 'Jane',
lastName: 'Smith',
phone: '9876543210',
role: Role.ADMIN,
};

const createdUser = { ...createUserSchema, id: 2 };
mockUserService.create.mockResolvedValue(createdUser);

const result = await controller.createUser(createUserSchema);

expect(result).toEqual(createdUser);
expect(mockUserService.create).toHaveBeenCalledWith(
createUserSchema.email,
createUserSchema.firstName,
createUserSchema.lastName,
createUserSchema.phone,
createUserSchema.role,
);
});

it('should handle service errors', async () => {
const createUserSchema: userSchemaDto = {
email: '[email protected]',
firstName: 'Jane',
lastName: 'Smith',
phone: '9876543210',
role: Role.STANDARD_VOLUNTEER,
};

const error = new Error('Database error');
mockUserService.create.mockRejectedValue(error);

await expect(controller.createUser(createUserSchema)).rejects.toThrow(
error,
);
});
});
});
8 changes: 8 additions & 0 deletions apps/backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Param,
ParseIntPipe,
Put,
Post,
BadRequestException,
Body,
//UseGuards,
Expand All @@ -15,6 +16,7 @@ import { UsersService } from './users.service';
import { User } from './user.entity';
import { Role } from './types';
import { VOLUNTEER_ROLES } from './types';
import { userSchemaDto } from './dtos/userSchema.dto';
//import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor';

@Controller('users')
Expand Down Expand Up @@ -48,4 +50,10 @@ export class UsersController {
}
return this.usersService.update(id, { role: role as Role });
}

@Post('/')
async createUser(@Body() createUserDto: userSchemaDto): Promise<User> {
const { email, firstName, lastName, phone, role } = createUserDto;
return this.usersService.create(email, firstName, lastName, phone, role);
}
}
Loading