Introdução
Uma correção pode ser desfeita acidentalmente em uma alteração posterior. Um teste de regressão de segurança transforma um requisito importante em uma requisição repetível e um resultado esperado.
Você ampliará um pequeno modelo Python para um endpoint protegido do DeskLite. Depois, verificará os próprios testes contra dois serviços locais com defeitos intencionais. Não é necessário instalar um framework de testes nem ter experiência anterior escrevendo testes.
Teste o endpoint protegido
Nesta etapa, você identificará o comportamento que os testes devem proteger.
O serviço correto preparado está na porta local 8080. Seu endpoint /tickets retorna o ticket original para um token válido e 401 com um objeto de erro para um token ausente ou incorreto. A autenticação e a resposta de negócio são requisitos separados: o status 200, por si só, não comprova que o ticket correto foi retornado.
cd /home/labex/project/ticket-tests
O course_test_fixture_token visível deste laboratório é um dado de teste público e deliberadamente falso. Ele é aceito apenas por estes serviços de ensino isolados e não tem autoridade externa. Ele não substitui as credenciais privadas de aplicativos ensinadas anteriormente.
Use a mesma opção de cabeçalho de requisição do curl, -H, apresentada no laboratório de tokens:
curl -i -H 'Authorization: Bearer course_test_fixture_token' http://127.0.0.1:8080/tickets
Espere HTTP 200 e {"tickets": [{"id": 1, "title": "Printer offline"}]}. -i inclui os cabeçalhos da resposta HTTP, incluindo seu status.
Agora omita completamente o cabeçalho:
curl -i http://127.0.0.1:8080/tickets
Espere HTTP 401 e {"error": "unauthorized"}. Essa rejeição é o comportamento exigido, não um defeito no exercício.
Leia o pequeno modelo de teste
Nesta etapa, você relacionará cada linha de um teste a uma parte de uma requisição real.
cat test_security.py
O arquivo preparado contém:
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
from api import request carrega um auxiliar de requisições preparado. def test_valid_token(): define uma função de teste nomeada. As linhas indentadas pertencem a essa função, assim como na função de busca do laboratório anterior.
request(path, headers) envia uma requisição HTTP real e retorna um dicionário Python: um mapeamento de nomes para valores. Os mapeamentos de cabeçalhos também usam chaves. {"Authorization": "Bearer ..."} envia um cabeçalho, enquanto {} não envia nenhum. response["status"] lê a entrada de status; response["body"] lê o corpo JSON já interpretado.
Uma asserção declara uma expectativa. O == do Python compara dois valores; ele é diferente do = simples usado para atribuir um valor. Um assert falso gera AssertionError, fazendo o teste falhar. A última linha compara o objeto de negócio esperado completo, incluindo o título do ticket.
O auxiliar escolhe um serviço preparado a partir do argumento de destino do executor. Você editará os testes, não o auxiliar nem o serviço.
Veja a lacuna em um teste que passa
Nesta etapa, você verá por que um teste positivo que passa é insuficiente.
Execute o executor de testes preparado. Seu argumento good seleciona o serviço correto:
python3 run_tests.py good
Espere test_valid_token com "result": "PASS". O executor reúne funções cujos nomes começam com test_ e chama cada uma. Ele retorna código de saída 0 quando todas passam, 1 para uma falha de asserção e 2 para um erro no teste ou em tempo de execução.
Dois outros serviços têm defeitos intencionais: missing-auth aceita requisições sem token; wrong-auth aceita um token incorreto. A resposta positiva deles continua funcionando. Tente ambos:
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth
Ambos ainda passam no único teste existente. Esse resultado expõe uma lacuna de cobertura. O teste nunca enviou uma requisição que deveria ser rejeitada, portanto não conseguiu perceber nenhum dos dois desvios de autenticação.
Adicione o requisito de token ausente
Nesta etapa, você adicionará um teste que espera a rejeição de uma requisição real sem autenticação.
Abra o arquivo de teste:
nano test_security.py
Preserve o teste positivo e adicione a nova função abaixo dele. O arquivo completo deve ser:
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
def test_missing_token():
response = request("/tickets", {})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
O mapeamento vazio {} significa nenhum cabeçalho de requisição. A asserção de status verifica a rejeição, enquanto a asserção do corpo garante que a resposta de erro não contenha dados de tickets. Preserve a indentação de quatro espaços. Salve com Ctrl+O, Enter e saia com Ctrl+X.
python3 run_tests.py good
Ambos os testes devem passar. Agora use o serviço com o defeito intencional de cabeçalho ausente:
python3 run_tests.py missing-auth
Espere que test_missing_token mostre "result": "FAIL" e "error": "AssertionError". Esse código de saída 1 intencional é uma evidência útil: o novo teste detecta o serviço aceitando uma requisição que deveria rejeitar. Isso é diferente de um erro de importação ou de conexão com o servidor.
Adicione o requisito de token incorreto
Nesta etapa, você cobrirá uma credencial presente, mas inválida.
Um cabeçalho pode existir sem ser válido. Testar apenas um cabeçalho ausente deixaria passar um serviço que aceita qualquer token não vazio. Abra o mesmo arquivo e adicione a terceira função, preservando as duas primeiras:
nano test_security.py
Use este arquivo completo:
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
def test_missing_token():
response = request("/tickets", {})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
def test_wrong_token():
response = request("/tickets", {"Authorization": "Bearer wrong_fixture_token"})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
wrong_fixture_token é deliberadamente rejeitado pelo serviço correto. Salve e saia com Ctrl+O, Enter, Ctrl+X.
python3 run_tests.py good
Espere que os três testes passem. Depois teste a regressão de cabeçalho incorreto:
python3 run_tests.py wrong-auth
Espere que apenas test_wrong_token falhe com AssertionError. Não altere 401 para 200 para fazer este serviço defeituoso passar; isso removeria o requisito de segurança.
Valide a detecção das duas regressões
Nesta etapa, você verificará o conjunto completo de testes contra as três implementações.
python3 run_tests.py good
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth
Leia os resultados individuais. O serviço correto deve passar nos três testes. A regressão de cabeçalho ausente deve falhar apenas em test_missing_token, e a regressão de cabeçalho incorreto deve falhar apenas em test_wrong_token. O teste de negócio com token válido deve continuar passando em todos.
Essas execuções comparativas com falhas são intencionais. Um erro de sintaxe, um erro de conexão ou uma falha incondicional não demonstra detecção. A verificação do curso também confirma que os testes enviaram as três requisições HTTP reais a cada serviço original.
As comparações cobrem essas duas regressões conhecidas. Elas não comprovam todas as propriedades possíveis de autenticação ou autorização.
Termine com uma execução bem-sucedida no serviço correto
Nesta etapa, você terminará com um comando de teste repetível para a implementação pretendida.
cat test_security.py
Confirme que o teste positivo original e ambos os testes de rejeição permanecem. Seus nomes descrevem os três requisitos independentes.
python3 run_tests.py good
Os três resultados devem ser PASS. Uma futura etapa de aprovação de entrega poderá executar esse mesmo comando no serviço pretendido. Os serviços com defeitos intencionais continuam disponíveis como controles de ensino; eles não são a implementação a entregar. Você protegeu o comportamento real em vez de entregar uma declaração escrita sobre a cobertura dos testes.
Resumo
Você relacionou requisitos explícitos de autenticação a três testes HTTP reais. A implementação correta passa; cada desvio de autenticação preparado é detectado pela asserção de rejeição correspondente. Essa comparação fornece evidências de que os testes protegem o comportamento, em vez de apenas executar sem erros.



