2025-10-28 09:58:44 +03:00
|
|
|
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
|
|
|
|
import { execa } from 'execa';
|
2026-04-01 19:03:28 +03:00
|
|
|
|
import { readFileSync } from 'fs';
|
2025-10-28 09:58:44 +03:00
|
|
|
|
import { setupTest } from '../helpers/setup.js';
|
|
|
|
|
|
import { FIXTURES } from '../helpers/fixtures.js';
|
|
|
|
|
|
import { join } from 'path';
|
|
|
|
|
|
import { fileExists } from '../../src/utils/file.js';
|
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
import { dirname } from 'path';
|
|
|
|
|
|
|
|
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
2026-04-01 19:03:28 +03:00
|
|
|
|
const pkg = JSON.parse(readFileSync(join(dirname(__filename), '../../package.json'), 'utf-8'));
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const __dirname = dirname(__filename);
|
|
|
|
|
|
const CLI_PATH = join(__dirname, '../../src/cli.ts');
|
|
|
|
|
|
|
|
|
|
|
|
describe('CLI', () => {
|
|
|
|
|
|
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('должен запуститься с корректными параметрами (локальный файл)', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
|
|
|
|
|
|
const { exitCode } = await execa('bun', [
|
|
|
|
|
|
'run',
|
|
|
|
|
|
CLI_PATH,
|
|
|
|
|
|
'--input',
|
|
|
|
|
|
FIXTURES.MINIMAL,
|
|
|
|
|
|
'--output',
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect(exitCode).toBe(0);
|
|
|
|
|
|
|
|
|
|
|
|
const generatedFile = join(outputPath, 'MinimalAPI.ts');
|
|
|
|
|
|
const exists = await fileExists(generatedFile);
|
|
|
|
|
|
expect(exists).toBe(true);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
test('должен генерировать монолит с кастомным именем файла', async () => {
|
2025-10-28 09:58:44 +03:00
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const customName = 'CustomApi';
|
|
|
|
|
|
|
|
|
|
|
|
const { exitCode } = await execa('bun', [
|
|
|
|
|
|
'run',
|
|
|
|
|
|
CLI_PATH,
|
|
|
|
|
|
'--input',
|
|
|
|
|
|
FIXTURES.MINIMAL,
|
|
|
|
|
|
'--output',
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
'--name',
|
|
|
|
|
|
customName,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect(exitCode).toBe(0);
|
|
|
|
|
|
|
|
|
|
|
|
const generatedFile = join(outputPath, `${customName}.ts`);
|
|
|
|
|
|
const exists = await fileExists(generatedFile);
|
|
|
|
|
|
expect(exists).toBe(true);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
test('должен генерировать split режим', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
|
|
|
|
|
|
const { exitCode } = await execa('bun', [
|
|
|
|
|
|
'run',
|
|
|
|
|
|
CLI_PATH,
|
|
|
|
|
|
'--input',
|
|
|
|
|
|
FIXTURES.MINIMAL,
|
|
|
|
|
|
'--output',
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
'--mode',
|
|
|
|
|
|
'split',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
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', 'index.ts'))).toBe(true);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
2025-10-28 09:58:44 +03:00
|
|
|
|
test('должен отображать версию с --version', async () => {
|
|
|
|
|
|
const { stdout } = await execa('bun', ['run', CLI_PATH, '--version']);
|
|
|
|
|
|
|
2026-04-01 19:03:28 +03:00
|
|
|
|
expect(stdout).toContain(pkg.version);
|
2025-10-28 09:58:44 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('должен отображать help с --help', async () => {
|
|
|
|
|
|
const { stdout } = await execa('bun', ['run', CLI_PATH, '--help']);
|
|
|
|
|
|
|
2026-06-30 07:59:52 +03:00
|
|
|
|
expect(stdout).toContain('Генерация TypeScript API клиента');
|
2025-10-28 09:58:44 +03:00
|
|
|
|
expect(stdout).toContain('--input');
|
|
|
|
|
|
expect(stdout).toContain('--output');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('обработка ошибок', () => {
|
|
|
|
|
|
test('должен выбросить ошибку без параметра --input', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await execa('bun', ['run', CLI_PATH, '--output', outputPath]);
|
|
|
|
|
|
throw new Error('Should have thrown');
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
expect(error.exitCode).not.toBe(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('должен выбросить ошибку без параметра --output', async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await execa('bun', ['run', CLI_PATH, '--input', FIXTURES.MINIMAL]);
|
|
|
|
|
|
throw new Error('Should have thrown');
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
expect(error.exitCode).not.toBe(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('должен выбросить ошибку для несуществующего файла', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
const nonexistentFile = join(tempDir, 'nonexistent.json');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await execa('bun', [
|
|
|
|
|
|
'run',
|
|
|
|
|
|
CLI_PATH,
|
|
|
|
|
|
'--input',
|
|
|
|
|
|
nonexistentFile,
|
|
|
|
|
|
'--output',
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
]);
|
|
|
|
|
|
throw new Error('Should have thrown');
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
expect(error.exitCode).not.toBe(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('должен выбросить ошибку для невалидного JSON', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await execa('bun', [
|
|
|
|
|
|
'run',
|
|
|
|
|
|
CLI_PATH,
|
|
|
|
|
|
'--input',
|
|
|
|
|
|
FIXTURES.INVALID,
|
|
|
|
|
|
'--output',
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
]);
|
|
|
|
|
|
throw new Error('Should have thrown');
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
expect(error.exitCode).not.toBe(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 30000);
|
2026-06-30 07:59:52 +03:00
|
|
|
|
|
|
|
|
|
|
test('должен показывать русскую ошибку для недоступного URL', async () => {
|
|
|
|
|
|
const outputPath = join(tempDir, 'output');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await execa('bun', [
|
|
|
|
|
|
'run',
|
|
|
|
|
|
CLI_PATH,
|
|
|
|
|
|
'--input',
|
|
|
|
|
|
'https://127.0.0.1:1/swagger.json',
|
|
|
|
|
|
'--output',
|
|
|
|
|
|
outputPath,
|
|
|
|
|
|
]);
|
|
|
|
|
|
throw new Error('Should have thrown');
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
expect(error.exitCode).not.toBe(0);
|
|
|
|
|
|
expect(error.stderr).toContain('Не удалось подключиться к OpenAPI спецификации');
|
|
|
|
|
|
expect(error.stderr).toContain('Проверьте доступность URL и сетевое подключение');
|
|
|
|
|
|
expect(error.stderr).not.toContain('Unable to connect');
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 30000);
|
2025-10-28 09:58:44 +03:00
|
|
|
|
});
|
2026-06-30 07:59:52 +03:00
|
|
|
|
});
|