| Bellacosa Mainframe e as APIs |
☕ Um Café no Bellacosa Mainframe
🕯️ John Constantine e a API dos Condenados
Requests, Responses, HTTP, JSON, autenticação, autorização, Test Data, ambientes, segurança, automação e aquele 200 OK que jurava que estava tudo bem
Há uma regra que aprendi depois de muitos anos diante de terminais verdes, JCLs, CICS, dumps, arquivos VSAM e bancos de dados:
quando um sistema diz que está tudo bem depressa demais, comece a desconfiar.
John Constantine provavelmente concordaria.
Constantine não é exatamente o sujeito que você chama quando a torneira está vazando. Ele aparece quando a torneira começa a falar latim, o encanador desaparece e alguém encontra um pentagrama desenhado atrás da caixa-d'água.
Com APIs acontece algo semelhante.
Você manda:
GET /customers/42e recebe:
HTTP/1.1 200 OKO programador iniciante sorri.
Constantine acende um cigarro imaginário, olha para o monitor e pergunta:
— OK para quem?
Essa é a pergunta que guiará nossa investigação.
Porque uma API pode responder 200 OK enquanto devolve os dados errados. Pode autenticar João e entregar os registros de Maria. Pode registrar uma compra duas vezes. Pode diminuir o estoque três vezes. Pode devolver um CPF que jamais deveria estar naquela resposta.
E existe algo ainda mais diabólico.
Às vezes o sistema está certo...
...e o teste é que está errado.
Bem-vindo ao submundo de API Testing.
🕯️ CAPÍTULO 1 — O que diabos é uma API?
API significa:
Application Programming Interface.
Uma definição simples seria:
uma interface que permite que dois componentes de software se comuniquem seguindo regras previamente estabelecidas.
O exemplo clássico é o restaurante.
Temos:
CLIENTE
↓
GARÇOM
↓
COZINHAO cliente não entra na cozinha para preparar o prato.
Ele faz um pedido.
O garçom leva esse pedido à cozinha.
A cozinha trabalha.
O garçom retorna com o resultado.
No mundo computacional:
APLICAÇÃO
↓
API
↓
SERVIDORUma aplicação meteorológica poderia solicitar:
GET /weather?city=Itatibae receber:
{
"temp": 28,
"condition": "Cloudy"
}A aplicação não precisa saber como o servidor calculou aquilo.
Pode haver Python lá atrás.
Java.
Node.js.
Ou algo bem mais interessante para nós:
APP
│
▼
REST API
│
▼
API Gateway
│
▼
z/OS Connect
│
▼
CICS
│
▼
COBOL
│
├── Db2
├── VSAM
├── IMS
└── MQPara quem chamou a API, isso deveria ser transparente.
Esse é o primeiro princípio importante:
a API oferece um contrato, não uma visita guiada à implementação.
🔮 CAPÍTULO 2 — O contrato
Constantine conhece contratos.
Normalmente os dele envolvem demônios, almas e cláusulas que ninguém deveria assinar sem ler as letras pequenas.
APIs também possuem contratos.
Se estabelecemos:
GET /users/42podemos definir que a resposta bem-sucedida será:
200 OK
Content-Type: application/jsoncom:
{
"id": 42,
"name": "Asha",
"active": true
}Isso estabelece expectativas.
id deve existir.
id deve ser numérico.
name deve ser texto.
active deve ser booleano.
Portanto:
"id": 42e:
"id": "42"não são necessariamente equivalentes.
Um frontend permissivo talvez converta silenciosamente um para o outro.
O contrato não deveria depender dessa benevolência.
É justamente aí que entram schemas e especificações como OpenAPI: eles tornam parte dessas expectativas formalmente verificáveis.
🩸 CAPÍTULO 3 — Request e Response
Toda invocação começa com um pedido.
Por exemplo:
POST /users
Content-Type: application/jsonBody:
{
"name": "Asha",
"role": "Tester"
}O servidor poderia responder:
201 Createde:
{
"id": 42,
"name": "Asha",
"role": "Tester"
}Temos então:
REQUEST
↓
API
↓
PROCESSAMENTO
↓
RESPONSEO REQUEST é a pergunta.
O RESPONSE é a resposta.
Mas o investigador competente guarda ambos quando alguma coisa dá errado.
Porque perguntar:
"Por que a API falhou?"
sem saber exatamente qual request foi enviado é quase tão produtivo quanto perguntar a Constantine:
"Alguma coisa sobrenatural aconteceu ontem. Descubra."
🪦 CAPÍTULO 4 — Os cinco rituais HTTP
Os métodos mais comuns são:
GET → consultar
POST → criar/processar
PUT → substituir
PATCH → alterar parcialmente
DELETE → removerUma sequência CRUD poderia ser:
POST /users
GET /users/42
PATCH /users/42
DELETE /users/42Mas cuidado com uma simplificação comum.
HTTP não é CRUD.
Podemos perfeitamente encontrar:
POST /payments/123/captureNesse caso não estamos simplesmente dizendo "CREATE".
Estamos solicitando uma operação de negócio.
Existem ainda métodos como HEAD e OPTIONS, e os métodos permitidos também fazem parte da superfície que merece testes de segurança. A OWASP recomenda verificar métodos HTTP disponíveis e controles de acesso associados, inclusive tentando operações com métodos alternativos quando apropriado.
🚪 CAPÍTULO 5 — URL: o endereço da casa assombrada
Considere:
https://api.shop.com/v1/products/42?currency=BRLPodemos desmontá-la:
https://api.shop.com
│
└── Base URL
/v1/products/42
│
└── endpoint/recurso
42
│
└── path parameter
currency=BRL
│
└── query parameterE imediatamente surgem testes.
Produto existente:
42Produto inexistente:
999999999Valor estranho:
-1Moeda válida:
BRLMoeda inválida:
MORDORCampo ausente.
Campo vazio.
Valor enorme.
Caracteres especiais.
É aqui que o QA começa a se comportar como Constantine:
não pergunta somente o que deveria funcionar; pergunta também o que acontece quando alguém faz aquilo que não deveria.
🔑 CAPÍTULO 6 — Headers: símbolos desenhados na porta
Uma requisição pode carregar informações adicionais:
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJ...
X-Request-ID: abc-123Cada header possui uma função.
Content-Type informa o formato enviado.
Accept pode expressar o formato desejado.
Authorization transporta informações necessárias à autenticação/autorização conforme o mecanismo adotado.
E X-Request-ID, ou outro correlation ID equivalente, pode ser extremamente útil.
Imagine:
Browser
↓ abc-123
API Gateway
↓ abc-123
z/OS Connect
↓ abc-123
CICS
↓ abc-123
COBOL
↓ abc-123
MQQuando tudo explode às 03:17, você procura:
abc-123e consegue reconstruir a passagem daquela transação pelos diversos componentes.
Nos sistemas distribuídos, correlação é praticamente trabalho de detetive.
👹 CAPÍTULO 7 — Os números da besta... HTTP
Não é exatamente 666.
Temos famílias:
1xx → informacional
2xx → sucesso
3xx → redirecionamento
4xx → problema relacionado à requisição/cliente
5xx → falha do lado servidorAlguns velhos conhecidos:
200 OK
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
429 Too Many Requests
500 Internal Server Error
503 Service UnavailableMas existe uma distinção especialmente importante:
401 ≠ 403Simplificando:
401 — você não está adequadamente autenticado.
403 — sabemos quem você é, mas você não pode fazer aquilo.
É a diferença entre Constantine chegar à porta sem a chave e chegar com a chave correta de uma sala para a qual ele continua não tendo autorização.
🧿 CAPÍTULO 8 — Authentication não é Authorization
Temos duas perguntas:
AUTHENTICATION
Quem é você?
AUTHORIZATION
O que você pode fazer?Considere:
João → autenticado
Maria → autenticadaIsso não significa:
João → pode consultar dados privados de MariaTeste:
GET /users/43
Authorization: Bearer TOKEN_DO_JOAOSe 43 pertence a Maria e João não possui autorização para acessar aquele objeto, a aplicação precisa impedir o acesso.
Esse tipo de vulnerabilidade é importante o bastante para aparecer como Broken Object Level Authorization — BOLA no OWASP API Security Top 10.
O teste profissional, portanto, não pergunta apenas:
Tenho token?Pergunta:
TOKEN A pode acessar RECURSO A?
TOKEN A pode acessar RECURSO B?
TOKEN B pode acessar RECURSO A?
USER pode executar operação ADMIN?
ADMIN pode?
GET é permitido?
DELETE também?
PATCH?É uma matriz.
ATOR × RECURSO × AÇÃOA OWASP descreve justamente esse modelo ator–recurso–ação como uma forma útil de representar e automatizar testes de autorização.
🧪 CAPÍTULO 9 — Positive, Negative e Boundary Testing
Suponha:
idade permitida = 18 até 60Teste positivo:
25 → ACCEPTTeste negativo:
"CONSTANTINE" → REJECTAgora vêm os limites:
17
18
60
61Por quê?
Porque um humilde:
IF WS-AGE > 18em vez de:
IF WS-AGE >= 18pode mandar o jovem de exatamente 18 anos diretamente para o purgatório da regra de negócio.
Os bugs adoram morar nas fronteiras.
Isso vale para:
quantidade
tamanho de senha
valor financeiro
número de itens
datas
paginação
strings
limites de créditoSempre pergunte:
o que acontece exatamente antes, exatamente no limite e exatamente depois?
🧟 CAPÍTULO 10 — Um erro correto também é PASS
Esta é deliciosa.
Mandamos:
{}para uma operação que exige email.
Esperamos:
400ou talvez 422, conforme o contrato adotado.
E algo como:
{
"code": "EMAIL_REQUIRED",
"message": "Email is required",
"field": "email"
}Se a aplicação rejeitar corretamente a requisição inválida...
o teste passou.
Parece contraditório somente enquanto confundimos:
API retornou errocom:
TESTE falhouUm teste negativo pode perfeitamente produzir:
HTTP ERROR + TEST PASSO que seria ruim?
Enviar uma requisição inválida e receber:
500 Internal Server Errorquando o contrato previa uma rejeição controlada.
Ou pior:
400 Bad Requestmas metade da transação já foi gravada.
Constantine abre o banco de dados.
E encontra o demônio escondido lá.
🏦 CAPÍTULO 11 — COMMIT e ROLLBACK entram no exorcismo
Agora o mainframeiro começa a sorrir.
Imagine:
Criar pedido
↓
Debitar limite
↓
Diminuir estoque
↓
Criar pagamentoA terceira etapa falha.
O que acontece?
Se o sistema deveria tratar tudo como uma unidade lógica de trabalho, talvez esperemos:
ROLLBACKe não:
pedido criado
limite debitado
estoque inalterado
pagamento inexistenteEsse é o tipo de criatura que aparece quando testamos apenas o status HTTP.
Recebemos:
500e pensamos:
"Certo, falhou."
Não.
A investigação ainda não terminou.
Precisamos verificar o estado persistente.
💳 CAPÍTULO 12 — O demônio da duplicidade
Nosso BELLACARD recebe:
COMPRA = R$ 100O cliente chama a API.
Timeout.
Não sabe se a compra aconteceu.
Tenta novamente.
Agora imagine:
REQUEST #1 → R$100
REQUEST #2 → R$100Resultado:
R$200Temos um problema gigantesco.
Em sistemas financeiros, retries, idempotência e mecanismos de deduplicação precisam ser considerados seriamente.
O teste não deveria perguntar somente:
A compra funcionou?Mas:
O que acontece se a mesma operação chegar novamente?Em determinadas APIs podemos encontrar mecanismos como:
Idempotency-Key: 8d937...permitindo reconhecer repetições da mesma intenção.
A pergunta Constantine seria:
Quantas vezes o ritual foi executado quando o cliente jurava tê-lo invocado uma vez?
🪜 CAPÍTULO 13 — A escada dos ambientes
Agora chegamos à nova peça do quebra-cabeça:
LOCAL
↓
QA
↓
STAGE
↓
PRODUCTION-LIKEEm organizações reais podemos encontrar inúmeras variações:
DEV
INTEGRATION
QA
SIT
UAT
STAGING
PRE-PROD
PRODOs nomes são menos importantes que o princípio.
Queremos promover software por ambientes controlados até chegarmos à produção.
No mundo mainframe isso não deveria causar nenhum choque.
Temos há décadas coisas como:
CICSD
CICST
CICSQ
CICSPou:
DB2D
DB2T
DB2Q
DB2Pe datasets:
BELLACOSA.DEV.CLIENTES
BELLACOSA.QA.CLIENTES
BELLACOSA.PROD.CLIENTESDevOps não inventou a separação de ambientes.
Só mudou boa parte do vocabulário e das ferramentas.
🧙 CAPÍTULO 14 — BASE_URL: o DDNAME das APIs
Podemos ter:
BASE_URL=https://qa.api.example.com
TOKEN=***
USER_ID=42e o teste:
await request.get(
`${BASE_URL}/users/${USER_ID}`
);Em Stage:
BASE_URL=https://stage.api.example.comEm QA:
BASE_URL=https://qa.api.example.comO teste permanece essencialmente igual.
Isso deveria soar familiar ao programador COBOL.
Em vez de amarrar recursos diretamente ao programa, usamos mecanismos externos de configuração.
No JCL:
//CLIENTE DD DSN=BELLACOSA.QA.CLIENTES,DISP=SHRe posteriormente:
//CLIENTE DD DSN=BELLACOSA.PROD.CLIENTES,DISP=SHRO programa não precisa ser recompilado porque mudamos de dataset.
O mesmo princípio reaparece no mundo moderno:
separe código de configuração.
☠️ CAPÍTULO 15 — .env não é um círculo mágico
Um arquivo:
.env.qapode conter configuração de QA.
Ótimo.
Mas cuidado:
.env ≠ Secret VaultColocar:
PASSWORD=123456
TOKEN=abc123fora do source principal não torna magicamente esses valores seguros.
Segredos exigem tratamento apropriado durante armazenamento, distribuição, utilização, logging e rotação. A OWASP recomenda evitar segredos hardcoded, minimizar sua exposição e usar soluções adequadas de gestão de segredos conforme a arquitetura.
E jamais faça:
console.log(token);e depois mande o log inteiro para:
CI REPORT
LOG SERVER
EMAIL
SLACK
ARQUIVOVocê acabou de transformar observabilidade em distribuição de credenciais.
👥 CAPÍTULO 16 — O fantasma chamado USER_ID=42
Suponha que todos os testes utilizem:
USER_ID=42Pipeline A:
altera usuário 42Pipeline B:
remove usuário 42Pipeline C:
consulta usuário 42Agora temos:
A ──┐
B ──┼── USER 42
C ──┘Resultado?
Caos.
Um teste passa.
Outro falha.
Executamos novamente.
Passa.
Executamos amanhã.
Falha.
Nasceu o:
👻 FLAKY TEST
E então surge a frase mais perigosa do departamento de QA:
"Roda de novo."
Quando a segunda execução passa, todos vão embora.
Pouco a pouco ninguém acredita mais na suíte automatizada.
Isso destrói o valor do CI/CD.
🧬 CAPÍTULO 17 — Dê uma identidade própria para cada criatura
Uma alternativa é gerar dados únicos:
email = bellacosa+timestamp@example.testou utilizar:
UUIDAssim:
TEST A → USER A
TEST B → USER B
TEST C → USER CEm vez de:
TEST A ─┐
TEST B ─┼── USER 42
TEST C ─┘Isso aumenta isolamento e reduz colisões.
Mas abre outra porta.
🧹 CAPÍTULO 18 — Quem limpa o pentagrama depois?
Imagine 10.000 execuções criando:
USERS
CARTS
ORDERS
PAYMENTS
SESSIONSDepois de meses:
TEST-0000001
TEST-0000002
TEST-0000003
...
TEST-9827719QA virou um cemitério.
Por isso:
SETUP
↓
CREATE DATA
↓
EXECUTE
↓
ASSERT
↓
CLEANUPE há um detalhe crítico:
FAIL
↓
CLEANUPtambém.
Se a limpeza ocorrer somente quando o teste termina normalmente, justamente os testes problemáticos deixarão mais sujeira.
Constantine jamais abandonaria o pentagrama aberto no chão.
O QA também não deveria abandonar seus registros temporários.
🕵️ CAPÍTULO 19 — Nunca use o cadáver verdadeiro
A regra:
Never use real customer data.
merece atenção especial.
Copiar produção indiscriminadamente:
PROD DATABASE
↓
COPY
↓
QA DATABASEpode transportar:
nomes
documentos
endereços
telefones
emails
informações financeiras
dados pessoaispara um ambiente que talvez possua controles diferentes de produção.
Dependendo da necessidade, estratégias podem envolver dados sintéticos, mascaramento, anonimização ou tokenização, considerando requisitos técnicos e regulatórios aplicáveis.
E há outro teste maravilhoso:
compare aquilo que a interface mostra com aquilo que a API realmente devolve.
A tela mostra:
Nome: João
Cidade: ItatibaMas o JSON devolve:
{
"name": "João",
"city": "Itatiba",
"internalSalary": 12345,
"cpf": "...",
"internalRiskScore": 972,
"passwordResetToken": "..."
}A interface simplesmente ignorou os campos extras.
O atacante não ignora.
A OWASP recomenda examinar respostas brutas procurando informações sensíveis ou dados desnecessariamente expostos.
🔥 CAPÍTULO 20 — Não teste somente o Response
Chegamos à maior armadilha de todas.
O teste faz:
POST /ordersRecebe:
201 Createde declara:
PASSConstantine pergunta:
— Cadê o pedido?
Verificamos.
Existe.
— E o total?
Errado.
— E o estoque?
Diminuiu duas vezes.
— E o pagamento?
Foi criado três vezes.
— E o MQ?
Duas mensagens.
— E o banco?
COMMIT realizado.
O nosso glorioso:
201 CREATEDacabou de virar:
FAILPor isso um teste robusto pode precisar validar:
STATUS
HEADERS
BODY
SCHEMA
BUSINESS RULE
SIDE EFFECT
PERSISTENCE
SECURITY
PERFORMANCEEstamos deixando de testar HTTP.
Estamos testando o sistema através do HTTP.
Essa diferença é gigantesca.
⏱️ CAPÍTULO 21 — Performance e o demônio da média
Imagine:
média = 200 msParece excelente.
Só que:
90 usuários → 100 ms
10 usuários → 1.100 msA média esconde sofrimento.
Por isso aparecem medidas como:
p50
p95
p99Se p95 = 800 ms, aproximadamente 95% das observações ficaram nesse valor ou abaixo, segundo a metodologia empregada.
Isso nos ajuda a enxergar a cauda.
Também podemos observar:
throughput
timeouts
error rate
CPU
memory
disk I/O
database locks
connection pools
queues
threads
retriesE retries merecem carinho especial.
Um servidor começa a ficar lento.
Clientes repetem chamadas.
O servidor recebe ainda mais trabalho.
Fica mais lento.
Clientes repetem ainda mais.
Temos:
SLOW
↓
RETRY
↓
MORE LOAD
↓
SLOWER
↓
MORE RETRIES
↓
☠️O inferno distribuído encontrou seu próprio mecanismo de escala.
🤖 CAPÍTULO 22 — Automatizando o exorcismo
Podemos começar com algo pequeno em Playwright:
test('create user', async ({ request }) => {
const response = await request.post('/users', {
data: {
name: 'Asha'
}
});
expect(response.status()).toBe(201);
const body = await response.json();
expect(body.name).toBe('Asha');
});Já é melhor do que testar manualmente toda vez.
Mas podemos evoluir:
status
+
headers
+
schema
+
body
+
business rules
+
authorization
+
side effects
+
cleanupE executar automaticamente:
COMMIT
↓
BUILD
↓
UNIT TEST
↓
DEPLOY QA
↓
API TEST
↓
CONTRACT TEST
↓
AUTHORIZATION TEST
↓
INTEGRATION TEST
↓
SECURITY TEST
↓
REPORT
↓
QUALITY GATEA OWASP também descreve a automação de testes de autorização como útil para detectar regressões durante o desenvolvimento e os releases.
🖥️ CAPÍTULO 23 — Constantine entra na LPAR
Agora traduzimos tudo para a língua ancestral.
| API | Mainframe — analogia conceitual |
|---|---|
| Request | entrada da transação |
| JSON | estrutura de dados |
| Endpoint | ponto de entrada |
| HTTP Method | operação solicitada |
| HTTP Status | retorno técnico |
| Token | contexto de segurança |
| Authorization | RACF/controle de acesso conceitualmente |
| Request ID | correlation ID |
| Side Effect | resultado transacional |
| Database | Db2/IMS/VSAM |
| Queue | MQ |
| Rollback | desfazer LUW |
| Environment | região/subsistema/configuração |
| BASE_URL | configuração externa/endereço do destino |
| Test Data | massa de testes |
Não são equivalências tecnológicas perfeitas.
São pontes mentais.
E elas mostram uma coisa fascinante:
muito daquilo que o mercado chama hoje de arquitetura moderna possui problemas que mainframeiros conhecem há décadas.
Estado.
Concorrência.
Segurança.
Transações.
Auditoria.
Ambientes.
Configuração.
Recuperação.
Integridade.
🕯️ CAPÍTULO 24 — O exorcismo final
Constantine está diante do monitor.
O teste apresenta:
HTTP/1.1 200 OKTodo mundo comemora.
Ele não.
Primeira pergunta:
O usuário correto recebeu os dados?Sim.
O schema está correto?Sim.
A autorização foi verificada?Sim.
Existe informação sensível extra?Não.
O banco ficou consistente?Sim.
A operação aconteceu uma única vez?Sim.
As mensagens corretas foram produzidas?Sim.
O tempo ficou dentro do objetivo definido?Sim.
O teste utilizou dados isolados?Sim.
O ambiente era realmente QA?Sim.
Os segredos ficaram protegidos?Sim.
A massa criada foi limpa?Sim.
Agora Constantine olha novamente:
200 OKE finalmente diz:
— Agora talvez esteja OK.
☕ EPÍLOGO — CC 0000 também pode mentir
Aqui está a lição que une o velho mainframe às APIs modernas.
Nós já aprendemos há décadas que:
CC 0000não significa necessariamente:
NEGÓCIO CORRETOUm batch COBOL pode terminar lindamente com:
MAXCC=0000e ter atualizado 50.000 clientes com a tarifa errada.
Tecnicamente terminou.
Funcionalmente foi uma catástrofe.
Da mesma maneira:
HTTP 200 OKnão significa:
TEST PASSEDNosso verdadeiro ritual é:
REQUEST VÁLIDO
+
CONTRATO CORRETO
+
AUTENTICAÇÃO
+
AUTORIZAÇÃO
+
DADOS CORRETOS
+
REGRA DE NEGÓCIO
+
SIDE EFFECT CORRETO
+
ESTADO CONSISTENTE
+
ISOLAMENTO
+
SEGURANÇA
+
PERFORMANCE
+
CLEANUP
=
PASSE existe ainda uma última possibilidade, aquela que faria Constantine sorrir.
O sistema está correto.
O endpoint está correto.
O banco está correto.
A autorização está correta.
O COMMIT está correto.
Mas o pipeline acusa:
FAILEDPorque dois testes resolveram utilizar simultaneamente:
USER_ID=42Nesse momento o verdadeiro monstro não está na API.
Está na suíte de testes.
E é por isso que o bom investigador nunca pergunta apenas:
“A API falhou?”
Ele pergunta:
“O que exatamente falhou: o request, o contrato, a autenticação, a autorização, a aplicação, a regra de negócio, a persistência, o ambiente, a configuração, a massa de dados... ou o próprio teste?”
Essa é a diferença entre apertar SEND no Postman e realmente compreender API Testing.
Às 03:17, quando o dashboard fica vermelho e alguém aparece no War Room dizendo “mas estava retornando 200!”, talvez seja tarde demais para chamar John Constantine.
Mas ainda dá tempo de chamar um velho programador COBOL.
Ele provavelmente olhará para aquele 200 OK, lembrará de todos os CC 0000 que já viu mentirem durante a carreira e fará a única pergunta que realmente importa:
— Certo. Mas o que foi gravado no banco? ☕🕯️
Sem comentários:
Enviar um comentário