Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
caminhoControlador: string,
declaracoes: Declaracao[]
): [string, { [key in MetodoHttpOpenApi]?: RotaOpenApi }] {
this.erros = [];
const descritivoControlador: { [key in MetodoHttpOpenApi]?: RotaOpenApi } = {};
const rotaRelativa = caminhoControlador
.replace(this.diretorioRotas, '')
Expand Down Expand Up @@ -228,6 +227,7 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
}

async documentar() {
this.erros = [];
const rotasEControladores = await this.encontrarControladores();
const documento: DocumentoOpenApi = {
openapi: '3.0.0',
Expand Down
43 changes: 39 additions & 4 deletions fontes/interface-linha-comando/documentar/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
import { AutoDocumentador } from "../../infraestrutura/auto-documentacao/auto-documentador";
import * as sistemaArquivos from 'fs';
import caminho from 'path';
import { yellow } from 'chalk';

export async function documentar() {
// TODO: Terminar após finalizar auto-documentador.
import { AutoDocumentador } from '../../infraestrutura/auto-documentacao/auto-documentador';

/**
* Comando `liquido documentar`.
*
* Lê as rotas do projeto e gera um arquivo `openapi.json` na raiz do
* projeto com a especificação OpenAPI correspondente.
*
* O comando sempre termina com um resultado explícito no console:
* - O caminho do arquivo gerado e o número de rotas documentadas; ou
* - O motivo de nenhum arquivo ter sido gerado (nenhuma rota encontrada
* no diretório esperado), além de avisos de análise, se houver.
*
* @param caminhoSaida Caminho do arquivo de saída. Quando não informado,
* usa `openapi.json` na raiz do projeto (diretório atual).
*/
export async function documentar(caminhoSaida?: string): Promise<void> {
const autoDocumentador = new AutoDocumentador();
await autoDocumentador.documentar();
const documento = await autoDocumentador.documentar();

for (const erro of autoDocumentador.erros) {
console.log(yellow(`Aviso: ${erro.message}`));
}

const rotasDocumentadas = Object.keys(documento.paths || {});

Check warning on line 29 in fontes/interface-linha-comando/documentar/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (rotasDocumentadas.length === 0) {
console.log(`Nenhum arquivo de rota (.delegua) foi encontrado em: ${autoDocumentador.diretorioRotas}`);
console.log('Nenhum arquivo de documentação foi gerado.');
return;
}

const caminhoArquivoSaida = caminhoSaida || caminho.join(process.cwd(), 'openapi.json');
sistemaArquivos.writeFileSync(caminhoArquivoSaida, JSON.stringify(documento, undefined, 4) + '\n');

console.log(
`Documentação OpenAPI gerada com ${rotasDocumentadas.length} rota(s) em: ${caminhoArquivoSaida}`
);
}
91 changes: 91 additions & 0 deletions testes/interface-linha-comando/documentar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as sistemaArquivos from 'fs';
import * as so from 'os';
import caminho from 'path';

import { documentar } from '../../fontes/interface-linha-comando/documentar';

/**
* Testes de regressão para o achado M7 (issue #114):
* `liquido documentar` não pode terminar em silêncio. Todo desfecho
* deve ser reportado explicitamente no console — o arquivo gerado e
* seu caminho, ou o motivo de nenhum arquivo ter sido gerado.
*/
describe('Comando documentar', () => {
let diretorioTemporario: string;
let espiaoConsole: jest.SpyInstance;
let espiaoCwd: jest.SpyInstance;

const saidaConsole = (): string =>
espiaoConsole.mock.calls.map((chamada) => String(chamada[0])).join('\n');

beforeEach(() => {
diretorioTemporario = sistemaArquivos.mkdtempSync(
caminho.join(so.tmpdir(), 'liquido-documentar-')
);
espiaoConsole = jest.spyOn(console, 'log').mockImplementation(() => {});
espiaoCwd = jest.spyOn(process, 'cwd').mockReturnValue(diretorioTemporario);
});

afterEach(() => {
espiaoConsole.mockRestore();
espiaoCwd.mockRestore();
sistemaArquivos.rmSync(diretorioTemporario, { recursive: true, force: true });
});

it('deve reportar explicitamente quando nenhuma rota é encontrada e não gerar arquivo', async () => {
await documentar();

const saida = saidaConsole();
expect(saida).toContain('Nenhum arquivo de rota');
expect(saida).toContain(caminho.join(diretorioTemporario, 'rotas/rest').replace(/\\/gi, '/'));
expect(saida).toContain('Nenhum arquivo de documentação foi gerado.');

expect(
sistemaArquivos.existsSync(caminho.join(diretorioTemporario, 'openapi.json'))
).toBe(false);
});

it('deve gerar openapi.json e reportar caminho e número de rotas', async () => {
const diretorioRota = caminho.join(diretorioTemporario, 'rotas', 'rest', 'artigos');
sistemaArquivos.mkdirSync(diretorioRota, { recursive: true });
sistemaArquivos.writeFileSync(
caminho.join(diretorioRota, 'inicial.delegua'),
[
'liquido.rotaGet(funcao(requisicao, resposta) {',
' resposta.json([])',
'})'
].join('\n')
);

await documentar();

const caminhoSaida = caminho.join(diretorioTemporario, 'openapi.json');
expect(sistemaArquivos.existsSync(caminhoSaida)).toBe(true);

const documento = JSON.parse(sistemaArquivos.readFileSync(caminhoSaida, 'utf-8'));
expect(documento.openapi).toBe('3.0.0');
expect(Object.keys(documento.paths)).toHaveLength(1);
expect(documento.paths['/artigos/']).toBeDefined();

const saida = saidaConsole();
expect(saida).toContain('Documentação OpenAPI gerada com 1 rota(s) em:');
expect(saida).toContain(caminhoSaida);
// Erros de análise acumulados não podem mais ser engolidos em silêncio.
expect(saida).toContain('Aviso:');
});

it('deve gravar no caminho de saída informado quando fornecido', async () => {
const diretorioRota = caminho.join(diretorioTemporario, 'rotas', 'rest');
sistemaArquivos.mkdirSync(diretorioRota, { recursive: true });
sistemaArquivos.writeFileSync(
caminho.join(diretorioRota, 'inicial.delegua'),
'liquido.rotaGet(funcao(requisicao, resposta) {\n resposta.json([])\n})'
);

const caminhoPersonalizado = caminho.join(diretorioTemporario, 'docs.json');
await documentar(caminhoPersonalizado);

expect(sistemaArquivos.existsSync(caminhoPersonalizado)).toBe(true);
expect(saidaConsole()).toContain(caminhoPersonalizado);
});
});
Loading