from fastapi import Depends, FastAPI from fastapi.testclient import TestClient from app import rate_limit, session from app.auth import NaoAutenticado, UsuarioLogado, exigir_login from app.routers import auth as router_auth app_teste = FastAPI() app_teste.include_router(router_auth.router) @app_teste.exception_handler(NaoAutenticado) def _handler(request, exc): from fastapi.responses import RedirectResponse return RedirectResponse(url=f"/login?next={exc.caminho_original}", status_code=303) @app_teste.get("/rota-protegida-teste") def _rota_protegida(usuario: UsuarioLogado = Depends(exigir_login)): return {"ok": True} client = TestClient(app_teste, follow_redirects=False) CSRF_TESTE = "teste-csrf-token" client.cookies.set("csrf_token", CSRF_TESTE) def test_get_login_mostra_formulario(): resposta = client.get("/login") assert resposta.status_code == 200 assert "senha" in resposta.text.lower() def test_post_login_credenciais_corretas_grava_cookie_e_redireciona(): resposta = client.post( "/login", data={"usuario": "admin", "senha": "teste123", "next": "/clientes", "csrf_token": CSRF_TESTE} ) assert resposta.status_code == 303 assert resposta.headers["location"] == "/clientes" assert session.SESSION_COOKIE_NAME in resposta.cookies def test_post_login_sem_next_redireciona_para_clientes(): resposta = client.post("/login", data={"usuario": "admin", "senha": "teste123", "csrf_token": CSRF_TESTE}) assert resposta.status_code == 303 assert resposta.headers["location"] == "/clientes" def test_post_login_next_externo_e_ignorado_por_seguranca(): resposta = client.post( "/login", data={"usuario": "admin", "senha": "teste123", "next": "https://site-malicioso.com", "csrf_token": CSRF_TESTE}, ) assert resposta.status_code == 303 assert resposta.headers["location"] == "/clientes" def test_post_login_next_com_barra_invertida_e_ignorado_por_seguranca(): # "/\evil.com" é tratado por navegadores modernos como equivalente a # "//evil.com" (URL protocol-relative) — precisa ser bloqueado também. resposta = client.post( "/login", data={"usuario": "admin", "senha": "teste123", "next": "/\\evil.com", "csrf_token": CSRF_TESTE}, ) assert resposta.status_code == 303 assert resposta.headers["location"] == "/clientes" def test_post_login_credenciais_erradas_nao_grava_cookie(): resposta = client.post( "/login", data={"usuario": "admin", "senha": "errada", "csrf_token": CSRF_TESTE} ) assert resposta.status_code == 200 assert "inválid" in resposta.text.lower() assert session.SESSION_COOKIE_NAME not in resposta.cookies def test_get_rota_protegida_sem_login_redireciona_para_login(): # Testes anteriores deixaram um painel_session válido no cookie jar do # client (persistente, como um navegador real) — limpa antes de simular # a requisição de um visitante não autenticado. client.cookies.delete(session.SESSION_COOKIE_NAME) resposta = client.get("/rota-protegida-teste") assert resposta.status_code == 303 assert resposta.headers["location"] == "/login?next=/rota-protegida-teste" def test_post_login_apos_muitas_falhas_e_bloqueado_mesmo_com_senha_certa(): # TestClient reporta sempre a mesma origem ("testclient") — limpa # antes pra não herdar a falha registrada em # test_post_login_credenciais_erradas_nao_grava_cookie() acima, e # limpa depois pra não vazar bloqueio pros testes seguintes deste # arquivo (que também usam essa mesma origem "testclient"). rate_limit._falhas.pop("testclient", None) try: for _ in range(rate_limit.MAX_TENTATIVAS): client.post("/login", data={"usuario": "admin", "senha": "errada", "csrf_token": CSRF_TESTE}) resposta = client.post( "/login", data={"usuario": "admin", "senha": "teste123", "csrf_token": CSRF_TESTE} ) assert resposta.status_code == 429 assert "muitas tentativas" in resposta.text.lower() assert session.SESSION_COOKIE_NAME not in resposta.cookies finally: rate_limit._falhas.pop("testclient", None) def test_post_logout_apaga_cookie_e_redireciona(): cookies = {session.SESSION_COOKIE_NAME: session.criar_token("admin", "Admin", "ADMIN")} resposta = client.post("/logout", data={"csrf_token": CSRF_TESTE}, cookies=cookies) assert resposta.status_code == 303 assert resposta.headers["location"] == "/login" # cookie apagado: o Set-Cookie da resposta zera o valor set_cookie = resposta.headers.get("set-cookie", "") assert session.SESSION_COOKIE_NAME in set_cookie def test_post_logout_apaga_cookie_de_preferencia_de_paginacao(): from app.routers.clientes import POR_PAGINA_COOKIE cookies = {session.SESSION_COOKIE_NAME: session.criar_token("admin", "Admin", "ADMIN")} resposta = client.post("/logout", data={"csrf_token": CSRF_TESTE}, cookies=cookies) assert resposta.status_code == 303 set_cookie = resposta.headers.get("set-cookie", "") assert POR_PAGINA_COOKIE in set_cookie