Nordlet

← Blogue

Plano de Testes de Pré-Produção para uma API de Contabilidade

Um manual de fiabilidade para equipas de engenharia: escritas duplicadas, reutilização de chaves de idempotência, assinaturas de webhooks, eventos fora de ordem, valores decimais, âmbitos, fechos de período e recuperação.

Nordlet Team · · 19 min de leitura

Um código 201 Created de uma API de contabilidade não diz quase nada. Confirma apenas que o servidor respondeu. Não confirma que existe exatamente uma fatura, que os débitos são iguais aos créditos, que o número do documento foi atribuído uma única vez ou que uma nova tentativa após uma quebra de ligação não vai contabilizar uma segunda cópia. A maioria das duplicações de contabilidade em produção surge precisamente dessa lacuna: uma mutação foi efetuada com sucesso no servidor enquanto o cliente ficou sem saber o resultado e repetiu silenciosamente a operação com uma nova chave.

Este plano destina-se a equipas que estão a avaliar uma API de contabilidade antes de a ligar a livros de contabilidade reais. O foco está no comportamento perante falhas, e não nos cenários ideais (happy paths). Cada secção abaixo indica o que testar, o que validar e o que constitui um resultado positivo. O contexto de implementação utilizado é o da Nordlet, cuja API recorre a chaves de autenticação (bearer keys) com âmbito limitado, montantes em formato de string decimal, chaves de idempotência e webhooks assinados. Para obter a sintaxe exata dos endpoints e os contratos de resposta, consulte as páginas de Convenções da API e Primeiros passos em vez de confiar em pressupostos.

O que significa realmente "passar" nos testes

Um teste prova que uma integração contabilística é segura apenas quando verifica a semântica, e não os códigos de estado HTTP. Para cada cenário, o conjunto de testes deve confirmar:

  • Efeito secundário no negócio: existe exatamente um cliente, uma fatura, um lançamento de diário, uma atribuição de número ou uma transição de pagamento.
  • Integridade contabilística: os débitos são iguais aos créditos e a precisão monetária mantém-se após a serialização.
  • Comportamento de repetição: uma nova tentativa devolve o resultado original em vez de criar um novo registo.
  • Durabilidade: os registos de auditoria e os eventos de webhook existem assim que a operação é consolidada.
  • Falhas sem efeitos secundários: os pedidos rejeitados não consomem números de documento nem escrevem linhas parciais no razão (ledger).
  • Recuperação: um erro transitório pode ser repetido sem duplicação nem perda permanente de dados.

As diretrizes de API da Google definem claramente o limite: apenas as operações cuja execução repetida deixa o mesmo estado final são seguras para repetir, e uma API deve identificar quais são as operações idempotentes em vez de repetir cegamente qualquer falha.

Configurar um ambiente descartável e observável

Utilize uma empresa em ambiente de testes (sandbox), e não os livros de produção. Uma empresa em sandbox na Nordlet utiliza os mesmos módulos e comportamento de API que uma empresa real e está identificada como dados de teste, o que a torna segura para testes de volume elevado com duplicações e falhas. Esta flag é imutável após a criação, pelo que os livros de contabilidade reais não podem ser reclassificados posteriormente como dados de teste. Tenha em atenção que a utilização da sandbox é contabilizada e faturada exatamente da mesma forma que a utilização numa empresa real, pelo que deve dimensionar os seus testes de volume tendo em conta o preçário em que se encontra.

Antes de escrever qualquer teste, prepare:

  • Uma empresa dedicada em sandbox com isSandbox: true.
  • Uma chave de API de teste limitada apenas aos módulos em teste.
  • Um endpoint de webhook ou um túnel local que preserve o corpo bruto (raw body) do pedido.
  • Clientes, fornecedores e um plano de contas pré-configurados (seeded).
  • Pelo menos dois períodos contábeis: um aberto e um fechado/bloqueado.
  • Um relógio de teste ou fixture de datas, caso a implementação o suporte.
  • Um sistema de registo local que guarde cada tentativa de pedido, chave de idempotência, hash de resposta, entrega de webhook e resultado de reconciliação.

Não execute testes de duplicação em dados de teste partilhados (staging). Um cliente ou uma fatura perdidos de uma execução anterior tornam as validações posteriores ambíguas.

A sua fixture para cada mutação deve registar o método, o endpoint, o corpo completo, a chave de idempotência, o âmbito da chave, um ID de correlação do cliente, os carateres de data/hora de início e fim, o estado da resposta, os cabeçalhos, o corpo, o ID do pedido e se a ligação caiu antes de a resposta chegar. Utilize strings decimais fixas, tais como "75.0000", para valores monetários. A Nordlet representa os valores monetários como strings decimais com até quatro casas decimais, e a sua fatura de exemplo utiliza esse formato.

A outra componente da configuração é a injeção de falhas. O seu ambiente de testes deve ser capaz de atrasar uma resposta, interromper uma ligação TCP após o servidor ter recebido o pedido, devolver respostas sintéticas 429/500/502/503/504, devolver um código diferente de 2xx no recetor de webhooks, entregar o mesmo webhook duas vezes, reordenar eventos, corromper um byte do corpo de um webhook, alterar os dados de uma chave de idempotência mantendo a mesma chave, executar dois pedidos idênticos em simultâneo e bloquear um período entre a configuração e o lançamento contábil. Estes cenários valem mais do que uma centena de exemplos de casos ideais.

Consolidar o contrato antes de escrever a lógica de repetição

Gere ou obtenha o esquema OpenAPI e compare-o com o seu cliente. A Nordlet publica a sua especificação OpenAPI na referência da API e gera os seus SDKs com tipos a partir dessa mesma especificação, pelo que os testes baseados no esquema superam as suposições manuais.

Verifique os campos obrigatórios e os tipos de dados, os campos em string decimal, os formatos de data e carateres de data/hora, as ações e âmbitos permitidos, a estrutura do invólucro de erros, os esquemas de eventos de webhook, quais as mutações que aceitam uma chave de idempotência e quais os campos de resposta que identificam o objeto criado.

Critérios de aprovação. O cliente rejeita localmente cargas úteis inválidas sempre que o esquema o permite. As respostas do servidor correspondem ao esquema documentado. Decimais inválidos, datas incorretas e campos obrigatórios em falta produzem erros de validação estruturados. Não é tentada nenhuma nova repetição perante uma falha de validação determinística.

Modo de falha a detetar. Um cliente que converte "121.0000" num número de vírgula flutuante binário (float) e serializa um montante ligeiramente diferente mais tarde. Os números inteiros passam; as linhas de IVA e as quantidades fracionárias falham. Teste os valores monetários como strings de ponta a ponta.

Autorização e isolamento de inquilinos (tenants)

Utilize chaves separadas para leitura e escrita. Em seguida, teste toda a matriz: chave em falta, chave inválida, chave válida com âmbito de módulo insuficiente, chave válida com o âmbito correto, chave pertencente a outra empresa e tentativa de ler um objeto de uma empresa diferente.

Validações:

  • Credenciais em falta ou inválidas devolvem 401.
  • Uma chave válida sem a permissão necessária devolve 403.
  • Não é criado nenhum efeito secundário contabilístico em nenhuma das falhas.
  • Um pedido bem-sucedido não consegue ler nem alterar os registos de outra empresa.
  • As falhas de segurança nunca são repetidas automaticamente.

Critério de aprovação. Consegue provar tanto a correção da autorização como o isolamento de inquilinos através de leituras pós-teste, e não apenas através de códigos de estado.

Comprovar uma transação limpa

Execute um fluxo pequeno e determinístico: crie ou encontre um cliente de teste, crie uma fatura com montantes em string decimal, emita-a, leia a fatura e o respetivo efeito contabilístico, leia o registo de auditoria e receba o webhook. O exemplo de primeiros passos da Nordlet demonstra a emissão de faturas a alocar um número sem saltos, a verificar se o período está aberto e a lançar um movimento de diário equilibrado na mesma transação.

Confirme que a fatura atinge o estado esperado, que o número do documento é atribuído exatamente uma vez, que os débitos são iguais aos créditos, que os totais de IVA e de linha correspondem ao cálculo esperado de arredondamento simétrico para cima (half-up), que o registo de auditoria identifica o autor e a alteração, que o webhook é eventualmente entregue e que a resposta da API e uma leitura de confirmação concordam quanto ao ID e ao estado da fatura.

Uma distinção importante a reter: a chegada de um webhook não é prova de que a mutação foi consolidada. A resposta da API juntamente com uma nova leitura são as suas principais validações transacionais. O webhook prova a notificação a jusante e é testado separadamente.

Idempotência: mesma chave, mesmo corpo

Gere uma chave por operação comercial lógica, e não uma chave por tentativa de rede. Envie a mutação com a chave K e o corpo B, guarde o ID criado e, em seguida, envie exatamente o mesmo pedido novamente.

Confirme que a segunda resposta representa a mesma operação, que o ID do objeto é idêntico e que existe apenas um objeto de negócio, um lançamento, um número de documento e um efeito financeiro. A resposta repetida deve conter o cabeçalho de repetição documentado (x-idempotent-replay: true no caso da Nordlet) e a auditoria não deve mostrar nenhuma segunda mutação.

Existem duas regras de armazenamento que alteram o comportamento esperado do seu ciclo de repetição, pelo que deve testá-las diretamente: um erro 4xx armazenado é repetido como qualquer outra resposta, enquanto um erro 5xx não é armazenado de todo, pelo que uma nova tentativa após um erro do servidor reexecuta a operação em vez de devolver uma repetição. As chaves também expirarem (na Nordlet após 24 horas), o que limita o período durante o qual uma repetição fica disponível.

A documentação da Nordlet garante que a adição de um cabeçalho Idempotency-Key impede que um pedido repetido crie uma duplicação, e as convenções da API definem o cabeçalho de repetição exato e o requisito de resposta idêntica ao nível do byte. O princípio coincide com o modelo publicado pela Stripe: uma chave identifica a operação lógica, as repetições devolvem o resultado guardado e a reutilização de uma chave com parâmetros diferentes é rejeitada em vez de ser alterada silenciosamente.

Critério de aprovação. Existe exatamente uma transição de estado financeiro, independentemente do número de vezes que o cliente envia o mesmo pedido dentro da janela de repetição.

Idempotência: mesma chave, corpo alterado

Envie o pedido original com a chave K e o corpo B1, seguido de um segundo pedido com a chave K e o corpo B2, onde pelo menos um montante, cliente, data ou linha seja diferente.

Resultado esperado: o pedido é rejeitado com o erro de reutilização de chave documentado (a Nordlet devolve 422 idempotency_key_reuse), o registo original permanece inalterado, não aparece nenhum segundo registo, número ou mutação de auditoria, e o cliente não tenta "corrigir" gerando uma nova chave. Aceitar B2 silenciosamente tornaria as chaves de repetição inseguras.

Pedidos duplicados concorrentes

Dispare dois pedidos idênticos com a mesma chave no mesmo instante. Dependendo do comportamento documentado, um é executado enquanto o outro recebe um conflito de em execução (in-progress) — a Nordlet devolve 409 idempotency_in_progress — ou ambos acabam por observar o mesmo resultado.

Confirme que ambas as chamadas não conseguem criar efeitos financeiros independentes, que é atribuído no máximo um número de documento, que um conflito temporário é repetido com a mesma chave em vez de uma nova, que o recurso final está completo e legível, e que o registo de idempotência não fica bloqueado permanentemente.

Modo de falha. Uma biblioteca de repetição que gera um UUID novo por tentativa transforma uma operação lógica em várias escritas independentes, anulando totalmente a idempotência.

O resultado incerto após uma quebra de ligação

Esta é a simulação de produção mais importante de todas. Envie uma mutação com a chave K, deixe o servidor consolidá-la, interumpa a ligação antes de a resposta chegar, registe o resultado como desconhecido, repita o pedido com a mesma chave K e reconcilie através da leitura do recurso e dos dados de auditoria.

Confirme que a repetição devolve a resposta original ou a resposta de repetição documentada, que existe exatamente um efeito secundário financeiro, que o cliente marca a operação como concluída apenas após uma repetição bem-sucedida ou uma leitura de reconciliação, e que não é gerada nenhuma nova chave. Um timeout não significa que "nada aconteceu". A Stripe descreve a idempotência precisamente como proteção contra erros de ligação em que o servidor pode ter processado uma mutação antes de o cliente conseguir receber a resposta.

Uma política de repetição conservadora

As decisões de repetição dependem tanto do erro como do facto de a operação ser idempotente, e não apenas do método HTTP.

Geralmente repetíveis, mantendo a mesma chave: reinicialização de ligação ou timeout após uma mutação idempotente, 408, 429 após respeitar o cabeçalho retry-after, e 502/503/504. Uma resposta documentada de idempotência em curso pode ser repetida após um pequeno atraso.

Não repetíveis sem reconciliação: erros de validação 400/422, falhas de autenticação 401/403, 404 para um recurso em falta, conflitos de negócio 409 não relacionados com uma chave em curso, alteração da carga útil numa chave existente e rejeição por período bloqueado.

Confirme a aplicação de uma espera exponencial com variação aleatória (jitter), limites explícitos de tentativas máximas e tempo decorrido, respeito pelo cabeçalho retry-after para limites de frequência, a mesma chave mantida nas repetições de uma operação, uma nova chave para cada operação diferente e registos (logs) que separam a tentativa original das repetições.

Teste a limitação de frequência (rate limiting) de forma isolada. Ultrapasse o limite documentado — a Nordlet permite por predefinição 300 pedidos por minuto por chave de API, e alguns endpoints sem autenticação possuem os seus próprios limites inferiores por endereço — e confirme que o servidor devolve a resposta de limite excedido, que o cliente interpreta retry-after como um período de espera em vez de um erro, que faz uma pausa em vez de bombardear o servidor, e que uma repetição de mutação limitada reutiliza a chave original. Os cabeçalhos x-ratelimit-* indicam o orçamento restante, o que serve de excelente validação prévia. Mantenha os testes de limitação de frequência afastados das validações financeiras, a menos que o seu ambiente consiga distinguir uma tentativa limitada de uma tentativa consolidada.

Verificação de assinatura de webhooks

Crie um recetor que preserve os bytes brutos. Para cada evento: capture o corpo bruto, leia o cabeçalho x-nordlet-signature, calcule o HMAC-SHA256 esperado com o segredo de subscrição, compare utilizando uma função de tempo constante (constant-time function), analise o JSON apenas após a verificação, registe a entrega e devolva rapidamente um código 2xx após a aceitação duradoura. A Nordlet assina um HMAC sobre o corpo bruto, envia-o como sha256=<hex> e repete as entregas com espera exponencial. As diretrizes de webhooks da Stripe reforçam a necessidade de verificar o corpo bruto não modificado com uma comparação de tempo constante.

Execute testes negativos: corpo e segredo corretos, corpo correto com segredo errado, alteração de um byte no corpo, JSON volta a ser serializado com espaços em branco alterados, cabeçalho em falta, assinatura malformada, repetição de uma entrega antiga caso o esquema inclua um carater de data/hora, e assinatura válida entregue na subscrição errada.

Critérios de aprovação. Apenas são aceites o corpo bruto e o segredo corretos. Assinaturas inválidas recebem um código diferente de 2xx. Cargas úteis inválidas nunca entram na fila de contabilidade. Os segredos mantêm-se fora dos registos comuns. O gestor de eventos emite um aviso de receção apenas após o evento estar registado de forma duradoura.

Webhooks duplicados e fora de ordem

A fiabilidade da entrega é um problema distinto da idempotência da API, pelo que o recetor também deve ser idempotente. Entregue o mesmo evento duas vezes, entregue um evento sale_invoice.paid antes de um evento sale_invoice.issued, devolva um erro 500 após persistir mas antes de responder, e repita um evento que o consumidor já processou.

Confirme que as entregas duplicadas não duplicam pagamentos, alterações de estado ou ações de diário, que a chave de desduplicação utiliza o ID de entrega ou de evento do fornecedor quando este existe (ou uma chave composta documentada, nunca um carater de data/hora arbitrário), que os eventos fora de ordem nunca substituem um estado mais recente por um mais antigo, que o consumidor consegue obter o estado atual a partir da API quando a ordem não é suficiente e que as tentativas falhadas do consumidor são encaminhadas através de uma fila ou via de mensagens mortas (dead-letter). A Stripe avisa explicitamente que a ordem de entrega não é garantida e recomenda o armazenamento dos IDs dos eventos processados.

Mantenha o endpoint leve: valide a forma, verifique a assinatura, persista ou coloque em fila, confirme a receção e devolva um código 2xx. A reconciliação e as notificações pertencem ao processamento assíncrono. Teste esta situação fazendo com que o processamento a jusante demore mais tempo do que o limite de timeout da entrega; um recetor correto continua a confirmar a receção após o enfileiramento duradouro.

Períodos contábeis abertos e bloqueados

A Nordlet aplica o bloqueio de períodos no momento do lançamento contábil em diários manuais, fluxos de documentos e importações, pelo que deve testar este controlo em vários locais. Crie documentos equivalentes datados num período aberto, num período bloqueado, no limite imediatamente anterior ao bloqueio, no primeiro dia do período aberto seguinte e com uma data de documento válida mas uma data de lançamento inválida, caso a API separe ambas.

Para o caso do período bloqueado, confirme que o pedido é rejeitado com o erro de período bloqueado documentado — um erro 409 conflict na Nordlet — e que não são escritas linhas de diário, não é consumido nenhum número de documento, nenhum documento transita para o estado lançado e nenhum webhook de sucesso é disparado. A versão com o período aberto deve ser bem-sucedida com os mesmos valores financeiros.

Teste de concorrência. Inicie um lançamento contábil num período aberto, bloqueie o período a meio do pedido e confirme que a consolidação não consegue contornar o bloqueio. O critério de aprovação crítico: um lançamento consolidado nunca pode cair num período que estava bloqueado antes de a transação ser concluída.

Capacidade de auditoria e reconciliação

Para cada mutação bem-sucedida, confirme que o objeto de negócio existe, que o lançamento contabilístico existe com débitos iguais aos créditos, que o registo de auditoria existe, que o evento de webhook existe ou pode ser obtido, e que o seu registo de operação aponta para o ID do objeto do fornecedor com repetições que fazem referência à mesma operação lógica.

Para cada mutação rejeitada, confirme que não existe nenhum documento parcial, que nenhum número foi consumido, a menos que o contrato permita a reserva, que nenhuma linha de razão existe, que nenhum webhook de sucesso existe e que o erro contém um ID de pedido para efeitos de diagnóstico. As convenções da Nordlet descrevem a auditabilidade das mutações e os lançamentos equilibrados como garantias da plataforma, com o equilíbrio a ser assegurado por um gatilho (trigger) de base de dados diferido no momento da consolidação, e não apenas pelo código da aplicação. Transforme essas garantias em consultas de reconciliação pós-teste em vez de confiar cegamente na resposta HTTP.

Matriz principal de testes

Área Ação de teste Validação esperada Critério de aprovação
Autenticação Sem chave, chave inválida, âmbito insuficiente Erros 401/403 estruturados; sem efeito secundário Todas as tentativas rejeitadas não criam registo contabilístico
Esquema e valores Decimal inválido, data, campo obrigatório Erro de validação ao nível do campo Não ocorre nenhuma repetição ou escrita parcial
Lançamento normal Criar e emitir uma fatura num período aberto Um documento, um diário equilibrado, um registo de auditoria O estado lido e o razão coincidem
Repetição com mesma chave Repetir mutação idêntica, mesma chave e corpo Resultado original repetido; sem duplicação ID do objeto, número, efeito de diário e auditoria mantêm-se únicos
Corpo alterado, mesma chave Reutilizar chave com montante diferente Rejeição documentada por reutilização de chave Registo original inalterado; nada de novo escrito
Quebra de ligação Consolidar, interromper, repetir com mesma chave Resultado repetido Exatamente um efeito secundário financeiro
Período bloqueado Lançar documento datado num período bloqueado Rejeição por período bloqueado Sem diário, sem número, sem webhook de sucesso
Assinatura de webhook Segredo errado, corpo alterado Resposta diferente de 2xx do recetor Apenas o corpo bruto e o segredo corretos são aceites

O que faria em primeiro lugar

Se o tempo for escasso, execute os testes na ordem que permite detetar os erros mais dispendiosos. Comece pela repetição após quebra de ligação, uma vez que esse cenário isolado origina a maioria das duplicações em produção. Em seguida, teste o par de idempotência com a mesma chave e corpo alterado. Depois, os períodos bloqueados, visto que um lançamento num mês fechado constitui um problema de auditoria que só se manifesta tardiamente. A verificação de assinaturas e os webhooks fora de ordem vêm a seguir, e a matriz completa de esquemas e autorizações pode ser executada continuamente em CI assim que o cliente estabilizar.

Tudo o resto neste plano serve para apoiar estes cinco pontos. Consiga que passem com sucesso numa empresa em sandbox, com leituras de reconciliação a confirmar os efeitos secundários, e saberá que a integração é segura antes de tocar num único livro de contabilidade real. Pode criar uma empresa em sandbox e uma chave de teste com âmbito limitado em poucos minutos a partir da página Primeiros passos, e verificar de que módulos necessita através da lista de funcionalidades.

FAQ

Uma resposta 2xx é suficiente para confirmar que uma escrita contabilística foi bem-sucedida?

Não. Um código de estado confirma que o servidor respondeu, mas não que existem exatamente um documento, uma atribuição de número e um lançamento de diário equilibrado. Cada teste de mutação deve ser seguido por uma nova leitura do objeto, do respetivo efeito contabilístico e do registo de auditoria. O caso mais perigoso ocorre na direção oposta: uma operação que foi consolidada enquanto o cliente nunca chegou a receber a resposta.

Devo gerar uma nova chave de idempotência quando um pedido expira por timeout?

Não, e este é o erro que provoca a maioria das duplicações em produção. A chave identifica a operação comercial lógica, e não a tentativa de rede. Repita com a mesma chave e, em seguida, reconcilie lendo o recurso. Uma biblioteca de repetição que gera um novo UUID por tentativa transforma uma fatura pretendida em várias escritas independentes.

O que acontece se reutilizar uma chave de idempotência com uma carga útil diferente?

Uma API correta rejeita o pedido em vez de aplicar o novo corpo silenciosamente. A Nordlet devolve 422 idempotency_key_reuse e deixa o registo original inalterado. O seu cliente deve tratar isto como um erro de programação a corrigir, e não como uma condição a contornar gerando uma nova chave.

Os webhooks substituem a leitura do objeto criado?

Não. A entrega de um webhook prova a notificação a jusante, e não que a transação foi consolidada da forma esperada, além de que a ordem de entrega não é garantida. Verifique a assinatura sobre o corpo bruto, desduplique com base no ID de entrega ou de evento e obtenha o estado atual a partir da API sempre que a ordem dos eventos por si só não for suficiente para decidir o que foi alterado.

Como posso testar o lançamento num período contábil bloqueado?

Crie documentos equivalentes num período aberto e num período bloqueado, e verifique se o caso bloqueado é rejeitado sem criar linhas de diário, sem consumir número de documento, sem transição de estado e sem disparar webhook de sucesso. Em seguida, execute a versão concorrente: inicie um lançamento num período aberto e bloqueie o período a meio do pedido. Um lançamento consolidado nunca pode cair num período que estava bloqueado antes de a transação ser concluída.

Posso executar testes de duplicação de alto volume numa empresa em sandbox?

Sim. Uma empresa em sandbox comporta-se como uma empresa real, está identificada como dados de teste e é eliminada imediatamente em vez de aguardar pelo período de retenção, pelo que constitui o local adequado para testes destrutivos e de duplicação. A utilização da sandbox continua a ser contabilizada e faturada como a utilização real, pelo que deve planear o volume em vez de executar ciclos indefinidamente.