# Aula 5.7 — Registros, interceptadores e testes

## 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`](../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

```ts
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

```ts
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:

```json
{
  "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:

```text
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

```ts
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`:

```ts
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

```ts
@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

```ts
@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

```text
          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:

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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:

```ts
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:

```ts
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:

```ts
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](../exemplos/aula-5.7/index.html).

### 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

- [NestJS — registros](https://docs.nestjs.com/techniques/logger)
- [NestJS — interceptadores](https://docs.nestjs.com/interceptors)
- [NestJS — testes](https://docs.nestjs.com/fundamentals/testing)
- [NestJS — ciclo de vida da requisição](https://docs.nestjs.com/faq/request-lifecycle)
