Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests #113

Merged
merged 2 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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