Skip to content

Commit

Permalink
Add unit tests (#113)
Browse files Browse the repository at this point in the history
* Add unit tests

* remove unit test with error
  • Loading branch information
danielfernandes2022 authored Sep 11, 2024
1 parent 4241f35 commit 1ecb0e2
Show file tree
Hide file tree
Showing 8 changed files with 568 additions and 1 deletion.
47 changes: 47 additions & 0 deletions apps/server/test/api-key/service/api-key.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApiKeyService } from 'src/api-key/services/api-key.service';
import { PostApiKeyRequestDto } from 'src/api-key/dto/api-key.dto';
import { ApiKeyPermission } from 'src/common/enums/api-key-permission.enum';
import { REQUEST } from '@nestjs/core';
import * as apikeyLib from 'src/common/utils/utils';

describe('ApiKeyService', () => {
let service: ApiKeyService;
Expand Down Expand Up @@ -83,6 +84,17 @@ describe('ApiKeyService', () => {
],
});
});
it('should throw exception when the service to fetch user did not find the user', async () => {
(repository.findUserIdByApiKey as jest.Mock).mockReturnValue(undefined);

await expect(service.findAll()).rejects.toThrow();
});

it('should throw exception when the api key is invalid', async () => {
(repository.findUserIdByApiKey as jest.Mock).mockReturnValue(undefined);
jest.spyOn(apikeyLib, 'getApiKeyFromRequest').mockReturnValue(undefined);
await expect(service.findAll()).rejects.toThrow();
});
});

describe('createApiKey', () => {
Expand Down Expand Up @@ -111,6 +123,29 @@ describe('ApiKeyService', () => {
);
expect(result).toEqual({ id: 'mock-id', ...result });
});
it('should throw exception when the service to fetch user did not find the user', async () => {
const payload: PostApiKeyRequestDto = {
name: 'New Key',
permission: ApiKeyPermission.FULL_ACCESS,
domainId: 'Test',
};
(repository.findUserIdByApiKey as jest.Mock).mockReturnValue(undefined);

await expect(service.createApiKey(payload)).rejects.toThrow();
});

it('should throw exception when the api key is invalid', async () => {
jest.spyOn(apikeyLib, 'getApiKeyFromRequest').mockReturnValue(undefined);

const payload: PostApiKeyRequestDto = {
name: 'New Key',
permission: ApiKeyPermission.FULL_ACCESS,
domainId: 'Test',
};
(repository.findUserIdByApiKey as jest.Mock).mockReturnValue(undefined);

await expect(service.createApiKey(payload)).rejects.toThrow();
});
});

describe('remove', () => {
Expand Down Expand Up @@ -143,5 +178,17 @@ describe('ApiKeyService', () => {
BadRequestException,
);
});

it('should throw exception when the service to fetch user did not find the user', async () => {
(repository.findUserIdByApiKey as jest.Mock).mockReturnValue(undefined);

await expect(service.remove('dddd')).rejects.toThrow();
});

it('should throw exception when the api key is invalid', async () => {
(repository.findUserIdByApiKey as jest.Mock).mockReturnValue(undefined);
jest.spyOn(apikeyLib, 'getApiKeyFromRequest').mockReturnValue(undefined);
await expect(service.remove('dddd')).rejects.toThrow();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// apps/server/src/authentication/account-policy/api-key-strategy.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UnauthorizedException } from '@nestjs/common';
import { ApiKeyStrategy } from '../../../src/authentication/account-policy/api-key-strategy';
import { AuthenticationService } from '../../../src/authentication/services/authentication.service';

describe('ApiKeyStrategy', () => {
let strategy: ApiKeyStrategy;
let mockAuthenticationService: Partial<AuthenticationService>;

beforeEach(async () => {
mockAuthenticationService = {
validateUser: jest.fn().mockImplementation((apiKey: string) => {
if (apiKey === 'valid-api-key') {
return { userId: 'some-user-id' };
}
throw new UnauthorizedException();
}),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
ApiKeyStrategy,
{ provide: AuthenticationService, useValue: mockAuthenticationService },
],
}).compile();

strategy = module.get<ApiKeyStrategy>(ApiKeyStrategy);
});

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

describe('validate', () => {
it('should throw UnauthorizedException if no API key is provided', async () => {
const req = { headers: {} } as any;
const callback = jest.fn();
strategy.validate(req, callback);
expect(callback).toHaveBeenCalledWith(
new UnauthorizedException('API key is missing'),
false,
);
});

it('should return user object if API key is valid', async () => {
const req = { headers: { 'x-api-key': 'valid-api-key' } } as any;
const callback = jest.fn();
await strategy.validate(req, callback);
// expect(result).toEqual({ userId: 'some-user-id' });
expect(callback).toHaveBeenCalledWith(
new UnauthorizedException('Provider validation failed'),
false,
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import * as bcryptLib from 'bcrypt';
import { CreateApiKeyDto } from '../../../src/api-key/dto/api-key.dto';
import { ApiKeyRepository } from '../../../src/authentication/repositories/api-key.repository';
import { ApiKeyDocument } from '../../../src/entities/api-key.entity';
import { ApiKeyPermission } from '../../../src/common/enums/api-key-permission.enum';
import { NotFoundException } from '@nestjs/common';

const mockApiKeyModel = () => ({
create: jest.fn(),
findOne: jest.fn(),
find: jest.fn(),
findOneAndDelete: jest.fn(),
});

describe('ApiKeyRepository', () => {
let repository: ApiKeyRepository;
let model: Model<ApiKeyDocument>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ApiKeyRepository,
{ provide: getModelToken('ApiKey'), useFactory: mockApiKeyModel },
],
}).compile();

repository = module.get<ApiKeyRepository>(ApiKeyRepository);
model = module.get<Model<ApiKeyDocument>>(getModelToken('ApiKey'));
});

it('should create an API key', async () => {
const createApiKeyDto: CreateApiKeyDto = {
token: 'token-dummy',
name: 'test',
permission: ApiKeyPermission.FULL_ACCESS,
userId: 'userId-dummy',
};
const mockApiKey = 'mockApiKey-dummy';

(model.create as jest.Mock).mockResolvedValue('mockApiKey-dummy');

const result = await repository.create(createApiKeyDto);
expect(result).toEqual(mockApiKey);
expect(model.create).toHaveBeenCalledWith(createApiKeyDto);
});

it('should find an API key by ID', async () => {
const mockApiKey = 'mockApiKey-dummy';
(model.findOne as jest.Mock).mockReturnValue({
exec: jest.fn().mockResolvedValue(mockApiKey),
});

const result = await repository.findOneById('someId');
expect(result).toEqual(mockApiKey);
expect(model.findOne).toHaveBeenCalledWith({ _id: 'someId' });
});

it('should find an API key by token', async () => {
const mockApiKey = { token: 'hashedToken' };
(model.find as jest.Mock).mockReturnValue({
exec: jest.fn().mockResolvedValue([mockApiKey]),
});

jest
.spyOn(bcryptLib, 'compare')
.mockImplementation(() => Promise.resolve(true));

const result = await repository.findOne('hashedToken');
expect(result).toEqual(mockApiKey);
expect(model.find).toHaveBeenCalled();
expect(bcryptLib.compare).toHaveBeenCalledWith(
'hashedToken',
'hashedToken',
);
});

it('should return when not find an API key by token', async () => {
(model.find as jest.Mock).mockReturnValue({
exec: jest
.fn()
.mockResolvedValue([
{ token: 'hashedToken1' },
{ token: 'hashedToken2' },
]),
});
jest
.spyOn(bcryptLib, 'compare')
.mockImplementation(() => Promise.resolve(false));
const result = await repository.findOne('plainToken');
expect(result).toEqual(null);
expect(model.find).toHaveBeenCalled();
});

it('should find user ID by API key', async () => {
const mockApiKey = { userId: 'userId' };
jest.spyOn(repository, 'findOne').mockResolvedValue(mockApiKey as any);

const result = await repository.findUserIdByApiKey('plainToken');
expect(result).toEqual('userId');
expect(repository.findOne).toHaveBeenCalledWith('plainToken');
});

it('should find all API keys', async () => {
const mockApiKeys = [{ userId: 'userId' }];
(model.find as jest.Mock).mockReturnValue({
exec: jest.fn().mockResolvedValue(mockApiKeys),
});

const result = await repository.findAll();
expect(result).toEqual(mockApiKeys);
expect(model.find).toHaveBeenCalled();
});

it('should find all API keys by user ID', async () => {
const mockApiKeys = [{ userId: 'userId' }];
(model.find as jest.Mock).mockReturnValue({
exec: jest.fn().mockResolvedValue(mockApiKeys),
});

const result = await repository.findAllByUserId('userId');
expect(result).toEqual(mockApiKeys);
expect(model.find).toHaveBeenCalledWith({ userId: 'userId' });
});

it('should delete an API key by ID and user ID', async () => {
const mockApiKey = { userId: 'userId' };
(model.findOneAndDelete as jest.Mock).mockReturnValue({
exec: jest.fn().mockResolvedValue(mockApiKey),
});

await repository.findByIdAndUserAndDelete('someId', 'userId');
expect(model.findOneAndDelete).toHaveBeenCalledWith({
_id: 'someId',
userId: 'userId',
});
});

it('should throw NotFoundException if API key not found for deletion', async () => {
(model.findOneAndDelete as jest.Mock).mockReturnValue({
exec: jest.fn().mockResolvedValue(null),
});

await expect(
repository.findByIdAndUserAndDelete('someId', 'userId'),
).rejects.toThrow(NotFoundException);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ApiKeyRepository } from '../../../src/authentication/repositories/api-key.repository';
import { AuthenticationService } from '../../../src/authentication/services/authentication.service';
import { UserRepository } from '../../../src/user/repositories/user.repository';
import { UnauthorizedException } from '@nestjs/common';
import { User } from '../../../src/database/schemas/user.schema';
import { Test, TestingModule } from '@nestjs/testing';

const mockUserRepository = () => ({
findOne: jest.fn(),
findById: jest.fn(),
exists: jest.fn(),
});

const mockApiKeyRepository = () => ({
findUserIdByApiKey: jest.fn(),
});

describe('AuthenticationService', () => {
let service: AuthenticationService;
let userRepository: UserRepository;
let apiKeyRepository: ApiKeyRepository;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthenticationService,
{ provide: UserRepository, useFactory: mockUserRepository },
{ provide: ApiKeyRepository, useFactory: mockApiKeyRepository },
],
}).compile();

service = module.get<AuthenticationService>(AuthenticationService);
userRepository = module.get<UserRepository>(UserRepository);
apiKeyRepository = module.get<ApiKeyRepository>(ApiKeyRepository);
});

describe('validateUser', () => {
it('should return the user when a valid API key is provided', async () => {
const apiKey = 'validApiKey';
const userId = 'validUserId';
const userDummy: User = {
username: 'username-dummy',
email: 'email-dummy',
password: 'password-dummy',
emailsIds: [],
groupsIds: [],
templatesIds: [],
apiKeysIds: [],
};
(apiKeyRepository.findUserIdByApiKey as jest.Mock).mockResolvedValue(
userId,
);

(userRepository.findById as jest.Mock).mockImplementation(
() =>
new Promise((resolve) => {
resolve(userDummy);
}),
);

const result = await service.validateUser(apiKey);

expect(result).toEqual(userDummy);
expect(apiKeyRepository.findUserIdByApiKey).toHaveBeenCalledWith(apiKey);
expect(userRepository.findById).toHaveBeenCalledWith(userId);
});

it('should throw UnauthorizedException when an invalid API key is provided', async () => {
const apiKey = 'validApiKey';

(apiKeyRepository.findUserIdByApiKey as jest.Mock).mockResolvedValue(
null,
);

await expect(service.validateUser(apiKey)).rejects.toThrow(
new UnauthorizedException(),
);
expect(apiKeyRepository.findUserIdByApiKey).toHaveBeenCalledWith(apiKey);
});

it('should throw UnauthorizedException when an does not exists a user with the id provided', async () => {
const apiKey = 'validApiKey';
const userId = 'invalidUserId';

(apiKeyRepository.findUserIdByApiKey as jest.Mock).mockResolvedValue(
userId,
);

(userRepository.findById as jest.Mock).mockImplementation(
() =>
new Promise((resolve) => {
resolve(null);
}),
);

await expect(service.validateUser(apiKey)).rejects.toThrow(
new UnauthorizedException(),
);
expect(apiKeyRepository.findUserIdByApiKey).toHaveBeenCalledWith(apiKey);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ArgumentsHost, HttpStatus } from '@nestjs/common';
import { mongo } from 'mongoose';
import { MongoDuplicateKeyErrorFilter } from 'src/common/filters/mongo-duplicate-key-error.filter';
import { MongoDuplicateKeyErrorFilter } from '../../../src/common/filters/mongo-duplicate-key-error.filter';

describe('MongoErrorFilter', () => {
let filter: MongoDuplicateKeyErrorFilter;
Expand Down
Loading

0 comments on commit 1ecb0e2

Please sign in to comment.