feat: сделать split-клиент режимом по умолчанию
- добавлен operationsTree для сборки полного клиента - удален режим генерации both - обновлена документация под npm SDK workflow - поднята версия пакета до 4.0.0
This commit is contained in:
@@ -63,7 +63,7 @@ describe('E2E Generation', () => {
|
||||
// 3. Проверка импорта (компиляция TypeScript)
|
||||
const testFile = join(tempDir, 'test-import.ts');
|
||||
const testCode = `
|
||||
import { createApiClient, HttpClient } from '${generatedFile}';
|
||||
import { createApiClient, HttpClient, operationsTree } from '${generatedFile}';
|
||||
import { getAll } from '${join(outputPath, 'operations', 'get-all.ts')}';
|
||||
|
||||
const api = createApiClient(new HttpClient(), {
|
||||
@@ -71,6 +71,8 @@ describe('E2E Generation', () => {
|
||||
getAll,
|
||||
},
|
||||
});
|
||||
const fullApi = createApiClient(new HttpClient(), operationsTree);
|
||||
fullApi.getAll;
|
||||
console.log('Import successful');
|
||||
`;
|
||||
|
||||
@@ -95,9 +97,10 @@ describe('E2E Generation', () => {
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const generatedFile = join(outputPath, 'MinimalAPI.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
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('повторная генерация (перезапись файлов)', async () => {
|
||||
|
||||
@@ -42,9 +42,10 @@ describe('CLI', () => {
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const generatedFile = join(outputPath, 'MinimalAPI.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
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('должен генерировать монолит с кастомным именем файла', async () => {
|
||||
@@ -60,6 +61,8 @@ describe('CLI', () => {
|
||||
outputPath,
|
||||
'--name',
|
||||
customName,
|
||||
'--mode',
|
||||
'single',
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
@@ -86,29 +89,30 @@ describe('CLI', () => {
|
||||
expect(exitCode).toBe(0);
|
||||
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('должен генерировать both режим', async () => {
|
||||
test('должен отклонять удаленный both режим', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
|
||||
const { exitCode } = await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
'--input',
|
||||
FIXTURES.MINIMAL,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'CustomApi',
|
||||
'--mode',
|
||||
'both',
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(await fileExists(join(outputPath, 'CustomApi.ts'))).toBe(true);
|
||||
expect(await fileExists(join(outputPath, 'index.ts'))).toBe(true);
|
||||
expect(await fileExists(join(outputPath, 'operations', 'index.ts'))).toBe(true);
|
||||
try {
|
||||
await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
'--input',
|
||||
FIXTURES.MINIMAL,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--mode',
|
||||
'both',
|
||||
]);
|
||||
throw new Error('Should have thrown');
|
||||
} catch (error: any) {
|
||||
expect(error.exitCode).not.toBe(0);
|
||||
expect(error.stderr).toContain('Некорректный режим генерации');
|
||||
expect(error.stderr).toContain('split, single');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
test('должен отображать версию с --version', async () => {
|
||||
|
||||
@@ -45,5 +45,15 @@ describe('config', () => {
|
||||
|
||||
expect(() => validateConfig(config)).toThrow('Ошибка конфигурации');
|
||||
});
|
||||
|
||||
test('должен выбросить ошибку для удаленного both режима', () => {
|
||||
const config = {
|
||||
inputPath: './openapi.json',
|
||||
outputPath: './output',
|
||||
mode: 'both',
|
||||
};
|
||||
|
||||
expect(() => validateConfig(config as Partial<GeneratorConfig>)).toThrow('Доступные значения: split, single');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Generator', () => {
|
||||
});
|
||||
|
||||
describe('корректная генерация', () => {
|
||||
test('должен создать выходной файл', async () => {
|
||||
test('должен по умолчанию создать split структуру', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
const config: GeneratorConfig = {
|
||||
inputPath: FIXTURES.MINIMAL,
|
||||
@@ -31,9 +31,10 @@ describe('Generator', () => {
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
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 () => {
|
||||
@@ -42,6 +43,7 @@ describe('Generator', () => {
|
||||
inputPath: FIXTURES.MINIMAL,
|
||||
outputPath,
|
||||
fileName: 'TestApi',
|
||||
mode: 'single',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
@@ -91,12 +93,77 @@ describe('Generator', () => {
|
||||
|
||||
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 () => {
|
||||
@@ -123,6 +190,26 @@ describe('Generator', () => {
|
||||
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 = {
|
||||
@@ -235,7 +322,7 @@ describe('Generator', () => {
|
||||
|
||||
// Генерация должна пройти успешно
|
||||
await generate(config);
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
}, 30000);
|
||||
@@ -250,7 +337,7 @@ describe('Generator', () => {
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
}, 30000);
|
||||
@@ -285,7 +372,7 @@ describe('Generator', () => {
|
||||
|
||||
// Генерация должна пройти успешно даже с Unicode
|
||||
await generate(config);
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
}, 30000);
|
||||
|
||||
Reference in New Issue
Block a user