Engenharia de IA Aplicada à Programação Web
Aula 7 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 5.1 Introdução ao Node.js e NestJS
  2. 5.2 Módulos, controladores e provedores
  3. 5.3 REST, rotas e respostas HTTP
  4. 5.4 DTOs, pipes e validação
  5. 5.5 Erros, filtros e configuração
  6. 5.6 OpenAPI e documentação
  7. 5.7 Registros, interceptadores e testes
  8. 5.8 Compilação e projeto final

Módulo 5 · Aula 5.7

Aula 5.7 — Registros, interceptadores e testes

2 horas Teoria + laboratório Node.js, NestJS e APIs
Ver fonte Markdown

Identificação

  • Módulo: 5 — Node.js, NestJS e APIs
  • Duração: 2 horas
  • Tipo: teoria aplicada e laboratório
  • Entrega: observação de requisições e suíte essencial de testes
  • Laboratório: ../exemplos/aula-5.7/index.html

Introdução

Quando uma API falha, dizer “não funcionou” não é suficiente. Precisamos saber qual operação ocorreu, quanto tempo levou, qual status foi devolvido e como localizar os registros relacionados sem expor dados privados.

Também não é seguro depender apenas de testes manuais. Uma alteração pequena pode quebrar uma regra já concluída. Testes automatizados executam cenários repetíveis e avisam quando o comportamento observado diverge do esperado.

Um registro é uma evidência produzida durante a execução. Um interceptador (interceptor) envolve o processamento de uma operação antes e depois do controlador. Um teste prepara uma situação, executa o código e verifica o resultado.

Nesta aula responderemos:

  • console.log é suficiente em uma API?
  • registro estruturado é apenas JSON?
  • interceptador substitui filtro de exceção?
  • podemos registrar o corpo inteiro da requisição?
  • teste unitário precisa iniciar servidor?
  • teste de integração precisa usar MySQL?
  • cobertura alta garante qualidade?

Objetivos

Ao concluir a aula, você será capaz de:

  1. diferenciar registros, métricas e rastreamento distribuído;
  2. usar o Logger do NestJS com contexto;
  3. produzir registros estruturados e seguros;
  4. correlacionar eventos da mesma requisição;
  5. medir duração com um interceptador;
  6. diferenciar middleware, guarda, pipe, interceptador e filtro;
  7. testar serviços isoladamente;
  8. testar controladores com dependências substituídas;
  9. testar endpoints com uma aplicação NestJS real em memória;
  10. escolher casos normais, limites e falhas importantes.

Pré-requisitos

  • aulas 5.1 a 5.6 concluídas;
  • injeção de dependência;
  • exceções e filtros;
  • noção de Promise e async/await;
  • familiaridade com status HTTP.

1. Observabilidade em linguagem simples

Observabilidade é a capacidade de compreender o estado interno de um sistema a partir de sinais produzidos por ele.

Sinal Responde principalmente
registros o que aconteceu em um evento?
métricas quantas vezes e com qual tendência?
rastreamento por onde uma operação passou?

Nesta aula focaremos registros e correlação. Métricas e rastreamento distribuído serão aprofundados na etapa de produção.

2. Logger do NestJS

import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class TasksService {
  private readonly logger = new Logger(TasksService.name);

  create(input: CreateTaskDto): Task {
    const task = this.repository.add(input);
    this.logger.log(`Tarefa criada: ${task.id}`);
    return task;
  }
}

O contexto TasksService ajuda a localizar a origem do evento.

3. Níveis de registro

Nível Uso típico
fatal processo não pode continuar
error operação falhou e exige investigação
warn situação anormal, mas controlada
log evento normal relevante
debug detalhe útil durante diagnóstico
verbose detalhe muito fino e normalmente temporário

Não transforme todo evento em erro. Níveis incorretos criam alarmes inúteis.

4. Configuração por ambiente

const app = await NestFactory.create(AppModule, {
  logger: process.env.NODE_ENV === 'production'
    ? ['fatal', 'error', 'warn', 'log']
    : ['fatal', 'error', 'warn', 'log', 'debug'],
});

Desabilitar todo registro em produção remove evidências importantes. Prefira controlar níveis e conteúdo.

5. Registro estruturado

Texto livre é difícil de pesquisar. Uma estrutura estável facilita filtros:

{
  "event": "http.request.completed",
  "method": "POST",
  "path": "/api/tasks",
  "statusCode": 201,
  "durationMs": 18,
  "correlationId": "req-a81f"
}

No ConsoleLogger, a opção json: true produz registros do sistema em JSON. Eventos da aplicação também precisam manter campos coerentes.

6. O que não registrar

Não registre:

  • senhas e confirmações de senha;
  • tokens de acesso ou atualização;
  • chaves de API;
  • cabeçalho Authorization completo;
  • conteúdo privado de documentos;
  • dados pessoais sem necessidade e base legítima;
  • variáveis de ambiente completas;
  • corpo integral de toda requisição.

Registrar dados demais é risco de segurança, privacidade e custo.

7. Correlação

Um identificador de correlação conecta eventos da mesma operação:

requisição recebida  correlationId=req-a81f
serviço executado    correlationId=req-a81f
resposta enviada     correlationId=req-a81f

O identificador não é senha, mas ainda deve ser validado e ter tamanho limitado.

8. Middleware de correlação

export function correlationMiddleware(
  request: Request,
  response: Response,
  next: NextFunction,
): void {
  const incoming = request.header('X-Correlation-ID');
  const correlationId = isSafeCorrelationId(incoming)
    ? incoming
    : randomUUID();

  response.setHeader('X-Correlation-ID', correlationId);
  request.correlationId = correlationId;
  next();
}

O middleware prepara contexto antes de o NestJS selecionar a operação.

9. O que é um interceptador?

O interceptador recebe um ExecutionContext e um CallHandler:

intercept(
  context: ExecutionContext,
  next: CallHandler,
): Observable<unknown> {
  return next.handle();
}

next.handle() representa a continuação do processamento. Sem chamá-lo, o controlador não executa, a menos que o interceptador produza uma resposta conscientemente.

10. Interceptador de duração

@Injectable()
export class HttpLoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(HttpLoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const startedAt = performance.now();
    const http = context.switchToHttp();
    const request = http.getRequest<RequestWithCorrelation>();
    const response = http.getResponse<Response>();

    return next.handle().pipe(
      finalize(() => {
        this.logger.log(JSON.stringify({
          event: 'http.request.completed',
          method: request.method,
          path: request.route?.path ?? request.path,
          statusCode: response.statusCode,
          durationMs: Math.round(performance.now() - startedAt),
          correlationId: request.correlationId,
        }));
      }),
    );
  }
}

Use o modelo de rota quando possível para evitar registrar identificadores pessoais no caminho, como /users/83921.

11. Registro global

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: HttpLoggingInterceptor,
    },
  ],
})
export class AppModule {}

Registrar pelo contêiner permite injetar dependências no interceptador.

12. Interceptador e filtro de exceção

Recurso Papel principal
interceptador envolver execução, medir, transformar
filtro converter exceção que escapou em resposta

O interceptador pode observar a finalização. O filtro continua responsável pelo contrato seguro de erro.

13. Duração não é desempenho completo

Medir a duração ajuda, mas não explica sozinho a causa. Uma operação lenta pode depender de rede, banco, CPU, fila ou serviço externo. Não conclua sem evidência adicional.

14. O que é um teste automatizado?

Um teste possui três movimentos:

  1. preparar dados e dependências;
  2. executar uma unidade ou operação;
  3. verificar resultado e efeitos observáveis.

O padrão é conhecido como preparar, agir e verificar (Arrange, Act, Assert).

15. Pirâmide de testes da API

          poucos testes ponta a ponta
       alguns testes de integração HTTP
      muitos testes de serviços e regras

Testes menores são rápidos e localizam falhas. Testes HTTP verificam a composição real.

16. Ambiente de testes do NestJS

O pacote @nestjs/testing cria um módulo de teste:

const moduleRef = await Test.createTestingModule({
  providers: [TasksService, InMemoryTasksRepository],
}).compile();

const service = moduleRef.get(TasksService);

Isso usa o contêiner de injeção sem abrir uma porta HTTP.

17. Teste unitário de serviço

describe('TasksService', () => {
  it('cria uma tarefa aberta', () => {
    const repository = new InMemoryTasksRepository();
    const service = new TasksService(repository);

    const task = service.create({ title: 'Testar a API' });

    expect(task).toMatchObject({
      title: 'Testar a API',
      completed: false,
    });
  });
});

Esse teste exercita regra e repositório em memória. Não precisa de MySQL.

18. Substituição de dependência

const repository = {
  findById: jest.fn().mockReturnValue(undefined),
};

const service = new TasksService(repository);

expect(() => service.findOne('task-404'))
  .toThrow(NotFoundException);

Uma substituição controlada permite produzir situações difíceis sem depender de rede.

19. Teste de controlador

const tasksService = {
  list: jest.fn().mockReturnValue([]),
};

const moduleRef = await Test.createTestingModule({
  controllers: [TasksController],
  providers: [{ provide: TasksService, useValue: tasksService }],
}).compile();

const controller = moduleRef.get(TasksController);

expect(controller.list()).toEqual([]);
expect(tasksService.list).toHaveBeenCalledTimes(1);

O teste verifica tradução e delegação, não repete regras do serviço.

20. Teste HTTP de integração

const moduleRef = await Test.createTestingModule({
  imports: [AppModule],
}).compile();

const app = moduleRef.createNestApplication();
app.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();

await request(app.getHttpServer())
  .post('/api/tasks')
  .send({ title: 'Testar contrato HTTP' })
  .expect(201)
  .expect(({ body }) => {
    expect(body.title).toBe('Testar contrato HTTP');
  });

await app.close();

O servidor é criado em memória para o teste; não é necessário escolher uma porta.

21. Reproduza a configuração real

Se produção usa prefixo, pipes, filtros e interceptadores globais, o teste HTTP deve aplicar a mesma configuração. Extraia uma função compartilhada:

export function configureApplication(app: INestApplication): void {
  app.setGlobalPrefix('api');
  app.useGlobalPipes(createValidationPipe());
}

Isso reduz diferenças entre teste e execução real.

22. Casos mínimos por endpoint

Para POST /api/tasks:

  • entrada válida retorna 201;
  • título curto retorna 400;
  • campo desconhecido segue a política escolhida;
  • duplicidade retorna 409 quando aplicável;
  • segredo nunca aparece em resposta ou registro.

23. Testes devem ser independentes

Cada teste precisa preparar seu próprio estado. Não dependa da ordem:

beforeEach(() => {
  repository.clear();
});

Um teste que passa sozinho e falha no conjunto indica estado compartilhado indevido.

24. Cobertura

Cobertura mostra quais linhas ou ramos foram executados. Ela não confirma:

  • qualidade das verificações;
  • casos de negócio relevantes;
  • ausência de vulnerabilidade;
  • coerência da documentação;
  • comportamento de dependências reais.

Use cobertura como mapa, não como certificado.

25. Teste não deve registrar segredo

Dados de teste também podem aparecer em relatórios e sistemas de integração contínua. Use valores fictícios e verifique a ocultação:

expect(serializedEvent).not.toContain('DATABASE_PASSWORD');
expect(serializedEvent).not.toContain('Bearer token-real');

26. Roteiro de implementação

  1. escolher eventos realmente úteis;
  2. definir campos estáveis para registros;
  3. validar ou gerar correlação;
  4. criar interceptador de duração;
  5. ocultar dados sensíveis;
  6. testar serviço e falhas de regra;
  7. testar controlador com dependência substituída;
  8. criar testes HTTP de sucesso e validação;
  9. fechar a aplicação após cada conjunto;
  10. executar a suíte sem depender de ordem.

27. Laboratório guiado

Abra o laboratório de observação e testes.

Etapa 1 — Envie uma operação

Escolha sucesso, validação, ausência ou falha inesperada.

Etapa 2 — Acompanhe o ciclo

Observe correlação, duração, status e registro público seguro.

Etapa 3 — Execute a suíte

Ative casos e confira quais camadas cada teste cobre.

Etapa 4 — Introduza uma regressão

Altere o comportamento simulado e veja qual teste detecta a falha.

28. Erros comuns

Registrar tudo

Volume excessivo aumenta custo e risco sem melhorar o diagnóstico.

Registrar senha antes de ocultar

O vazamento já ocorreu no momento da escrita.

Medir somente sucesso

Falhas também precisam de status, duração e correlação.

Testar método privado

Prefira comportamento observável. Métodos privados podem mudar sem alterar o contrato.

Repetir implementação no teste

O teste pode reproduzir o mesmo erro e passar. Verifique entradas e saídas.

Usar o banco real em todo teste

Isso torna a suíte lenta e frágil. Use diferentes níveis conscientemente.

29. Exercícios

  1. crie um evento estruturado para GET /api/tasks;
  2. remova identificadores sensíveis de um caminho registrado;
  3. teste criação com título válido;
  4. teste falha para título curto;
  5. teste controlador com serviço substituído;
  6. crie teste HTTP para 404;
  7. explique por que cobertura de 100% pode esconder defeitos.

30. Desafio individual

Implemente observação segura de PATCH /api/tasks/:id:

  • correlação;
  • duração;
  • status final;
  • caminho normalizado;
  • nenhuma entrada sensível;
  • teste de sucesso;
  • teste de recurso ausente;
  • teste de validação.

31. Lista de verificação de conclusão

  • Diferencio registro, métrica e rastreamento.
  • Uso contexto no Logger.
  • Conheço os níveis de registro.
  • Não registro credenciais ou corpos indiscriminadamente.
  • Correlaciono eventos da mesma operação.
  • Sei o papel de um interceptador.
  • Testo regras sem abrir servidor.
  • Substituo dependências conscientemente.
  • Testo endpoints com configuração próxima da real.
  • Minha suíte não depende de ordem.

32. Rubrica da entrega

Critério Pontos
Registros estruturados 20
Segurança e correlação 20
Interceptador e duração 20
Testes unitários 20
Testes HTTP e independência 20
Total 100

33. Resumo

  • registros explicam eventos específicos;
  • correlação conecta evidências da mesma operação;
  • interceptadores envolvem execução e podem medir duração;
  • filtros continuam responsáveis por respostas de exceção;
  • testes pequenos verificam regras rapidamente;
  • testes HTTP confirmam a composição da aplicação;
  • cobertura orienta investigação, mas não garante qualidade.

Próxima aula

Na Aula 5.8, compilaremos a API, reuniremos evidências, documentaremos a execução local e concluiremos o projeto do Módulo 5.

Fontes oficiais