feat(sync): synchronize Hiboutik data through POST /sync
POST /sync starts the full synchronization in the background (202, or 409 when one is already running). Entities are synced in dependency order: brands, categories, suppliers, products (with versions and stocks), customers. Each row is synced independently so that a failure is logged without stopping the rest. Replaces the default AppService.
This commit is contained in:
89
src/services/hiboutik/category.hiboutik.service.spec.ts
Normal file
89
src/services/hiboutik/category.hiboutik.service.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
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> = {}): CategoryHiboutikDto =>
|
||||
({
|
||||
category_id: id,
|
||||
category_name: `Category ${id}`,
|
||||
category_id_parent: parentId,
|
||||
category_enabled: 1,
|
||||
category_enabled_www: 0,
|
||||
category_position: 3,
|
||||
...overrides,
|
||||
}) as CategoryHiboutikDto;
|
||||
|
||||
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('does not delete anything when Hiboutik returns no categories', async () => {
|
||||
categoryHiboutikRepository.getCategories.mockResolvedValue([]);
|
||||
|
||||
await service.syncCategories();
|
||||
|
||||
expect(categoryDatabaseRepository.deleteMissing).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user