Skip to content

Commit 29f9eb4

Browse files
authored
Merge pull request #49 from Code-4-Community/BH/ssf-53-create-user-endpoint
added create user endpoint and unit tests for users
2 parents 018e4e6 + a46b272 commit 29f9eb4

File tree

7 files changed

+431
-4
lines changed

7 files changed

+431
-4
lines changed

apps/backend/src/auth/auth.controller.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SignInResponseDto } from './dtos/sign-in-response.dto';
1111
import { RefreshTokenDto } from './dtos/refresh-token.dto';
1212
import { ConfirmPasswordDto } from './dtos/confirm-password.dto';
1313
import { ForgotPasswordDto } from './dtos/forgot-password.dto';
14+
import { Role } from '../users/types';
1415

1516
@Controller('auth')
1617
export class AuthController {
@@ -32,6 +33,8 @@ export class AuthController {
3233
signUpDto.email,
3334
signUpDto.firstName,
3435
signUpDto.lastName,
36+
signUpDto.phone,
37+
Role.STANDARD_VOLUNTEER,
3538
);
3639

3740
return user;

apps/backend/src/auth/dtos/sign-up.dto.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IsEmail, IsString } from 'class-validator';
1+
import { IsEmail, IsNotEmpty, IsString, IsPhoneNumber } from 'class-validator';
22

33
export class SignUpDto {
44
@IsString()
@@ -12,4 +12,12 @@ export class SignUpDto {
1212

1313
@IsString()
1414
password: string;
15+
16+
@IsString()
17+
@IsNotEmpty()
18+
@IsPhoneNumber('US', {
19+
message:
20+
'phone must be a valid phone number (make sure all the digits are correct)',
21+
})
22+
phone: string;
1523
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
IsEmail,
3+
IsEnum,
4+
IsNotEmpty,
5+
IsString,
6+
IsOptional,
7+
IsPhoneNumber,
8+
} from 'class-validator';
9+
import { Role } from '../types';
10+
11+
export class userSchemaDto {
12+
@IsEmail()
13+
@IsNotEmpty()
14+
email: string;
15+
16+
@IsString()
17+
@IsNotEmpty()
18+
firstName: string;
19+
20+
@IsString()
21+
@IsNotEmpty()
22+
lastName: string;
23+
24+
@IsString()
25+
@IsNotEmpty()
26+
@IsPhoneNumber('US', {
27+
message:
28+
'phone must be a valid phone number (make sure all the digits are correct)',
29+
})
30+
phone: string;
31+
32+
@IsEnum(Role)
33+
role: Role;
34+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { BadRequestException } from '@nestjs/common';
2+
import { UsersController } from './users.controller';
3+
import { UsersService } from './users.service';
4+
import { User } from './user.entity';
5+
import { Role } from './types';
6+
import { userSchemaDto } from './dtos/userSchema.dto';
7+
8+
import { Test, TestingModule } from '@nestjs/testing';
9+
import { mock } from 'jest-mock-extended';
10+
11+
const mockUserService = mock<UsersService>();
12+
13+
const mockUser1: User = {
14+
id: 1,
15+
16+
firstName: 'John',
17+
lastName: 'Doe',
18+
phone: '1234567890',
19+
role: Role.STANDARD_VOLUNTEER,
20+
};
21+
22+
const mockUser2: User = {
23+
id: 2543210,
24+
25+
firstName: 'Bob',
26+
lastName: 'Smith',
27+
phone: '9876',
28+
role: Role.LEAD_VOLUNTEER,
29+
};
30+
31+
describe('UsersController', () => {
32+
let controller: UsersController;
33+
34+
beforeEach(async () => {
35+
mockUserService.findUsersByRoles.mockReset();
36+
mockUserService.findOne.mockReset();
37+
mockUserService.remove.mockReset();
38+
mockUserService.update.mockReset();
39+
mockUserService.create.mockReset();
40+
41+
const module: TestingModule = await Test.createTestingModule({
42+
controllers: [UsersController],
43+
providers: [
44+
{
45+
provide: UsersService,
46+
useValue: mockUserService,
47+
},
48+
],
49+
}).compile();
50+
51+
controller = module.get<UsersController>(UsersController);
52+
});
53+
54+
it('should be defined', () => {
55+
expect(controller).toBeDefined();
56+
});
57+
58+
describe('GET /volunteers', () => {
59+
it('should return all volunteers', async () => {
60+
const volunteers = [mockUser1, mockUser2];
61+
mockUserService.findUsersByRoles.mockResolvedValue(volunteers);
62+
63+
const result = await controller.getAllVolunteers();
64+
65+
const hasAdmin = result.some((user) => user.role === Role.ADMIN);
66+
expect(hasAdmin).toBe(false);
67+
68+
expect(result).toEqual(volunteers);
69+
expect(mockUserService.findUsersByRoles).toHaveBeenCalledWith([
70+
Role.LEAD_VOLUNTEER,
71+
Role.STANDARD_VOLUNTEER,
72+
]);
73+
});
74+
});
75+
76+
describe('GET /:id', () => {
77+
it('should return a user by id', async () => {
78+
mockUserService.findOne.mockResolvedValue(mockUser1);
79+
80+
const result = await controller.getUser(1);
81+
82+
expect(result).toEqual(mockUser1);
83+
expect(mockUserService.findOne).toHaveBeenCalledWith(1);
84+
});
85+
});
86+
87+
describe('DELETE /:id', () => {
88+
it('should remove a user by id', async () => {
89+
mockUserService.remove.mockResolvedValue(mockUser1);
90+
91+
const result = await controller.removeUser(1);
92+
93+
expect(result).toEqual(mockUser1);
94+
expect(mockUserService.remove).toHaveBeenCalledWith(1);
95+
});
96+
});
97+
98+
describe('PUT :id/role', () => {
99+
it('should update user role with valid role', async () => {
100+
const updatedUser = { ...mockUser1, role: Role.ADMIN };
101+
mockUserService.update.mockResolvedValue(updatedUser);
102+
103+
const result = await controller.updateRole(1, Role.ADMIN);
104+
105+
expect(result).toEqual(updatedUser);
106+
expect(mockUserService.update).toHaveBeenCalledWith(1, {
107+
role: Role.ADMIN,
108+
});
109+
});
110+
111+
it('should throw BadRequestException for invalid role', async () => {
112+
await expect(controller.updateRole(1, 'invalid_role')).rejects.toThrow(
113+
BadRequestException,
114+
);
115+
expect(mockUserService.update).not.toHaveBeenCalled();
116+
});
117+
});
118+
119+
describe('POST /api/users', () => {
120+
it('should create a new user with all required fields', async () => {
121+
const createUserSchema: userSchemaDto = {
122+
123+
firstName: 'Jane',
124+
lastName: 'Smith',
125+
phone: '9876543210',
126+
role: Role.ADMIN,
127+
};
128+
129+
const createdUser = { ...createUserSchema, id: 2 };
130+
mockUserService.create.mockResolvedValue(createdUser);
131+
132+
const result = await controller.createUser(createUserSchema);
133+
134+
expect(result).toEqual(createdUser);
135+
expect(mockUserService.create).toHaveBeenCalledWith(
136+
createUserSchema.email,
137+
createUserSchema.firstName,
138+
createUserSchema.lastName,
139+
createUserSchema.phone,
140+
createUserSchema.role,
141+
);
142+
});
143+
144+
it('should handle service errors', async () => {
145+
const createUserSchema: userSchemaDto = {
146+
147+
firstName: 'Jane',
148+
lastName: 'Smith',
149+
phone: '9876543210',
150+
role: Role.STANDARD_VOLUNTEER,
151+
};
152+
153+
const error = new Error('Database error');
154+
mockUserService.create.mockRejectedValue(error);
155+
156+
await expect(controller.createUser(createUserSchema)).rejects.toThrow(
157+
error,
158+
);
159+
});
160+
});
161+
});

apps/backend/src/users/users.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Param,
66
ParseIntPipe,
77
Put,
8+
Post,
89
BadRequestException,
910
Body,
1011
//UseGuards,
@@ -15,6 +16,7 @@ import { UsersService } from './users.service';
1516
import { User } from './user.entity';
1617
import { Role } from './types';
1718
import { VOLUNTEER_ROLES } from './types';
19+
import { userSchemaDto } from './dtos/userSchema.dto';
1820
//import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor';
1921

2022
@Controller('users')
@@ -48,4 +50,10 @@ export class UsersController {
4850
}
4951
return this.usersService.update(id, { role: role as Role });
5052
}
53+
54+
@Post('/')
55+
async createUser(@Body() createUserDto: userSchemaDto): Promise<User> {
56+
const { email, firstName, lastName, phone, role } = createUserDto;
57+
return this.usersService.create(email, firstName, lastName, phone, role);
58+
}
5159
}

0 commit comments

Comments
 (0)