109 lines
4.5 KiB
TypeScript
109 lines
4.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||
import { ApiError } from '../../api/client';
|
||
import type { FileOut, UploadResult } from '../../types/api';
|
||
import { filesPath, hasPendingFiles, isUploadResult, listLegalEntityFiles, summarizeUpload, uploadFiles } from './files';
|
||
|
||
const result: UploadResult = {
|
||
accepted: [{ file_id: 'f1', job_id: 'j1', name: 'osv.xlsx' }],
|
||
rejected: [{ name: 'dup.xlsx', code: 'DUPLICATE_FILE', message: 'Файл уже загружен', file_id: 'f0' }],
|
||
};
|
||
|
||
describe('isUploadResult', () => {
|
||
it('отличает UploadResult от detail-ошибки', () => {
|
||
expect(isUploadResult(result)).toBe(true);
|
||
expect(isUploadResult({ detail: { code: 'ENTITY_NOT_IN_PERIMETER', message: 'x' } })).toBe(false);
|
||
expect(isUploadResult(null)).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('summarizeUpload', () => {
|
||
it('имена и причины', () => {
|
||
expect(summarizeUpload(result)).toEqual({
|
||
accepted: ['osv.xlsx'],
|
||
rejected: [{ name: 'dup.xlsx', reason: 'Файл уже загружен' }],
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('hasPendingFiles', () => {
|
||
const file = (status: FileOut['status']): FileOut => ({
|
||
id: status,
|
||
legal_entity_id: 'le',
|
||
original_name: null,
|
||
file_type: 'PDF',
|
||
status,
|
||
version_no: 1,
|
||
size_bytes: null,
|
||
uploaded_at: null,
|
||
consolidation_id: null,
|
||
job: null,
|
||
documents: [],
|
||
});
|
||
it('UPLOADED/PROCESSING — ждём', () => {
|
||
expect(hasPendingFiles([file('PROCESSED'), file('PROCESSING')])).toBe(true);
|
||
expect(hasPendingFiles([file('UPLOADED')])).toBe(true);
|
||
});
|
||
it('остальные — нет', () => {
|
||
expect(hasPendingFiles([file('PROCESSED'), file('ERROR')])).toBe(false);
|
||
expect(hasPendingFiles([])).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('filesPath', () => {
|
||
it('с консолидацией — маршрут консолидации, без неё — маршрут ЮЛ', () => {
|
||
expect(filesPath('le', 'c1')).toBe('/consolidations/c1/legal-entities/le/files');
|
||
expect(filesPath('le', null)).toBe('/legal-entities/le/files');
|
||
});
|
||
});
|
||
|
||
describe('uploadFiles', () => {
|
||
afterEach(() => vi.restoreAllMocks());
|
||
|
||
it('422 с UploadResult возвращается как результат (и с консолидацией)', async () => {
|
||
const client = await import('../../api/client');
|
||
vi.spyOn(client, 'apiFetch').mockRejectedValue(new ApiError(422, undefined, 'x', { accepted: [], rejected: result.rejected }));
|
||
await expect(uploadFiles('le', [new File(['a'], 'dup.xlsx')], 'c1')).resolves.toEqual({ accepted: [], rejected: result.rejected });
|
||
});
|
||
|
||
it('422 с detail и 409 пробрасываются', async () => {
|
||
const client = await import('../../api/client');
|
||
const error = new ApiError(409, 'CONSOLIDATION_NOT_EDITABLE', 'нет версии в работе', {
|
||
detail: { code: 'CONSOLIDATION_NOT_EDITABLE' },
|
||
});
|
||
vi.spyOn(client, 'apiFetch').mockRejectedValue(error);
|
||
await expect(uploadFiles('le', [new File(['a'], 'a.xlsx')], 'c1')).rejects.toBe(error);
|
||
});
|
||
|
||
it('консолидация — в пути, query нет, лишних полей формы нет', async () => {
|
||
const client = await import('../../api/client');
|
||
const spy = vi.spyOn(client, 'apiFetch').mockResolvedValue(result);
|
||
await uploadFiles('le', [new File(['a'], 'a.xlsx'), new File(['b'], 'b.pdf')], 'c1');
|
||
const [path, init] = spy.mock.calls[0];
|
||
expect(path).toBe('/consolidations/c1/legal-entities/le/files');
|
||
const form = init?.body as FormData;
|
||
expect(form.getAll('files')).toHaveLength(2);
|
||
const keys: string[] = [];
|
||
form.forEach((_, key) => keys.push(key));
|
||
expect(keys.every((k) => k === 'files')).toBe(true);
|
||
});
|
||
|
||
it('без консолидации — маршрут ЮЛ без query', async () => {
|
||
const client = await import('../../api/client');
|
||
const spy = vi.spyOn(client, 'apiFetch').mockResolvedValue(result);
|
||
await uploadFiles('le', [new File(['a'], 'a.xlsx')], null);
|
||
expect(spy.mock.calls[0][0]).toBe('/legal-entities/le/files');
|
||
});
|
||
});
|
||
|
||
describe('listLegalEntityFiles', () => {
|
||
afterEach(() => vi.restoreAllMocks());
|
||
it('стол консолидации и список ЮЛ', async () => {
|
||
const client = await import('../../api/client');
|
||
const spy = vi.spyOn(client, 'apiFetch').mockResolvedValue({ items: [], total: 0, limit: 100, offset: 0 });
|
||
await listLegalEntityFiles('le', 'c1');
|
||
await listLegalEntityFiles('le', null);
|
||
expect(spy.mock.calls[0][0]).toBe('/consolidations/c1/legal-entities/le/files?limit=100');
|
||
expect(spy.mock.calls[1][0]).toBe('/legal-entities/le/files?limit=100');
|
||
});
|
||
});
|