2025-10-28 09:58:44 +03:00
|
|
|
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
|
|
|
|
import { generate } from '../../src/generator.js';
|
|
|
|
|
|
import { setupTest } from '../helpers/setup.js';
|
|
|
|
|
|
import { FIXTURES } from '../helpers/fixtures.js';
|
|
|
|
|
|
import { join } from 'path';
|
|
|
|
|
|
import { fileExists, readTextFile } from '../../src/utils/file.js';
|
|
|
|
|
|
import type { GeneratorConfig } from '../../src/config.js';
|
|
|
|
|
|
|
|
|
|
|
|
describe('Generator', () => {
|
|
|
|
|
|
let tempDir: string;
|
|
|
|
|
|
let cleanup: () => Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
|
const setup = await setupTest();
|
|
|
|
|
|
tempDir = setup.tempDir;
|
|
|
|
|
|
cleanup = setup.cleanup;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
|
await cleanup();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('корректная генерация', () => {
|
2026-06-30 10:46:15 +03:00
|
|
|
|
test('должен по умолчанию создать split структуру', async () => {
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.MINIMAL,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 10:46:15 +03:00
|
|
|
|
expect(await fileExists(join(outputPath, 'index.ts'))).toBe(true);
|
|
|
|
|
|
expect(await fileExists(join(outputPath, 'http-client.ts'))).toBe(true);
|
|
|
|
|
|
expect(await fileExists(join(outputPath, 'operations-tree.ts'))).toBe(true);
|
|
|
|
|
|
expect(await fileExists(join(outputPath, 'operations', 'index.ts'))).toBe(true);
|
2025-10-28 09:58:44 +03:00
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
test('должен добавлять русскую подпись в generated файлы', async () => {
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.MINIMAL,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 10:46:15 +03:00
|
|
|
|
mode: 'single',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
|
|
|
|
|
const generatedFile = join(outputPath, 'TestApi.ts');
|
|
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
expect(content.startsWith('/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n\n/*')).toBe(true);
|
|
|
|
|
|
expect(content).toContain('АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ');
|
|
|
|
|
|
expect(content).toContain('Не редактируйте вручную: изменения будут перезаписаны.');
|
|
|
|
|
|
expect(content).toContain('Генератор: @gromlab/api-codegen');
|
|
|
|
|
|
expect(content).toContain('Репозиторий: https://gromlab.ru/gromov/api-codegen');
|
|
|
|
|
|
expect(content).not.toContain('THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен добавлять русскую подпись в split режиме', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.MINIMAL,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
mode: 'split',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
|
|
|
|
|
const generatedFile = join(outputPath, 'index.ts');
|
|
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
|
|
|
|
|
expect(content.startsWith('/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n\n/*')).toBe(true);
|
|
|
|
|
|
expect(content).toContain('АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ');
|
|
|
|
|
|
expect(content).toContain('Не редактируйте вручную: изменения будут перезаписаны.');
|
|
|
|
|
|
expect(content).toContain('Генератор: @gromlab/api-codegen');
|
|
|
|
|
|
expect(content).not.toContain('THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен генерировать корректную структуру кода', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.MINIMAL,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
mode: 'split',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
|
|
|
|
|
const indexFile = join(outputPath, 'index.ts');
|
|
|
|
|
|
const httpClientFile = join(outputPath, 'http-client.ts');
|
2026-06-30 10:46:15 +03:00
|
|
|
|
const operationsTreeFile = join(outputPath, 'operations-tree.ts');
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const indexContent = await readTextFile(indexFile);
|
|
|
|
|
|
const httpClientContent = await readTextFile(httpClientFile);
|
2026-06-30 10:46:15 +03:00
|
|
|
|
const operationsTreeContent = await readTextFile(operationsTreeFile);
|
2026-06-30 07:59:52 +03:00
|
|
|
|
|
2025-10-28 09:58:44 +03:00
|
|
|
|
// Проверяем наличие основных элементов
|
2026-06-30 07:59:52 +03:00
|
|
|
|
expect(indexContent).toContain('createApiClient');
|
2026-06-30 10:46:15 +03:00
|
|
|
|
expect(indexContent).toContain('export { operationsTree } from "./operations-tree"');
|
|
|
|
|
|
expect(indexContent).toContain('export type { OperationsTree } from "./operations-tree"');
|
|
|
|
|
|
expect(indexContent).toContain('export * as operations from "./operations"');
|
2026-06-30 07:59:52 +03:00
|
|
|
|
expect(httpClientContent).toContain('export class HttpClient');
|
2026-06-30 10:46:15 +03:00
|
|
|
|
expect(operationsTreeContent).toContain('export const operationsTree');
|
|
|
|
|
|
expect(operationsTreeContent).toContain('export type OperationsTree');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен генерировать operationsTree с группировкой по тегам', async () => {
|
|
|
|
|
|
const inputPath = join(tempDir, 'tagged.json');
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
|
|
|
|
|
|
await Bun.write(inputPath, JSON.stringify({
|
|
|
|
|
|
openapi: '3.0.0',
|
|
|
|
|
|
info: {
|
|
|
|
|
|
title: 'Tagged API',
|
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
|
},
|
|
|
|
|
|
paths: {
|
|
|
|
|
|
'/users': {
|
|
|
|
|
|
get: {
|
|
|
|
|
|
tags: ['users'],
|
|
|
|
|
|
operationId: 'UserController_getAll',
|
|
|
|
|
|
responses: {
|
|
|
|
|
|
200: { description: 'OK' },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
post: {
|
|
|
|
|
|
tags: ['users'],
|
|
|
|
|
|
operationId: 'UserController_create',
|
|
|
|
|
|
responses: {
|
|
|
|
|
|
201: { description: 'Created' },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
'/auth/login': {
|
|
|
|
|
|
post: {
|
|
|
|
|
|
tags: ['auth'],
|
|
|
|
|
|
operationId: 'AuthController_login',
|
|
|
|
|
|
responses: {
|
|
|
|
|
|
200: { description: 'OK' },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
await generate({
|
|
|
|
|
|
inputPath,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TaggedApi',
|
|
|
|
|
|
mode: 'split',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const content = await readTextFile(join(outputPath, 'operations-tree.ts'));
|
|
|
|
|
|
|
|
|
|
|
|
expect(content).toContain('import { create } from "./operations/create"');
|
|
|
|
|
|
expect(content).toContain('import { getAll } from "./operations/get-all"');
|
|
|
|
|
|
expect(content).toContain('import { login } from "./operations/login"');
|
|
|
|
|
|
expect(content).toContain('users: {');
|
|
|
|
|
|
expect(content).toContain('getAll: getAll');
|
|
|
|
|
|
expect(content).toContain('create: create');
|
|
|
|
|
|
expect(content).toContain('auth: {');
|
|
|
|
|
|
expect(content).toContain('login: login');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен обработать все HTTP методы', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.VALID,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const operationsIndex = join(outputPath, 'operations', 'index.ts');
|
|
|
|
|
|
const content = await readTextFile(operationsIndex);
|
2025-10-28 09:58:44 +03:00
|
|
|
|
|
|
|
|
|
|
// Проверяем что методы UserController переименованы
|
|
|
|
|
|
// UserController_getAll -> getAll
|
|
|
|
|
|
// UserController_create -> create
|
|
|
|
|
|
expect(content).toContain('getAll');
|
|
|
|
|
|
expect(content).toContain('create');
|
|
|
|
|
|
expect(content).toContain('getById');
|
|
|
|
|
|
expect(content).toContain('update');
|
2026-06-30 07:59:52 +03:00
|
|
|
|
expect(content).toContain('deleteUsersId');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2026-06-30 10:46:15 +03:00
|
|
|
|
test('должен генерировать индексный реэкспорт всех operations', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.VALID,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
mode: 'split',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
|
|
|
|
|
const content = await readTextFile(join(outputPath, 'operations', 'index.ts'));
|
|
|
|
|
|
|
|
|
|
|
|
expect(content).toContain('export { create } from "./create";');
|
|
|
|
|
|
expect(content).toContain('export { deleteUsersId } from "./delete-users-id";');
|
|
|
|
|
|
expect(content).toContain('export { getAll } from "./get-all";');
|
|
|
|
|
|
expect(content).toContain('export { getById } from "./get-by-id";');
|
|
|
|
|
|
expect(content).toContain('export { update } from "./update";');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2025-10-28 09:58:44 +03:00
|
|
|
|
test('должен генерировать типы для request и response', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.VALID,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'data-contracts.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем наличие типов
|
|
|
|
|
|
expect(content).toContain('User');
|
|
|
|
|
|
expect(content).toContain('CreateUserDto');
|
|
|
|
|
|
expect(content).toContain('UpdateUserDto');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен генерировать enum типы', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.VALID,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'data-contracts.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем наличие enum
|
2026-06-30 07:59:52 +03:00
|
|
|
|
expect(content).toContain('type UserRole');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
expect(content).toContain('admin');
|
|
|
|
|
|
expect(content).toContain('user');
|
|
|
|
|
|
expect(content).toContain('guest');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2026-06-30 23:52:06 +03:00
|
|
|
|
test('должен генерировать hooks для авторизации', async () => {
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.WITH_AUTH,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'http-client.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
2026-06-30 23:52:06 +03:00
|
|
|
|
// Проверяем наличие hooks для добавления авторизации
|
|
|
|
|
|
expect(content).toContain('onRequest?: RequestInterceptor');
|
|
|
|
|
|
expect(content).toContain('secure?: boolean');
|
|
|
|
|
|
expect(content).not.toContain('baseApiParams');
|
|
|
|
|
|
expect(content).not.toContain('setSecurityData');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен использовать baseUrl из servers', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.VALID,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'http-client.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем что baseUrl установлен
|
|
|
|
|
|
expect(content).toContain('https://api.example.com');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен применять хук onFormatRouteName', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.VALID,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'operations', 'index.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем что "Controller" удален из имен методов
|
|
|
|
|
|
expect(content).not.toContain('UserControllerGetAll');
|
|
|
|
|
|
expect(content).not.toContain('UserControllerCreate');
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем что методы названы корректно
|
|
|
|
|
|
expect(content).toContain('getAll');
|
|
|
|
|
|
expect(content).toContain('create');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('edge cases', () => {
|
|
|
|
|
|
test('должен обработать пустую спецификацию', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.EMPTY,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Генерация должна пройти успешно
|
|
|
|
|
|
await generate(config);
|
2026-06-30 10:46:15 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'index.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const exists = await fileExists(generatedFile);
|
|
|
|
|
|
expect(exists).toBe(true);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен обработать минимальную спецификацию', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.MINIMAL,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 10:46:15 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'index.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const exists = await fileExists(generatedFile);
|
|
|
|
|
|
expect(exists).toBe(true);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен обработать сложную спецификацию', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.COMPLEX,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
2026-06-30 07:59:52 +03:00
|
|
|
|
mode: 'split',
|
2025-10-28 09:58:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await generate(config);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'operations', 'index.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const content = await readTextFile(generatedFile);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем что все контроллеры присутствуют
|
|
|
|
|
|
expect(content).toContain('list'); // ProductController_list -> list
|
|
|
|
|
|
expect(content).toContain('create');
|
|
|
|
|
|
expect(content).toContain('getById');
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
test('должен обработать Unicode символы', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const config: GeneratorConfig = {
|
|
|
|
|
|
inputPath: FIXTURES.EDGE_CASES,
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
fileName: 'TestApi',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Генерация должна пройти успешно даже с Unicode
|
|
|
|
|
|
await generate(config);
|
2026-06-30 10:46:15 +03:00
|
|
|
|
const generatedFile = join(outputPath, 'index.ts');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const exists = await fileExists(generatedFile);
|
|
|
|
|
|
expect(exists).toBe(true);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
});
|
2026-06-30 07:59:52 +03:00
|
|
|
|
});
|