import { Logger } from '@nestjs/common'; import { CategoryHiboutikService } from './category.hiboutik.service'; import { CategoryConverter } from '../../models/converter/category.converter'; import { CategoryHiboutikDto } from '../../models/hiboutikDTO/category.hiboutik.dto'; const hiboutikCategory = (id: number, parentId: number, overrides: Partial = {}): CategoryHiboutikDto => ({ category_id: id, category_name: `Category ${id}`, category_id_parent: parentId, category_enabled: 1, category_enabled_www: 0, category_position: 3, ...overrides, }); describe('CategoryHiboutikService', () => { const categoryHiboutikRepository = { getCategories: jest.fn() }; const categoryDatabaseRepository = { upsert: jest.fn(), setParent: jest.fn(), deleteMissing: jest.fn() }; let service: CategoryHiboutikService; let calls: string[]; beforeAll(() => { jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined); jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined); jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); }); beforeEach(() => { jest.clearAllMocks(); calls = []; categoryDatabaseRepository.upsert.mockImplementation((category: { hiboutikId: number }) => { calls.push(`upsert ${category.hiboutikId}`); return Promise.resolve({}); }); categoryDatabaseRepository.setParent.mockImplementation((id: number) => { calls.push(`parent ${id}`); return Promise.resolve({}); }); categoryDatabaseRepository.deleteMissing.mockResolvedValue(0); service = new CategoryHiboutikService( categoryHiboutikRepository as never, categoryDatabaseRepository as never, new CategoryConverter(), ); }); it('stores position and web visibility', async () => { categoryHiboutikRepository.getCategories.mockResolvedValue([hiboutikCategory(1, 0)]); await service.syncCategories(); expect(categoryDatabaseRepository.upsert).toHaveBeenCalledWith({ hiboutikId: 1, name: 'Category 1', enabled: true, position: 3, webEnabled: false, }); }); it('links parents once every category is saved, a child being listed before its parent', async () => { categoryHiboutikRepository.getCategories.mockResolvedValue([hiboutikCategory(2, 3), hiboutikCategory(3, 0)]); await service.syncCategories(); expect(calls).toEqual(['upsert 2', 'upsert 3', 'parent 2', 'parent 3']); expect(categoryDatabaseRepository.setParent).toHaveBeenCalledWith(2, 3); expect(categoryDatabaseRepository.setParent).toHaveBeenCalledWith(3, null); }); it('keeps syncing the other categories when one fails, then deletes the missing ones', async () => { categoryHiboutikRepository.getCategories.mockResolvedValue([hiboutikCategory(1, 0), hiboutikCategory(2, 0)]); categoryDatabaseRepository.upsert.mockRejectedValueOnce(new Error('boom')); await expect(service.syncCategories()).resolves.toBeUndefined(); expect(categoryDatabaseRepository.upsert).toHaveBeenCalledTimes(2); expect(categoryDatabaseRepository.deleteMissing).toHaveBeenCalledWith([1, 2]); }); it('deletes every category when Hiboutik answers with an empty list', async () => { categoryHiboutikRepository.getCategories.mockResolvedValue([]); await service.syncCategories(); expect(categoryDatabaseRepository.deleteMissing).toHaveBeenCalledWith([]); }); it('deletes nothing when the Hiboutik call fails', async () => { categoryHiboutikRepository.getCategories.mockRejectedValue(new Error('Request failed with status code 500')); await expect(service.syncCategories()).rejects.toThrow('500'); expect(categoryDatabaseRepository.deleteMissing).not.toHaveBeenCalled(); }); });