Desenvolvendo uma API de transações de cartão de crédito com validações
Recentemente precisei desenvolver uma API que realizava transações de cartões de crédito passando por diversas validações, como por exemplo, se o valor solicitado pelo vendedor no momento da compra é maior que o limite existente no cartão ou ainda, se o cartão está ou não bloqueado.
Levando esses pontos em consideração, temos que efetuar pelo menos 3 testes:
- Verificar se o valor da conta ultrapassa o limite disponível;
- Verificar se o cartão está ativo;
- Verificar se a transação foi aprovada e se as verificações acima retornaram em falso.
O JSON que será recebido pela nossa API será conforme abaixo:
{ "status": true, "number":123456, "limit":1000, "transaction":{ "amount":500 } }
Então vamos ao código:
Primeira coisa é instalar as dependências:
python3 -m pip install pytest flask
Agora vamos escrever os testes, para isso vou utilizar uma ferramenta chamada Pytest.
Arquivo: test_app.py
#!/usr/bin/python3 import os import tempfile import pytest from app import app @pytest.fixture def client(): app.config['TESTING'] = True client = app.test_client() yield client def test_valid_transaction(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) assert True == rv.get_json().get("aprovado") assert 500 == rv.get_json().get("novoLimite") def test_above_limit(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":1500 } } rv = client.post("/api/transaction",json=card) assert False == rv.get_json().get("aprovado") assert "Compra acima do limite" in rv.get_json().get("motivo") def test_blocked_card(client): card = { "status": False, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) assert False == rv.get_json().get("aprovado") assert "Cartao bloqueado" in rv.get_json().get("motivo")
Neste momento, vamos criar um arquivo chamado app.py que será a nossa API de fato, no entanto esse arquivo ainda não está completo e servirá apenas para ver se os testes estão funcionando.
#!/usr/bin/python3 from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/api/transaction",methods=["POST"]) def transacao(): response = {"aprovado":True,"novoLimite":10} return jsonify(response) if __name__ == '__main__': app.run(debug=True)
Para executar os testes execute o seguinte comando:
pytest
Obviamente todos os testes vão falhar, porém nosso objetivo é fazer eles darem certo.
Com os testes falhando a saída será parecida com essa:
============================================== test session starts ============================================== platform linux -- Python 3.6.5, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 rootdir: /home/alisson, inifile: collected 3 items blog/test_app.py FFF [100%] =================================================== FAILURES ==================================================== ____________________________________________ test_valid_transaction _____________________________________________ client = <FlaskClient > def test_valid_transaction(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) assert True == rv.get_json().get("aprovado") > assert 500 == rv.get_json().get("novoLimite") E AssertionError: assert 500 == 10 E + where 10 = ('novoLimite') E + where = {'aprovado': True, 'novoLimite': 10}.get E + where {'aprovado': True, 'novoLimite': 10} = <bound method JSONMixin.get_json of >() E + where <bound method JSONMixin.get_json of > = .get_json blog/test_app.py:28: AssertionError _______________________________________________ test_above_limit ________________________________________________ client = <FlaskClient > def test_above_limit(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":1500 } } rv = client.post("/api/transaction",json=card) > assert False == rv.get_json().get("approved") E AssertionError: assert False == None E + where None = ('approved') E + where = {'aprovado': True, 'novoLimite': 10}.get E + where {'aprovado': True, 'novoLimite': 10} = <bound method JSONMixin.get_json of >() E + where <bound method JSONMixin.get_json of > = .get_json blog/test_app.py:40: AssertionError _______________________________________________ test_blocked_card _______________________________________________ client = <FlaskClient > def test_blocked_card(client): card = { "status": False, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) > assert False == rv.get_json().get("approved") E AssertionError: assert False == None E + where None = ('approved') E + where = {'aprovado': True, 'novoLimite': 10}.get E + where {'aprovado': True, 'novoLimite': 10} = <bound method JSONMixin.get_json of >() E + where <bound method JSONMixin.get_json of > = .get_json blog/test_app.py:53: AssertionError =========================================== 3 failed in 3.06 seconds ============================================
Note que falharam no total 3 testes:
- test_valid_transaction
> assert 500 == rv.get_json().get("novoLimite") E AssertionError: assert 500 == 10
Nesse primeiro teste, era esperado que o novo limite do cartão fosse 500 e foi retornado 10.
- test_above_limit
> assert False == rv.get_json().get("aprovado") E AssertionError: assert False == None
Nesse segundo teste, era esperado que o valor de aprovado fosse igual a False
- test_blocked_card
> assert False == rv.get_json().get("aprovado") E AssertionError: assert False == None
Nesse último teste, também era esperado que o valor de aprovado fosse igual a False, pois as transações não podem ser permitidas.
Vamos agora para aplicação principal.
Para fazer a validação das transações vou criar um decorator chamado checar_cartao, ele ficará da seguinte forma:
def checar_cartao(f): wraps(f) def validacoes(*args, **kwargs): dados = request.get_json() if not dados.get("status"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Cartao bloqueado"} return jsonify(response) if dados.get("limit") < dados.get("transaction").get("amount"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Compra acima do limite"} return jsonify(response) return f(*args, **kwargs) return(validacoes)
Vamos chamá-lo antes da requisição ser respondida:
@app.route("/api/transaction",methods=["POST"]) @checar_cartao def transacao(): // codigo da funcao
Agora explicando o código acima.
O Decorator em Python é uma função que retorna uma função, então qual a lógica desse decorator?
Ao invés de fazer uma série de IFs dentro do código principal da função da API, antes mesmo de uma requisição chegar, ela é enviada para o decorator – que tem a função validações – onde será verificado o limite do cartão de crédito e o seu status, caso as condições sejam verdadeiras é retornada uma função jsonify que devolve a transação como negada.
Caso todas as condições sejam falsas, no final temos o return f(*args, **kwargs), que devolve a função original, neste caso a função transação e o fluxo do programa segue normalmente.
Assim, o código do app.py ficou da seguinte maneira:
#!/usr/bin/python3 from flask import Flask, request, jsonify from functools import wraps app = Flask(__name__) def checar_cartao(f): wraps(f) def validacoes(*args, **kwargs): dados = request.get_json() if not dados.get("status"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Cartao bloqueado"} return jsonify(response) if dados.get("limit") < dados.get("transaction").get("amount"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Compra acima do limite"} return jsonify(response) return f(*args, **kwargs) return(validacoes) @app.route("/api/transaction",methods=["POST"]) @checar_cartao def transacao(): card = request.get_json() novo_limite = card.get("limit") - card.get("transaction").get("amount") response = {"aprovado":True,"novoLimite":novo_limite} return jsonify(response) if __name__ == '__main__': app.run(debug=True)
Faça as alterações no seu código e rode os testes novamente, a saída agora deve ser parecida com a saída abaixo:
============================================== test session starts ============================================== platform linux -- Python 3.6.5, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 rootdir: /home/alisson/blog, inifile: collected 3 items test_app.py ... [100%] =========================================== 3 passed in 0.14 seconds ============================================
Isso significa que todos os testes passaram.
É nóis valeu!
About author
Você pode gostar também
Guia definitivo para aprender PHP em 2018: passo a passo para iniciantes
No ano passado, em 2017, não foram poucas as vezes em que fui abordada por pessoas, em sua maioria mulheres, que me perguntaram: Como faço pra aprender PHP? Em início
Descubra as vantagens e características do HTML5 e CSS3
HTML é um acrônimo para Hyper Text Markup Language. E, HTML5 é uma linguagem de marcação para estruturar e exibir conteúdo para a World Wide Web (WWW). É uma versão aprimorada do padrão
Guia completo para instalação do Gitea: Ferramenta open source de gerenciamento de código-fonte
O Gitea é uma ferramenta open source de Source Code Management – SCM, ou seja, gerenciamento de código-fonte, escrita em Go e que foi criada em novembro de 2016 além