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
13 changes: 11 additions & 2 deletions fontes/infraestrutura/auto-documentacao/auto-documentador.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,19 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
const codigo = atributos['codigo'] || atributos['código'];
const retorno: RespostaOpenApi = {};
for (const [nomeAtributo, valorAtributo] of Object.entries(atributos)) {
retorno.content![decoradoresValidosResposta[nomeAtributo] as any] = valorAtributo;
const nomeOpenApi = decoradoresValidosResposta[nomeAtributo];
if (nomeOpenApi === 'statusCode') {
continue;
}

if (nomeOpenApi === 'content') {
retorno.content = valorAtributo;
continue;
}

(retorno as Record<string, any>)[nomeOpenApi] = valorAtributo;
}

// delete retorno['statusCode'];
return [codigo, retorno];
}

Expand Down
208 changes: 208 additions & 0 deletions testes/infraestrutura/auto-documentador.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { Chamada, Decorador, Literal, Vetor } from '@designliquido/delegua/construtos';
import { Expressao } from '@designliquido/delegua/declaracoes';
import { ConstrutoInterface } from '@designliquido/delegua/interfaces';

import { AutoDocumentador } from '../../fontes/infraestrutura/auto-documentacao/auto-documentador';
import { MetodoHttpOpenApi } from '../../fontes/infraestrutura/auto-documentacao/metodo-http-open-api';
import { RotaOpenApi } from '../../fontes/infraestrutura/auto-documentacao/rota-open-api';
import { GeradorExpressoes } from '../../fontes/infraestrutura/utilidades/gerador-expressoes';

type ControladorDocumentado = [string, { [key in MetodoHttpOpenApi]?: RotaOpenApi }];

class AutoDocumentadorTeste extends AutoDocumentador {
constructor(private readonly controladores: ControladorDocumentado[] = []) {
super();
}

exporResolverConstrutoValorDecorador(construto: ConstrutoInterface): any {
return this.resolverConstrutoValorDecorador(construto);
}

exporResolverAtributosDecorador(decorador: Decorador): Record<string, any> {
return this.resolverAtributosDecorador(decorador);
}

exporResolverDecoradorDocumentacao(atributos: Record<string, any>): RotaOpenApi {
return this.resolverDecoradorDocumentacao(atributos);
}

exporResolverDecoradorResposta(atributos: Record<string, any>): any {
return this.resolverDecoradorResposta(atributos);
}

exporResolverDecorador(decorador: Decorador): any {
return this.resolverDecorador(decorador);
}

exporLerControlador(caminhoControlador: string, declaracoes: Expressao[]) {
return this.lerControlador(caminhoControlador, declaracoes);
}

protected async encontrarControladores() {
return this.controladores;
}
}

const literal = (valor: string) => new Literal(-1, -1, valor);

const decoradorDocumentacao = () =>
new Decorador(-1, -1, '@rest.documentacao', {
sumario: literal('Lista usuarios'),
descricao: literal('Retorna usuarios cadastrados'),
idOperacao: literal('listarUsuarios'),
etiquetas: new Vetor(-1, -1, [literal('usuarios'), literal('admin')])
});

describe('AutoDocumentador', () => {
it('deve resolver valores literais e vetores usados em decoradores', () => {
const autoDocumentador = new AutoDocumentadorTeste();
const vetor = new Vetor(-1, -1, [literal('usuarios'), literal('admin')]);

expect(autoDocumentador.exporResolverConstrutoValorDecorador(literal('ok'))).toBe(
'ok'
);
expect(autoDocumentador.exporResolverConstrutoValorDecorador(vetor)).toEqual([
'usuarios',
'admin'
]);
});

it('deve traduzir atributos de documentacao para campos OpenAPI', () => {
const autoDocumentador = new AutoDocumentadorTeste();
const atributos = autoDocumentador.exporResolverAtributosDecorador(
decoradorDocumentacao()
);

expect(autoDocumentador.exporResolverDecoradorDocumentacao(atributos)).toEqual({
summary: 'Lista usuarios',
description: 'Retorna usuarios cadastrados',
operationId: 'listarUsuarios',
tags: ['usuarios', 'admin']
});
});

it('deve registrar erro quando decorador de resposta nao informa codigo', () => {
const autoDocumentador = new AutoDocumentadorTeste();

expect(
autoDocumentador.exporResolverDecoradorResposta({
descricao: 'Resposta sem status'
})
).toBeNull();
expect(autoDocumentador.erros).toHaveLength(1);
expect(autoDocumentador.erros[0].message).toContain('@rest.resposta');
});

it('deve resolver decorador de resposta para o mapa de respostas OpenAPI', () => {
const autoDocumentador = new AutoDocumentadorTeste();
const formatos = {
'application/json': {
schema: {
type: 'object'
}
}
};

expect(
autoDocumentador.exporResolverDecoradorResposta({
codigo: 200,
descricao: 'Usuarios encontrados',
formatos
})
).toEqual([
200,
{
description: 'Usuarios encontrados',
content: formatos
}
]);
});

it('deve rejeitar decorador desconhecido de controlador', () => {
const autoDocumentador = new AutoDocumentadorTeste();
const decorador = new Decorador(-1, -1, '@rest.invalido', {});

expect(autoDocumentador.exporResolverDecorador(decorador)).toBeNull();
expect(autoDocumentador.erros).toHaveLength(1);
expect(autoDocumentador.erros[0].message).toContain('@rest.invalido');
});

it('deve ler um controlador e associar decoradores ao metodo HTTP', () => {
const autoDocumentador = new AutoDocumentadorTeste();
const gerador = new GeradorExpressoes();
const chamadaRota = new Chamada(
-1,
gerador.gerarAcessoMetodoOuPropriedade(
gerador.gerarReferenciaVariavel('liquido'),
'rotaGet'
),
[]
);
const decoradorResposta = new Decorador(-1, -1, '@rest.resposta', {
codigo: literal('200'),
descricao: literal('Usuarios encontrados')
});
const declaracao = new Expressao(chamadaRota, [
decoradorDocumentacao(),
decoradorResposta
]);
const caminhoControlador =
autoDocumentador.diretorioRotas + '/usuarios/inicial.delegua';

expect(
autoDocumentador.exporLerControlador(caminhoControlador, [declaracao])
).toEqual([
'/usuarios/',
{
get: {
summary: 'Lista usuarios',
description: 'Retorna usuarios cadastrados',
operationId: 'listarUsuarios',
tags: ['usuarios', 'admin'],
responses: {
'200': {
description: 'Usuarios encontrados'
}
}
}
}
]);
});

it('deve montar o documento OpenAPI com metadados e rotas encontradas', async () => {
const autoDocumentador = new AutoDocumentadorTeste([
[
'/usuarios/',
{
get: {
summary: 'Lista usuarios'
}
}
]
]);
autoDocumentador.nomeAplicacao = 'Minha API';
autoDocumentador.versao = '1.2.3';
autoDocumentador.descricao = 'API de exemplo';

await expect(autoDocumentador.documentar()).resolves.toEqual({
openapi: '3.0.0',
servers: [],
info: {
description: 'API de exemplo',
version: '1.2.3',
title: 'Minha API',
license: {
name: 'MIT',
url: 'https://github.com/DesignLiquido/liquido/LICENSE'
}
},
paths: {
'/usuarios/': {
get: {
summary: 'Lista usuarios'
}
}
}
});
});
});
68 changes: 68 additions & 0 deletions testes/infraestrutura/resposta-requisicao.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DeleguaFuncao } from '@designliquido/delegua/interpretador/estruturas';

import { Requisicao } from '../../fontes/infraestrutura/requisicao';
import { Resposta } from '../../fontes/infraestrutura/resposta';

const obterLexemaPrimeiroParametro = (
resposta: Resposta,
nomeMetodo: string
): string =>
(resposta.metodos[nomeMetodo] as DeleguaFuncao).declaracao.parametros[0].nome
.lexema;

describe('Testes dos descritores de requisicao e resposta', () => {
it('Resposta deve declarar propriedades e metodos de resposta do Express', () => {
const resposta = new Resposta();
const nomesPropriedades = resposta.propriedades.map(
(propriedade) => propriedade.nome.lexema
);

expect(nomesPropriedades).toEqual([
'destino',
'respostaJson',
'mensagem',
'statusHttp',
'valores',
'visao'
]);
expect(Object.keys(resposta.metodos).sort()).toEqual([
'enviar',
'json',
'lmht',
'redirecionar',
'status'
]);
expect(obterLexemaPrimeiroParametro(resposta, 'enviar')).toBe('mensagem');
expect(obterLexemaPrimeiroParametro(resposta, 'status')).toBe(
'statusHttp'
);
expect(obterLexemaPrimeiroParametro(resposta, 'lmht')).toBe(
'visaoEValores'
);
expect(obterLexemaPrimeiroParametro(resposta, 'json')).toBe('json');
expect(obterLexemaPrimeiroParametro(resposta, 'redirecionar')).toBe(
'destino'
);
});

it('Requisicao deve envelopar a requisicao Express e declarar propriedades esperadas', () => {
const requisicaoExpress = {
body: { nome: 'Liquido' },
params: { id: '1' },
query: { pagina: '2' }
};
const requisicao = new Requisicao(requisicaoExpress);
const nomesPropriedades = requisicao.propriedades.map(
(propriedade) => propriedade.nome.lexema
);

expect(requisicao.requisicaoExpress).toBe(requisicaoExpress);
expect(nomesPropriedades).toEqual([
'corpo',
'parametros',
'parametrosPesquisa',
'parametrosCaminho'
]);
expect(requisicao.metodos).toEqual({});
});
});
Loading