back tests 80+
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { HttpException } from '@nestjs/common';
|
||||
import { AuthController } from 'src/auth/auth.controller';
|
||||
import { AuthService } from 'src/auth/auth.service';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
let authService: AuthService;
|
||||
|
||||
const mockAuthService = {
|
||||
validateUser: jest.fn(),
|
||||
login: jest.fn(),
|
||||
changePassword: jest.fn(),
|
||||
updateAdminProfile: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: mockAuthService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AuthController>(AuthController);
|
||||
authService = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('должен вернуть access_token при успешной аутентификации', async () => {
|
||||
const loginDto = { login: 'admin', password: 'password' };
|
||||
const mockUser = { login: 'admin' };
|
||||
const mockToken = { access_token: 'jwt-token' };
|
||||
|
||||
mockAuthService.validateUser.mockResolvedValue(mockUser);
|
||||
mockAuthService.login.mockReturnValue(mockToken);
|
||||
|
||||
const result = await controller.login(loginDto);
|
||||
|
||||
expect(result).toEqual(mockToken);
|
||||
expect(authService.validateUser).toHaveBeenCalledWith(
|
||||
'admin',
|
||||
'password',
|
||||
);
|
||||
expect(authService.login).toHaveBeenCalledWith(mockUser);
|
||||
});
|
||||
|
||||
it('должен бросить HttpException при неверных учётных данных', async () => {
|
||||
const loginDto = { login: 'admin', password: 'wrong' };
|
||||
|
||||
mockAuthService.validateUser.mockResolvedValue(null);
|
||||
|
||||
await expect(controller.login(loginDto)).rejects.toThrow(HttpException);
|
||||
|
||||
await expect(controller.login(loginDto)).rejects.toThrow(
|
||||
'Неверный логин или пароль',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changePassword', () => {
|
||||
it('должен изменить пароль', async () => {
|
||||
const newPassword = 'newSecurePassword';
|
||||
|
||||
mockAuthService.changePassword.mockResolvedValue(undefined);
|
||||
|
||||
const result = await controller.changePassword(newPassword);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(authService.changePassword).toHaveBeenCalledWith(newPassword);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateProfile', () => {
|
||||
it('должен обновить профиль с логином и паролем', async () => {
|
||||
const body = { login: 'newAdmin', password: 'newPassword' };
|
||||
|
||||
mockAuthService.updateAdminProfile.mockResolvedValue(undefined);
|
||||
|
||||
const result = await controller.updateProfile(body);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(authService.updateAdminProfile).toHaveBeenCalledWith(
|
||||
'newAdmin',
|
||||
'newPassword',
|
||||
);
|
||||
});
|
||||
|
||||
it('должен обновить профиль только с логином', async () => {
|
||||
const body = { login: 'newAdmin' };
|
||||
|
||||
mockAuthService.updateAdminProfile.mockResolvedValue(undefined);
|
||||
|
||||
const result = await controller.updateProfile(body);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(authService.updateAdminProfile).toHaveBeenCalledWith(
|
||||
'newAdmin',
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,255 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AuthService } from 'src/auth/auth.service';
|
||||
import { Setting } from 'src/settings/entities/setting.entity';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
let settingsRepo: Repository<Setting>;
|
||||
let jwtService: JwtService;
|
||||
let configService: ConfigService;
|
||||
|
||||
const mockSettingsRepo = {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockJwtService = {
|
||||
sign: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConfigService = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AuthService,
|
||||
{
|
||||
provide: getRepositoryToken(Setting),
|
||||
useValue: mockSettingsRepo,
|
||||
},
|
||||
{
|
||||
provide: JwtService,
|
||||
useValue: mockJwtService,
|
||||
},
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: mockConfigService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
settingsRepo = module.get<Repository<Repository<Setting>>>(
|
||||
getRepositoryToken(Setting),
|
||||
);
|
||||
jwtService = module.get<JwtService>(JwtService);
|
||||
configService = module.get<ConfigService>(ConfigService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('validateUser', () => {
|
||||
it('должен вернуть пользователя при верном пароле', async () => {
|
||||
const plainPassword = 'correctPassword';
|
||||
const hashedPassword = await bcrypt.hash(plainPassword, 10);
|
||||
|
||||
mockSettingsRepo.findOne
|
||||
.mockResolvedValueOnce({ key: 'admin_login', value: 'admin' })
|
||||
.mockResolvedValueOnce({
|
||||
key: 'admin_password',
|
||||
value: hashedPassword,
|
||||
});
|
||||
|
||||
const result = await service.validateUser('admin', plainPassword);
|
||||
|
||||
expect(result).toEqual({ login: 'admin' });
|
||||
});
|
||||
|
||||
it('должен вернуть null, если admin_login не найден', async () => {
|
||||
mockSettingsRepo.findOne.mockResolvedValueOnce(null);
|
||||
|
||||
const result = await service.validateUser('admin', 'password');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('должен вернуть null, если admin_password не найден', async () => {
|
||||
mockSettingsRepo.findOne
|
||||
.mockResolvedValueOnce({ key: 'admin_login', value: 'admin' })
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
const result = await service.validateUser('admin', 'password');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('должен вернуть null при неверном пароле', async () => {
|
||||
const hashedPassword = await bcrypt.hash('correctPassword', 10);
|
||||
|
||||
mockSettingsRepo.findOne
|
||||
.mockResolvedValueOnce({ key: 'admin_login', value: 'admin' })
|
||||
.mockResolvedValueOnce({
|
||||
key: 'admin_password',
|
||||
value: hashedPassword,
|
||||
});
|
||||
|
||||
const result = await service.validateUser('admin', 'wrongPassword');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('должен вернуть access_token', () => {
|
||||
const user = { login: 'admin' };
|
||||
const mockToken = 'jwt-token-123';
|
||||
|
||||
mockJwtService.sign.mockReturnValue(mockToken);
|
||||
|
||||
const result = service.login(user);
|
||||
|
||||
expect(result).toEqual({ access_token: mockToken });
|
||||
expect(jwtService.sign).toHaveBeenCalledWith({ username: 'admin' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('changePassword', () => {
|
||||
it('должен изменить пароль администратора', async () => {
|
||||
const newPassword = 'newSecurePassword';
|
||||
const existingSetting = { key: 'admin_password', value: 'oldHash' };
|
||||
|
||||
mockSettingsRepo.findOne.mockResolvedValue(existingSetting);
|
||||
mockSettingsRepo.save.mockResolvedValue({
|
||||
key: 'admin_password',
|
||||
value: 'newHash',
|
||||
});
|
||||
|
||||
await service.changePassword(newPassword);
|
||||
|
||||
expect(settingsRepo.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: 'admin_password',
|
||||
value: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('должен создать запись пароля, если не существует', async () => {
|
||||
const newPassword = 'newPassword';
|
||||
|
||||
mockSettingsRepo.findOne.mockResolvedValue(null);
|
||||
mockSettingsRepo.create.mockReturnValue({ key: 'admin_password' });
|
||||
mockSettingsRepo.save.mockResolvedValue({ key: 'admin_password' });
|
||||
|
||||
await service.changePassword(newPassword);
|
||||
|
||||
expect(settingsRepo.create).toHaveBeenCalledWith({
|
||||
key: 'admin_password',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAdminProfile', () => {
|
||||
it('должен обновить логин и пароль', async () => {
|
||||
const newLogin = 'newAdmin';
|
||||
const newPassword = 'newPassword';
|
||||
|
||||
mockSettingsRepo.findOne
|
||||
.mockResolvedValueOnce({ key: 'admin_login', value: 'oldAdmin' })
|
||||
.mockResolvedValueOnce({ key: 'admin_password', value: 'oldHash' });
|
||||
|
||||
mockSettingsRepo.save.mockResolvedValue({});
|
||||
|
||||
await service.updateAdminProfile(newLogin, newPassword);
|
||||
|
||||
expect(settingsRepo.save).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('должен обновить только логин, если пароль не передан', async () => {
|
||||
const newLogin = 'newAdmin';
|
||||
|
||||
mockSettingsRepo.findOne.mockResolvedValue({
|
||||
key: 'admin_login',
|
||||
value: 'oldAdmin',
|
||||
});
|
||||
mockSettingsRepo.save.mockResolvedValue({});
|
||||
|
||||
await service.updateAdminProfile(newLogin);
|
||||
|
||||
expect(settingsRepo.save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('должен создать логин, если не существует', async () => {
|
||||
mockSettingsRepo.findOne.mockResolvedValue(null);
|
||||
mockSettingsRepo.create.mockReturnValue({ key: 'admin_login' });
|
||||
mockSettingsRepo.save.mockResolvedValue({});
|
||||
|
||||
await service.updateAdminProfile('newAdmin', 'password');
|
||||
|
||||
expect(settingsRepo.create).toHaveBeenCalledWith({ key: 'admin_login' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('seedAdmin', () => {
|
||||
it('должен создать администратора, если не существует', async () => {
|
||||
mockSettingsRepo.findOne.mockResolvedValue(null);
|
||||
mockConfigService.get
|
||||
.mockReturnValueOnce('admin')
|
||||
.mockReturnValueOnce('admin');
|
||||
|
||||
mockSettingsRepo.create
|
||||
.mockReturnValueOnce({ key: 'admin_login' })
|
||||
.mockReturnValueOnce({ key: 'admin_password' });
|
||||
|
||||
mockSettingsRepo.save.mockResolvedValue({});
|
||||
|
||||
await service.seedAdmin();
|
||||
|
||||
expect(settingsRepo.create).toHaveBeenCalledTimes(2);
|
||||
expect(settingsRepo.save).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('НЕ должен создавать администратора, если уже существует', async () => {
|
||||
mockSettingsRepo.findOne.mockResolvedValue({
|
||||
key: 'admin_login',
|
||||
value: 'admin',
|
||||
});
|
||||
|
||||
await service.seedAdmin();
|
||||
|
||||
expect(settingsRepo.create).not.toHaveBeenCalled();
|
||||
expect(settingsRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('должен использовать ENV переменные для логина/пароля', async () => {
|
||||
mockSettingsRepo.findOne.mockResolvedValue(null);
|
||||
mockConfigService.get
|
||||
.mockReturnValueOnce('customAdmin')
|
||||
.mockReturnValueOnce('customPassword');
|
||||
|
||||
mockSettingsRepo.create
|
||||
.mockReturnValueOnce({ key: 'admin_login', value: 'customAdmin' })
|
||||
.mockReturnValueOnce({ key: 'admin_password' });
|
||||
|
||||
mockSettingsRepo.save.mockResolvedValue({});
|
||||
|
||||
await service.seedAdmin();
|
||||
|
||||
expect(configService.get).toHaveBeenCalledWith('ADMIN_LOGIN');
|
||||
expect(configService.get).toHaveBeenCalledWith('ADMIN_PASSWORD');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
|
||||
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
|
||||
describe('JwtAuthGuard', () => {
|
||||
let guard: JwtAuthGuard;
|
||||
let reflector: Reflector;
|
||||
let mockContext: ExecutionContext;
|
||||
let mockRequest: any;
|
||||
|
||||
beforeEach(() => {
|
||||
reflector = new Reflector();
|
||||
guard = new JwtAuthGuard(reflector);
|
||||
|
||||
mockRequest = {
|
||||
url: '/api/test',
|
||||
method: 'GET',
|
||||
headers: {},
|
||||
query: {},
|
||||
};
|
||||
|
||||
mockContext = {
|
||||
switchToHttp: jest.fn().mockReturnValue({
|
||||
getRequest: jest.fn().mockReturnValue(mockRequest),
|
||||
getResponse: jest.fn(),
|
||||
}),
|
||||
getHandler: jest.fn(),
|
||||
getClass: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('canActivate', () => {
|
||||
it('должен вернуть true для публичного маршрута', () => {
|
||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true);
|
||||
|
||||
const result = guard.canActivate(mockContext);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('должен вызвать super.canActivate для защищенного маршрута', () => {
|
||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
|
||||
|
||||
const superCanActivate = jest
|
||||
.spyOn(JwtAuthGuard.prototype, 'canActivate' as any)
|
||||
.mockImplementation(() => true);
|
||||
|
||||
guard.canActivate(mockContext);
|
||||
|
||||
expect(superCanActivate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('НЕ должен добавлять токен, если authorization уже есть', () => {
|
||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
|
||||
mockRequest.query.token = 'test-jwt-token';
|
||||
mockRequest.headers.authorization = 'Bearer existing-token';
|
||||
|
||||
const _superCanActivate = jest
|
||||
.spyOn(JwtAuthGuard.prototype, 'canActivate' as any)
|
||||
.mockImplementation(() => true);
|
||||
|
||||
guard.canActivate(mockContext);
|
||||
|
||||
expect(mockRequest.headers.authorization).toBe('Bearer existing-token');
|
||||
});
|
||||
|
||||
it('должен вернуть результат super.canActivate', () => {
|
||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
|
||||
|
||||
const _superCanActivate = jest
|
||||
.spyOn(JwtAuthGuard.prototype, 'canActivate' as any)
|
||||
.mockImplementation(() => 'PENDING_RESULT');
|
||||
|
||||
const result = guard.canActivate(mockContext);
|
||||
|
||||
expect(result).toBe('PENDING_RESULT');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleRequest', () => {
|
||||
it('должен вернуть пользователя при успешной аутентификации', () => {
|
||||
const user = { username: 'admin' };
|
||||
|
||||
const result = guard.handleRequest(null, user, null);
|
||||
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it('должен бросить ошибку при наличии ошибки', () => {
|
||||
const _error = new Error('Invalid token');
|
||||
|
||||
expect(() => guard.handleRequest(_error, null, null)).toThrow(Error);
|
||||
});
|
||||
|
||||
it('должен бросить UnauthorizedException, если пользователь null и нет ошибки', () => {
|
||||
expect(() => guard.handleRequest(null, null, null)).toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
|
||||
it('должен бросить ошибку с сообщением из Error', () => {
|
||||
const _error = new Error('Custom error message');
|
||||
|
||||
try {
|
||||
guard.handleRequest(_error, null, null);
|
||||
} catch (e) {
|
||||
expect((e as Error).message).toContain('Custom error message');
|
||||
}
|
||||
});
|
||||
|
||||
it('должен бросить ошибку с сообщением из строки', () => {
|
||||
const _error = 'String error message';
|
||||
|
||||
// handleRequest пробрасывает строковую ошибку как есть через throw
|
||||
expect(() => guard.handleRequest(_error as any, null, null)).toThrow(
|
||||
'String error message',
|
||||
);
|
||||
});
|
||||
|
||||
it('должен бросить ошибку с JSON сообщением', () => {
|
||||
const _error = { message: 'JSON error' };
|
||||
|
||||
// Для объекта берётся message поле
|
||||
expect(() => guard.handleRequest(_error as any, null, null)).toThrow(
|
||||
'JSON error',
|
||||
);
|
||||
});
|
||||
|
||||
it('должен бросить ошибку с сообщением "null", если error=null и user=null', () => {
|
||||
try {
|
||||
guard.handleRequest(null, null, null);
|
||||
} catch (_e: any) {
|
||||
// UnauthorizedException имеет пустое сообщение по умолчанию
|
||||
expect(_e).toBeInstanceOf(UnauthorizedException);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user