back tests 80+

This commit is contained in:
iqubik
2026-03-29 03:52:43 +03:00
parent e7a63dfff0
commit 7f5aae32e7
28 changed files with 5024 additions and 9 deletions
+111
View File
@@ -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,
);
});
});
});
+255
View File
@@ -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');
});
});
});
+149
View File
@@ -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);
}
});
});
});