Тесты: часть 1
This commit is contained in:
143
tests/unit/cli.test.ts
Normal file
143
tests/unit/cli.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { execa } from 'execa';
|
||||
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);
|
||||
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);
|
||||
|
||||
test('должен генерировать с кастомным именем файла', async () => {
|
||||
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);
|
||||
|
||||
test('должен отображать версию с --version', async () => {
|
||||
const { stdout } = await execa('bun', ['run', CLI_PATH, '--version']);
|
||||
|
||||
expect(stdout).toContain('1.0.0');
|
||||
});
|
||||
|
||||
test('должен отображать help с --help', async () => {
|
||||
const { stdout } = await execa('bun', ['run', CLI_PATH, '--help']);
|
||||
|
||||
expect(stdout).toContain('Generate TypeScript API client');
|
||||
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);
|
||||
});
|
||||
});
|
||||
49
tests/unit/config.test.ts
Normal file
49
tests/unit/config.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { validateConfig, type GeneratorConfig } from '../../src/config.js';
|
||||
|
||||
describe('config', () => {
|
||||
describe('validateConfig', () => {
|
||||
test('должен пройти валидацию для корректной конфигурации', () => {
|
||||
const config: Partial<GeneratorConfig> = {
|
||||
inputPath: './openapi.json',
|
||||
outputPath: './output',
|
||||
fileName: 'Api',
|
||||
};
|
||||
|
||||
expect(() => validateConfig(config)).not.toThrow();
|
||||
expect(validateConfig(config)).toBe(true);
|
||||
});
|
||||
|
||||
test('должен пройти валидацию без опционального fileName', () => {
|
||||
const config: Partial<GeneratorConfig> = {
|
||||
inputPath: './openapi.json',
|
||||
outputPath: './output',
|
||||
};
|
||||
|
||||
expect(() => validateConfig(config)).not.toThrow();
|
||||
expect(validateConfig(config)).toBe(true);
|
||||
});
|
||||
|
||||
test('должен выбросить ошибку без inputPath', () => {
|
||||
const config: Partial<GeneratorConfig> = {
|
||||
outputPath: './output',
|
||||
};
|
||||
|
||||
expect(() => validateConfig(config)).toThrow('Input path is required');
|
||||
});
|
||||
|
||||
test('должен выбросить ошибку без outputPath', () => {
|
||||
const config: Partial<GeneratorConfig> = {
|
||||
inputPath: './openapi.json',
|
||||
};
|
||||
|
||||
expect(() => validateConfig(config)).toThrow('Output path is required');
|
||||
});
|
||||
|
||||
test('должен выбросить ошибку без обоих обязательных полей', () => {
|
||||
const config: Partial<GeneratorConfig> = {};
|
||||
|
||||
expect(() => validateConfig(config)).toThrow('Configuration validation failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
241
tests/unit/generator.test.ts
Normal file
241
tests/unit/generator.test.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
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('должен создать выходной файл', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
const config: GeneratorConfig = {
|
||||
inputPath: FIXTURES.MINIMAL,
|
||||
outputPath,
|
||||
fileName: 'TestApi',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.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, 'TestApi.ts');
|
||||
const content = await readTextFile(generatedFile);
|
||||
|
||||
// Проверяем наличие основных элементов
|
||||
expect(content).toContain('export class');
|
||||
expect(content).toContain('HttpClient');
|
||||
}, 30000);
|
||||
|
||||
test('должен обработать все HTTP методы', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
const config: GeneratorConfig = {
|
||||
inputPath: FIXTURES.VALID,
|
||||
outputPath,
|
||||
fileName: 'TestApi',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const content = await readTextFile(generatedFile);
|
||||
|
||||
// Проверяем что методы 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('delete');
|
||||
}, 30000);
|
||||
|
||||
test('должен генерировать типы для request и response', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
const config: GeneratorConfig = {
|
||||
inputPath: FIXTURES.VALID,
|
||||
outputPath,
|
||||
fileName: 'TestApi',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.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',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const content = await readTextFile(generatedFile);
|
||||
|
||||
// Проверяем наличие enum
|
||||
expect(content).toContain('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',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.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',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.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',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.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, 'TestApi.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, 'TestApi.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',
|
||||
};
|
||||
|
||||
await generate(config);
|
||||
|
||||
const generatedFile = join(outputPath, 'TestApi.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, 'TestApi.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
}, 30000);
|
||||
});
|
||||
});
|
||||
111
tests/unit/utils/file.test.ts
Normal file
111
tests/unit/utils/file.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { fileExists, readJsonFile, readTextFile, ensureDir, writeFileWithDirs } from '../../../src/utils/file.js';
|
||||
import { setupTest } from '../../helpers/setup.js';
|
||||
import { join } from 'path';
|
||||
import { writeFile, mkdir } from 'fs/promises';
|
||||
|
||||
describe('file utils', () => {
|
||||
let tempDir: string;
|
||||
let cleanup: () => Promise<void>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const setup = await setupTest();
|
||||
tempDir = setup.tempDir;
|
||||
cleanup = setup.cleanup;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
describe('fileExists', () => {
|
||||
test('должен вернуть true для существующего файла', async () => {
|
||||
const filePath = join(tempDir, 'test.txt');
|
||||
await writeFile(filePath, 'test content');
|
||||
|
||||
const exists = await fileExists(filePath);
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
test('должен вернуть false для несуществующего файла', async () => {
|
||||
const filePath = join(tempDir, 'nonexistent.txt');
|
||||
|
||||
const exists = await fileExists(filePath);
|
||||
expect(exists).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readTextFile', () => {
|
||||
test('должен прочитать содержимое текстового файла', async () => {
|
||||
const filePath = join(tempDir, 'test.txt');
|
||||
const content = 'Hello, World!';
|
||||
await writeFile(filePath, content);
|
||||
|
||||
const result = await readTextFile(filePath);
|
||||
expect(result).toBe(content);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readJsonFile', () => {
|
||||
test('должен прочитать и распарсить JSON файл', async () => {
|
||||
const filePath = join(tempDir, 'test.json');
|
||||
const data = { name: 'Test', value: 42 };
|
||||
await writeFile(filePath, JSON.stringify(data));
|
||||
|
||||
const result = await readJsonFile(filePath);
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
|
||||
test('должен выбросить ошибку для невалидного JSON', async () => {
|
||||
const filePath = join(tempDir, 'invalid.json');
|
||||
await writeFile(filePath, 'not a json');
|
||||
|
||||
await expect(readJsonFile(filePath)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureDir', () => {
|
||||
test('должен создать директорию', async () => {
|
||||
const dirPath = join(tempDir, 'test-dir');
|
||||
|
||||
await ensureDir(dirPath);
|
||||
|
||||
const exists = await fileExists(dirPath);
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
test('должен создать вложенные директории', async () => {
|
||||
const dirPath = join(tempDir, 'a', 'b', 'c');
|
||||
|
||||
await ensureDir(dirPath);
|
||||
|
||||
const exists = await fileExists(dirPath);
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
test('не должен падать если директория уже существует', async () => {
|
||||
const dirPath = join(tempDir, 'existing');
|
||||
await mkdir(dirPath);
|
||||
|
||||
// Не должно выбрасывать ошибку
|
||||
await ensureDir(dirPath);
|
||||
const exists = await fileExists(dirPath);
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeFileWithDirs', () => {
|
||||
test('должен записать файл и создать директории', async () => {
|
||||
const filePath = join(tempDir, 'nested', 'dir', 'file.txt');
|
||||
const content = 'test content';
|
||||
|
||||
await writeFileWithDirs(filePath, content);
|
||||
|
||||
const exists = await fileExists(filePath);
|
||||
expect(exists).toBe(true);
|
||||
|
||||
const readContent = await readTextFile(filePath);
|
||||
expect(readContent).toBe(content);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user