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; beforeEach(async () => { const setup = await setupTest(); tempDir = setup.tempDir; cleanup = setup.cleanup; }); afterEach(async () => { await cleanup(); }); describe('корректная генерация', () => { test('должен по умолчанию создать split структуру', async () => { const outputPath = join(tempDir, 'output'); const config: GeneratorConfig = { inputPath: FIXTURES.MINIMAL, outputPath, fileName: 'TestApi', }; await generate(config); 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); }, 30000); test('должен добавлять русскую подпись в generated файлы', async () => { const outputPath = join(tempDir, 'output'); const config: GeneratorConfig = { inputPath: FIXTURES.MINIMAL, outputPath, fileName: 'TestApi', mode: 'single', }; await generate(config); const generatedFile = join(outputPath, 'TestApi.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).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'); const operationsTreeFile = join(outputPath, 'operations-tree.ts'); const indexContent = await readTextFile(indexFile); const httpClientContent = await readTextFile(httpClientFile); const operationsTreeContent = await readTextFile(operationsTreeFile); // Проверяем наличие основных элементов expect(indexContent).toContain('createApiClient'); 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"'); expect(httpClientContent).toContain('export class HttpClient'); 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'); }, 30000); test('должен обработать все HTTP методы', async () => { const outputPath = join(tempDir, 'output'); const config: GeneratorConfig = { inputPath: FIXTURES.VALID, outputPath, fileName: 'TestApi', mode: 'split', }; await generate(config); const operationsIndex = join(outputPath, 'operations', 'index.ts'); const content = await readTextFile(operationsIndex); // Проверяем что методы UserController переименованы // UserController_getAll -> getAll // UserController_create -> create expect(content).toContain('getAll'); expect(content).toContain('create'); expect(content).toContain('getById'); expect(content).toContain('update'); expect(content).toContain('deleteUsersId'); }, 30000); 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); test('должен генерировать типы для request и response', async () => { const outputPath = join(tempDir, 'output'); const config: GeneratorConfig = { inputPath: FIXTURES.VALID, outputPath, fileName: 'TestApi', mode: 'split', }; await generate(config); const generatedFile = join(outputPath, 'data-contracts.ts'); 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', mode: 'split', }; await generate(config); const generatedFile = join(outputPath, 'data-contracts.ts'); const content = await readTextFile(generatedFile); // Проверяем наличие enum expect(content).toContain('type UserRole'); expect(content).toContain('admin'); expect(content).toContain('user'); expect(content).toContain('guest'); }, 30000); test('должен генерировать hooks для авторизации', async () => { const outputPath = join(tempDir, 'output'); const config: GeneratorConfig = { inputPath: FIXTURES.WITH_AUTH, outputPath, fileName: 'TestApi', mode: 'split', }; await generate(config); const generatedFile = join(outputPath, 'http-client.ts'); const content = await readTextFile(generatedFile); // Проверяем наличие hooks для добавления авторизации expect(content).toContain('onRequest?: RequestInterceptor'); expect(content).toContain('secure?: boolean'); expect(content).not.toContain('baseApiParams'); expect(content).not.toContain('setSecurityData'); }, 30000); test('должен использовать baseUrl из servers', async () => { const outputPath = join(tempDir, 'output'); const config: GeneratorConfig = { inputPath: FIXTURES.VALID, outputPath, fileName: 'TestApi', mode: 'split', }; await generate(config); const generatedFile = join(outputPath, 'http-client.ts'); 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', mode: 'split', }; await generate(config); const generatedFile = join(outputPath, 'operations', 'index.ts'); 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); const generatedFile = join(outputPath, 'index.ts'); 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); const generatedFile = join(outputPath, 'index.ts'); 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', mode: 'split', }; await generate(config); const generatedFile = join(outputPath, 'operations', 'index.ts'); 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); const generatedFile = join(outputPath, 'index.ts'); const exists = await fileExists(generatedFile); expect(exists).toBe(true); }, 30000); }); });