/* eslint-disable @typescript-eslint/unbound-method */ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { BadRequestException } from '@nestjs/common'; import { DomainsService } from 'src/domains/domains.service'; import { Domain } from 'src/domains/entities/domain.entity'; describe('DomainsService', () => { let service: DomainsService; let repo: Repository; const mockRepo = { find: jest.fn(), findOne: jest.fn(), findOneBy: jest.fn(), create: jest.fn(), save: jest.fn(), delete: jest.fn(), clear: jest.fn(), findAndCount: jest.fn(), count: jest.fn(), createQueryBuilder: jest.fn(() => ({ where: jest.fn().mockReturnThis(), getOne: jest.fn(), })), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ DomainsService, { provide: getRepositoryToken(Domain), useValue: mockRepo, }, ], }).compile(); service = module.get(DomainsService); repo = module.get>(getRepositoryToken(Domain)); }); afterEach(() => { jest.clearAllMocks(); }); describe('onModuleInit', () => { it('должен создать домены по умолчанию, если база пустая', async () => { mockRepo.count.mockResolvedValue(0); mockRepo.create.mockReturnValue({ name: 'ya.ru' }); mockRepo.save.mockResolvedValue({}); await service.onModuleInit(); expect(repo.count).toHaveBeenCalledTimes(1); expect(repo.create).toHaveBeenCalledTimes(10); expect(repo.save).toHaveBeenCalled(); }); it('НЕ должен создавать домены, если они уже есть', async () => { mockRepo.count.mockResolvedValue(5); await service.onModuleInit(); expect(repo.count).toHaveBeenCalledTimes(1); expect(repo.create).not.toHaveBeenCalled(); expect(repo.save).not.toHaveBeenCalled(); }); }); describe('create', () => { it('должен создать домен', async () => { const dto = { name: 'example.com' }; const normalized = 'example.com'; const created = { id: 1, name: normalized }; mockRepo.createQueryBuilder.mockReturnValue({ where: jest.fn().mockReturnThis(), getOne: jest.fn().mockResolvedValue(null), }); mockRepo.create.mockReturnValue(created); mockRepo.save.mockResolvedValue(created); const result = await service.create(dto); expect(result).toEqual(created); expect(repo.create).toHaveBeenCalledWith({ name: normalized }); }); it('должен вернуть существующий домен, если он уже есть', async () => { const dto = { name: 'example.com' }; const existing = { id: 1, name: 'example.com' }; mockRepo.createQueryBuilder.mockReturnValue({ where: jest.fn().mockReturnThis(), getOne: jest.fn().mockResolvedValue(existing), }); const result = await service.create(dto); expect(result).toEqual(existing); expect(repo.create).not.toHaveBeenCalled(); expect(repo.save).not.toHaveBeenCalled(); }); it('должен бросить BadRequestException для некорректного домена', async () => { const dto = { name: 'not-a-domain' }; await expect(service.create(dto)).rejects.toThrow(BadRequestException); await expect(service.create(dto)).rejects.toThrow( 'Некорректное доменное имя', ); }); it('должен нормализовать домен с протоколом и путём', async () => { const dto = { name: 'https://example.com/path/to/page' }; const normalized = 'example.com'; mockRepo.createQueryBuilder.mockReturnValue({ where: jest.fn().mockReturnThis(), getOne: jest.fn().mockResolvedValue(null), }); mockRepo.create.mockReturnValue({ name: normalized }); mockRepo.save.mockResolvedValue({ name: normalized }); await service.create(dto); expect(repo.create).toHaveBeenCalledWith({ name: normalized }); }); it('должен нормализовать wildcard домен *.example.com → example.com', async () => { const dto = { name: '*.example.com' }; const normalized = 'example.com'; mockRepo.createQueryBuilder.mockReturnValue({ where: jest.fn().mockReturnThis(), getOne: jest.fn().mockResolvedValue(null), }); mockRepo.create.mockReturnValue({ name: normalized }); mockRepo.save.mockResolvedValue({ name: normalized }); await service.create(dto); expect(repo.create).toHaveBeenCalledWith({ name: normalized }); }); it('должен игнорировать комментарии в домене', async () => { const dto = { name: 'example.com # это комментарий' }; const normalized = 'example.com'; mockRepo.createQueryBuilder.mockReturnValue({ where: jest.fn().mockReturnThis(), getOne: jest.fn().mockResolvedValue(null), }); mockRepo.create.mockReturnValue({ name: normalized }); mockRepo.save.mockResolvedValue({ name: normalized }); await service.create(dto); expect(repo.create).toHaveBeenCalledWith({ name: normalized }); }); }); describe('findAll', () => { it('должен вернуть домены с пагинацией', async () => { const mockDomains = [ { id: 1, name: 'ya.ru' }, { id: 2, name: 'vk.com' }, ]; mockRepo.findAndCount.mockResolvedValue([mockDomains, 100]); const result = await service.findAll(1, 10); expect(result).toEqual({ data: mockDomains, total: 100, }); expect(repo.findAndCount).toHaveBeenCalledWith({ take: 10, skip: 0, order: { id: 'DESC' }, }); }); it('должен использовать значения по умолчанию для пагинации', async () => { mockRepo.findAndCount.mockResolvedValue([[], 0]); await service.findAll(); expect(repo.findAndCount).toHaveBeenCalledWith({ take: 10, skip: 0, order: { id: 'DESC' }, }); }); }); describe('findAllUnpaginated', () => { it('должен вернуть все домены без пагинации', async () => { const mockDomains = [ { id: 1, name: 'ya.ru' }, { id: 2, name: 'vk.com' }, ]; mockRepo.find.mockResolvedValue(mockDomains); const result = await service.findAllUnpaginated(); expect(result).toEqual(mockDomains); expect(repo.find).toHaveBeenCalledWith({ order: { name: 'ASC' } }); }); }); describe('findOne', () => { it('должен вернуть домен по ID', async () => { const mockDomain = { id: 1, name: 'ya.ru' }; mockRepo.findOneBy.mockResolvedValue(mockDomain); const result = await service.findOne(1); expect(result).toEqual(mockDomain); expect(repo.findOneBy).toHaveBeenCalledWith({ id: 1 }); }); }); describe('remove', () => { it('должен удалить домен по ID', async () => { mockRepo.delete.mockResolvedValue({ affected: 1 }); await service.remove(1); expect(repo.delete).toHaveBeenCalledWith(1); }); }); describe('removeAll', () => { it('должен удалить все домены', async () => { mockRepo.clear.mockResolvedValue(undefined); const result = await service.removeAll(); expect(result).toEqual({ success: true }); expect(repo.clear).toHaveBeenCalledTimes(1); }); }); describe('createMany', () => { it('должен создать несколько доменов', async () => { const domains = ['ya.ru', 'vk.com', 'ok.ru']; mockRepo.find.mockResolvedValue([]); mockRepo.create.mockReturnValue({ name: 'ya.ru' }); mockRepo.save.mockResolvedValue({}); const result = await service.createMany(domains); expect(result.count).toBeGreaterThan(0); expect(repo.save).toHaveBeenCalled(); }); it('должен пропустить существующие домены', async () => { const domains = ['ya.ru', 'vk.com']; const existing = [{ id: 1, name: 'ya.ru' }]; mockRepo.find.mockResolvedValue(existing); const result = await service.createMany(domains); expect(result.count).toBe(1); // Только vk.com будет создан }); it('должен вернуть 0, если все домены уже существуют', async () => { const domains = ['ya.ru', 'vk.com']; const existing = [ { id: 1, name: 'ya.ru' }, { id: 2, name: 'vk.com' }, ]; mockRepo.find.mockResolvedValue(existing); const result = await service.createMany(domains); expect(result.count).toBe(0); }); it('должен вернуть 0 для пустого массива', async () => { const result = await service.createMany([]); expect(result.count).toBe(0); expect(repo.find).not.toHaveBeenCalled(); }); it('должен нормализовать домены при массовом создании', async () => { const domains = ['*.EXAMPLE.com', 'https://vk.com/path', ' ok.ru ']; mockRepo.find.mockResolvedValue([]); mockRepo.create.mockReturnValue({ name: 'example.com' }); mockRepo.save.mockResolvedValue({}); await service.createMany(domains); expect(repo.create).toHaveBeenCalled(); }); }); });