Files
api-codegen/tests/unit/generator.test.ts
S.Gromov fe5d3ae091 feat: сделать split-клиент режимом по умолчанию
- добавлен operationsTree для сборки полного клиента
- удален режим генерации both
- обновлена документация под npm SDK workflow
- поднята версия пакета до 4.0.0
2026-06-30 10:46:15 +03:00

381 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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('корректная генерация', () => {
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('должен обработать Bearer authentication', 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);
// Проверяем наличие методов для работы с токеном
expect(content).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);
});
});