Skip to content

Commit 9392f5d

Browse files
committed
fix sonar qube findings and add tests
1 parent 8acf8db commit 9392f5d

File tree

6 files changed

+299
-24
lines changed

6 files changed

+299
-24
lines changed

src/users/db/dbusers.service.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { HttpException, HttpStatus } from '@nestjs/common';
1+
import { HttpException, HttpStatus, Logger } from '@nestjs/common';
22
import { CreateUserDto } from '../dto/user-create.dto';
33
import { UserLoginResponseDto } from '../dto/user-login-response.dto';
44
import { PrismaService } from '../../prisma/prisma.service';
55
import { User } from '@prisma/client';
66
import { UpdateUserDto } from '../dto/user-update.dto';
77
import { AuthService } from '../../auth/auth.service';
88
import { UserLoginRequestDto } from '../dto/user-login-request.dto';
9-
import { Logger } from '@nestjs/common';
109
import { Users } from '../users.interface';
1110

1211
export class DbUsersService implements Users {
1312
private readonly logger: Logger = new Logger(DbUsersService.name);
1413

1514
constructor(
16-
private prismaService: PrismaService,
17-
private authService: AuthService
15+
private readonly prismaService: PrismaService,
16+
private readonly authService: AuthService
1817
) {}
1918

2019
async create(createUserDto: CreateUserDto): Promise<UserLoginResponseDto> {
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { LdapUsersService } from './ldapusers.service';
3+
import { AuthService } from '../../auth/auth.service';
4+
import { PrismaService } from '../../prisma/prisma.service';
5+
import { ConfigService } from '@nestjs/config';
6+
import { Role, User } from '@prisma/client';
7+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8+
import { Entry as LdapEntry, Client as LdapClient, SearchOptions, SearchResult } from 'ldapts';
9+
10+
jest.mock('ldapts', () => {
11+
const mockLdapClient = {
12+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
13+
search: jest.fn((_searchDN, searchOptions: SearchOptions): Promise<SearchResult> => {
14+
if (searchOptions.filter.toString().includes('[email protected]')) {
15+
return Promise.resolve({
16+
searchEntries: [
17+
{
18+
dn: 'dn',
19+
LDAP_ATTRIBUTE_MAIL: '[email protected]',
20+
LDAP_ATTRIBUTE_FIRST_NAME: 'first',
21+
LDAP_ATTRIBUTE_LAST_NAME: 'last',
22+
},
23+
],
24+
searchReferences: [],
25+
});
26+
} else {
27+
return Promise.resolve({ searchEntries: [], searchReferences: [] });
28+
}
29+
}),
30+
bind: jest.fn(),
31+
unbind: jest.fn(),
32+
};
33+
34+
return {
35+
Client: jest.fn(() => mockLdapClient),
36+
};
37+
});
38+
39+
const user: User = {
40+
id: '1',
41+
42+
firstName: 'John',
43+
lastName: 'Doe',
44+
apiKey: 'generatedApiKey',
45+
password: 'encryptedPassword',
46+
isActive: true,
47+
role: Role.editor,
48+
updatedAt: new Date(),
49+
createdAt: new Date(),
50+
};
51+
52+
describe('LdapUsersService match from ldap', () => {
53+
let service: LdapUsersService;
54+
let prismaService: PrismaService;
55+
let configService: ConfigService;
56+
let authService: AuthService;
57+
58+
beforeEach(async () => {
59+
const module: TestingModule = await Test.createTestingModule({
60+
providers: [
61+
{
62+
provide: ConfigService,
63+
useValue: {
64+
get: jest.fn().mockReturnValue('true'),
65+
getOrThrow: jest.fn((string) => (string === 'LDAP_USERS_SEARCH_FILTER' ? '(&(mail={{email}}))' : string)),
66+
},
67+
},
68+
{
69+
provide: PrismaService,
70+
useValue: {
71+
user: {
72+
findMany: jest.fn().mockResolvedValueOnce([]),
73+
findUnique: jest.fn(),
74+
create: jest.fn(),
75+
update: jest.fn(),
76+
},
77+
},
78+
},
79+
{
80+
provide: AuthService,
81+
useValue: {
82+
generateApiKey: jest.fn(() => 'generatedApiKey'),
83+
encryptPassword: jest.fn(() => 'encryptedPassword'),
84+
signToken: jest.fn(() => 'token'),
85+
},
86+
},
87+
],
88+
}).compile();
89+
configService = module.get<ConfigService>(ConfigService);
90+
prismaService = module.get<PrismaService>(PrismaService);
91+
authService = module.get<AuthService>(AuthService);
92+
service = new LdapUsersService(configService, prismaService, authService);
93+
});
94+
95+
it('should be defined', () => {
96+
expect(service).toBeDefined();
97+
});
98+
99+
it('should create new user on login', async () => {
100+
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValue(undefined);
101+
const prismaUserCreateMock = jest.spyOn(prismaService.user, 'create').mockResolvedValue(user);
102+
await service.login({ email: '[email protected]', password: 'password' });
103+
expect(prismaUserCreateMock).toBeCalled();
104+
});
105+
106+
it('should login with user already in db', async () => {
107+
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValue(user);
108+
const prismaUserCreateMock = jest.spyOn(prismaService.user, 'create');
109+
await service.login({ email: '[email protected]', password: 'password' });
110+
expect(prismaUserCreateMock).not.toBeCalled();
111+
});
112+
});
113+
114+
describe('LdapUsersService with no results from ldap', () => {
115+
let service: LdapUsersService;
116+
let prismaService: PrismaService;
117+
let configService: ConfigService;
118+
let authService: AuthService;
119+
120+
beforeEach(async () => {
121+
const module: TestingModule = await Test.createTestingModule({
122+
providers: [
123+
{
124+
provide: ConfigService,
125+
useValue: {
126+
get: jest.fn().mockReturnValue('true'),
127+
getOrThrow: jest.fn().mockReturnValue('string'),
128+
},
129+
},
130+
{
131+
provide: PrismaService,
132+
useValue: {
133+
user: {
134+
findMany: jest.fn().mockResolvedValueOnce([]),
135+
create: jest.fn(),
136+
update: jest.fn(),
137+
},
138+
},
139+
},
140+
{
141+
provide: AuthService,
142+
useValue: {
143+
generateApiKey: jest.fn(() => 'generatedApiKey'),
144+
encryptPassword: jest.fn(() => 'encryptedPassword'),
145+
},
146+
},
147+
],
148+
}).compile();
149+
configService = module.get<ConfigService>(ConfigService);
150+
prismaService = module.get<PrismaService>(PrismaService);
151+
authService = module.get<AuthService>(AuthService);
152+
service = new LdapUsersService(configService, prismaService, authService);
153+
});
154+
155+
it('should be defined', () => {
156+
expect(service).toBeDefined();
157+
});
158+
159+
it('should never change a password', async () => {
160+
const prismaUserCreateMock = jest.spyOn(prismaService.user, 'create');
161+
const prismaUserUpdateMock = jest.spyOn(prismaService.user, 'update');
162+
const result = await service.changePassword(user, 'newPassword');
163+
expect(prismaUserCreateMock).not.toBeCalled();
164+
expect(prismaUserUpdateMock).not.toBeCalled();
165+
expect(result).toBe(true);
166+
});
167+
168+
it('should not login when ldap search results are empty', async () => {
169+
expect.assertions(1);
170+
try {
171+
await service.login({ email: 'testÜ*()\\\[email protected]', password: 'password' });
172+
} catch (e) {
173+
expect(e.message).toBe('Invalid email or password.');
174+
}
175+
});
176+
});

src/users/ldap/ldapusers.service.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { HttpException, HttpStatus } from '@nestjs/common';
1+
import { HttpException, HttpStatus, Logger } from '@nestjs/common';
22
import { CreateUserDto } from '../dto/user-create.dto';
33
import { UserLoginResponseDto } from '../dto/user-login-response.dto';
44
import { PrismaService } from '../../prisma/prisma.service';
55
import { Role, User } from '@prisma/client';
66
import { UpdateUserDto } from '../dto/user-update.dto';
77
import { AuthService } from '../../auth/auth.service';
88
import { UserLoginRequestDto } from '../dto/user-login-request.dto';
9-
import { Logger } from '@nestjs/common';
109
import { Entry as LdapEntry, Client as LdapClient } from 'ldapts';
1110
import { Users } from '../users.interface';
1211
import { ConfigService } from '@nestjs/config';

src/users/users.factory.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export class UsersFactoryService {
1111
private readonly logger: Logger = new Logger(UsersFactoryService.name);
1212

1313
constructor(
14-
private configService: ConfigService,
15-
private prismaService: PrismaService,
16-
private authService: AuthService
14+
private readonly configService: ConfigService,
15+
private readonly prismaService: PrismaService,
16+
private readonly authService: AuthService
1717
) {}
1818

1919
getUsersService(): Users {

src/users/users.service.spec.ts

Lines changed: 115 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,27 @@ import { AuthService } from '../auth/auth.service';
44
import { PrismaService } from '../prisma/prisma.service';
55
import { ConfigService } from '@nestjs/config';
66
import { UsersFactoryService } from './users.factory';
7-
import { Role } from '@prisma/client';
7+
import { Role, User } from '@prisma/client';
88
import { UserLoginResponseDto } from './dto/user-login-response.dto';
9+
import { AssignRoleDto } from './dto/assign-role.dto';
10+
import { randomUUID } from 'crypto';
11+
import { UserDto } from './dto/user.dto';
12+
import { UpdateUserDto } from './dto/user-update.dto';
913

10-
describe('UsersService', () => {
14+
const user: User = {
15+
id: '1',
16+
17+
firstName: 'John',
18+
lastName: 'Doe',
19+
apiKey: 'generatedApiKey',
20+
password: 'encryptedPassword',
21+
isActive: true,
22+
role: Role.editor,
23+
updatedAt: new Date(),
24+
createdAt: new Date(),
25+
};
26+
27+
describe('UsersService with DbUserService implementation', () => {
1128
let service: UsersService;
1229
let prismaService: PrismaService;
1330

@@ -28,6 +45,9 @@ describe('UsersService', () => {
2845
user: {
2946
findMany: jest.fn().mockResolvedValueOnce([]),
3047
create: jest.fn(),
48+
update: jest.fn(),
49+
delete: jest.fn(),
50+
findUnique: jest.fn().mockResolvedValueOnce(user),
3151
},
3252
},
3353
},
@@ -36,6 +56,8 @@ describe('UsersService', () => {
3656
useValue: {
3757
generateApiKey: jest.fn(() => 'generatedApiKey'),
3858
encryptPassword: jest.fn(() => 'encryptedPassword'),
59+
compare: jest.fn(() => true),
60+
signToken: jest.fn(() => 'token'),
3961
},
4062
},
4163
],
@@ -49,6 +71,19 @@ describe('UsersService', () => {
4971
expect(service).toBeDefined();
5072
});
5173

74+
it('should generate new api key', async () => {
75+
// Arrange
76+
const prismaUserUpdateMock = jest.spyOn(prismaService.user, 'update');
77+
prismaUserUpdateMock.mockResolvedValueOnce(user);
78+
79+
// Act
80+
const result = await service.generateNewApiKey(user);
81+
82+
// Assert
83+
expect(prismaUserUpdateMock).toHaveBeenCalled();
84+
expect(typeof result).toBe('string');
85+
});
86+
5287
it('should create a new user', async () => {
5388
// Arrange
5489
const createUserDto = {
@@ -59,18 +94,7 @@ describe('UsersService', () => {
5994
};
6095

6196
const prismaUserCreateMock = jest.spyOn(prismaService.user, 'create');
62-
prismaUserCreateMock.mockResolvedValueOnce({
63-
id: '1',
64-
65-
firstName: 'John',
66-
lastName: 'Doe',
67-
apiKey: 'generatedApiKey',
68-
password: 'encryptedPassword',
69-
isActive: true,
70-
role: Role.editor,
71-
updatedAt: new Date(),
72-
createdAt: new Date(),
73-
});
97+
prismaUserCreateMock.mockResolvedValueOnce(user);
7498

7599
// Act
76100
const result = await service.create(createUserDto);
@@ -87,4 +111,81 @@ describe('UsersService', () => {
87111
});
88112
expect(result).toEqual(expect.any(UserLoginResponseDto));
89113
});
114+
115+
it('should update a user', async () => {
116+
// Arrange
117+
const updateUserDto: UpdateUserDto = {
118+
119+
firstName: 'John',
120+
lastName: 'Doe',
121+
};
122+
123+
const prismaUserUpdateMock = jest.spyOn(prismaService.user, 'update');
124+
prismaUserUpdateMock.mockResolvedValueOnce(user);
125+
126+
// Act
127+
const result = await service.update(user.id, updateUserDto);
128+
129+
// Assert
130+
expect(prismaUserUpdateMock).toHaveBeenCalled();
131+
expect(result).toEqual(expect.any(UserLoginResponseDto));
132+
});
133+
134+
it('should change password', async () => {
135+
// Arrange
136+
const prismaUserUpdateMock = jest.spyOn(prismaService.user, 'update');
137+
prismaUserUpdateMock.mockResolvedValueOnce(user);
138+
139+
// Act
140+
const result = await service.changePassword(user, 'newPassword');
141+
142+
// Assert
143+
expect(result).toEqual(true);
144+
});
145+
146+
it('should login existing user', async () => {
147+
// Act
148+
const result = await service.login({ email: '[email protected]', password: 'doesntmatter' });
149+
150+
// Assert
151+
expect(result).toEqual(expect.any(UserLoginResponseDto));
152+
});
153+
154+
it('should delete existing user', async () => {
155+
// Arrange
156+
const prismaUserDeleteMock = jest.spyOn(prismaService.user, 'delete');
157+
prismaUserDeleteMock.mockResolvedValueOnce(user);
158+
159+
// Act
160+
await service.delete(user.id);
161+
162+
// Assert
163+
expect(prismaUserDeleteMock).toHaveBeenCalled();
164+
});
165+
166+
it('should get existing user', async () => {
167+
// Act
168+
const result = await service.get(user.id);
169+
170+
// Assert
171+
expect(result).toBeInstanceOf(UserDto);
172+
});
173+
174+
it('should assign role to user', async () => {
175+
// Arrange
176+
const assignRoleDto: AssignRoleDto = {
177+
id: randomUUID(),
178+
role: Role.editor,
179+
};
180+
181+
const prismaUserCreateMock = jest.spyOn(prismaService.user, 'update');
182+
prismaUserCreateMock.mockResolvedValueOnce(user);
183+
184+
// Act
185+
const result = await service.assignRole(assignRoleDto);
186+
187+
// Assert
188+
expect(prismaUserCreateMock).toHaveBeenCalled();
189+
expect(result).toBeInstanceOf(UserDto);
190+
});
90191
});

0 commit comments

Comments
 (0)