From 057c58f5006a1cc5cb42ae502f4b9c8abb2021ac Mon Sep 17 00:00:00 2001 From: LubuSeb <187313664+LubuSeb@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:56:04 +0200 Subject: [PATCH] Eleva cobertura de testes para 75% --- .../auto-documentacao/auto-documentador.ts | 13 +- .../infraestrutura/auto-documentador.test.ts | 208 ++++++++++++++++++ .../resposta-requisicao.test.ts | 68 ++++++ 3 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 testes/infraestrutura/auto-documentador.test.ts create mode 100644 testes/infraestrutura/resposta-requisicao.test.ts diff --git a/fontes/infraestrutura/auto-documentacao/auto-documentador.ts b/fontes/infraestrutura/auto-documentacao/auto-documentador.ts index b50e488..7bf3707 100644 --- a/fontes/infraestrutura/auto-documentacao/auto-documentador.ts +++ b/fontes/infraestrutura/auto-documentacao/auto-documentador.ts @@ -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)[nomeOpenApi] = valorAtributo; } - // delete retorno['statusCode']; return [codigo, retorno]; } diff --git a/testes/infraestrutura/auto-documentador.test.ts b/testes/infraestrutura/auto-documentador.test.ts new file mode 100644 index 0000000..bb100e1 --- /dev/null +++ b/testes/infraestrutura/auto-documentador.test.ts @@ -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 { + return this.resolverAtributosDecorador(decorador); + } + + exporResolverDecoradorDocumentacao(atributos: Record): RotaOpenApi { + return this.resolverDecoradorDocumentacao(atributos); + } + + exporResolverDecoradorResposta(atributos: Record): 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' + } + } + } + }); + }); +}); diff --git a/testes/infraestrutura/resposta-requisicao.test.ts b/testes/infraestrutura/resposta-requisicao.test.ts new file mode 100644 index 0000000..80a68af --- /dev/null +++ b/testes/infraestrutura/resposta-requisicao.test.ts @@ -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({}); + }); +});