diff --git a/tests/test_include_more_routes.py b/tests/test_include_more_routes.py new file mode 100644 index 000000000..ed84b4195 --- /dev/null +++ b/tests/test_include_more_routes.py @@ -0,0 +1,118 @@ +import pytest +from fastapi import APIRouter, FastAPI, Request +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient +from pydantic import BaseModel + +# ====================== +# Configuração do app e rotas +# ====================== + +app = FastAPI() +router = APIRouter() + + +class Item(BaseModel): + nome: str + quantidade: int + + +@router.route("/items/", methods=["GET", "POST"]) +async def read_items(request: Request): + if request.method == "POST": + try: + dados = await request.json() + item = Item(**dados) + return JSONResponse( + {"message": "Item criado", "item": item.model_dump()}, status_code=201 + ) + except Exception: + return JSONResponse({"detail": "Erro ao processar JSON"}, status_code=400) + return JSONResponse({"hello": "world"}) + + +app.include_router(router) +client = TestClient(app) + + +################ Testes + + +# Testa se a rota GET /items/ retorna a resposta padrão esperada +def test_get_items(): + resposta = client.get("/items/") + assert resposta.status_code == 200 + assert resposta.json() == {"hello": "world"} + + +# Testa se a rota POST /items/ aceita e retorna corretamente um JSON válido +def test_post_items(): + payload = {"nome": "Caderno", "quantidade": 10} + resposta = client.post("/items/", json=payload) + assert resposta.status_code == 201 + assert resposta.json() == { + "message": "Item criado", + "item": {"nome": "Caderno", "quantidade": 10}, + } + + +# Testa se a rota POST /items/ retorna erro ao receber JSON inválido (faltando campo obrigatório) +def test_post_json_invalido(): + payload = {"quantidade": 5} # Campo 'nome' está ausente + resposta = client.post("/items/", json=payload) + assert resposta.status_code == 400 + assert resposta.json()["detail"] == "Erro ao processar JSON" + + +# Testa se a rota POST /items/ retorna erro ao receber corpo que não seja JSON +def test_post_com_corpo_nao_json(): + resposta = client.post("/items/", content="texto") # Texto cru, não é JSON + assert resposta.status_code == 400 + assert resposta.json()["detail"] == "Erro ao processar JSON" + + +# Testa se a rota POST /items/ aceita headers customizados e responde corretamente +def test_post_com_header_customizado(): + headers = {"Custom-Header": "123"} + payload = {"nome": "Caneta", "quantidade": 3} + resposta = client.post("/items/", json=payload, headers=headers) + assert resposta.status_code == 201 + assert resposta.json()["item"]["nome"] == "Caneta" + + +# Testa se métodos não permitidos como PUT são corretamente bloqueados pela API +def test_method_not_allowed(): + resposta = client.put("/items/") + assert resposta.status_code == 405 # 405 Method Not Allowed + assert "detail" in resposta.json() + + +# Testa múltiplos métodos HTTP para a mesma rota usando parametrização +# Verifica se cada método responde com o status esperado +@pytest.mark.parametrize( + "metodo,status_esperado", + [ + ("GET", 200), + ("POST", 201), + ("PUT", 405), + ("DELETE", 405), + ("PATCH", 405), + ], +) +def test_varios_metodos(metodo, status_esperado): + payload = {"nome": "Caneta", "quantidade": 1} + resposta = client.request(metodo, "/items/", json=payload) + assert resposta.status_code == status_esperado + + +# Testa se a resposta da rota GET /items/ contém o header correto de Content-Type (application/json) +def test_headers_da_resposta(): + resposta = client.get("/items/") + assert resposta.headers["content-type"].startswith("application/json") + + +# Testa se a rota GET ignora parâmetros de query (ex: ?busca=algo) +def test_get_com_query_params_ignorados(): + resposta = client.get("/items/?busca=algo") + assert resposta.status_code == 200 + assert resposta.json() == {"hello": "world"} diff --git a/tests/test_route_with_multiple_methods.py b/tests/test_route_with_multiple_methods.py new file mode 100644 index 000000000..e114cbf04 --- /dev/null +++ b/tests/test_route_with_multiple_methods.py @@ -0,0 +1,131 @@ +# https://github.com/fastapi/fastapi/issues/10180 + +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient + + +def test_mount_subapp_on_apirouter_should_not_work(): + app = FastAPI() + router = APIRouter(prefix="/api") + + @router.get("/main") + def main(): + return {"msg": "main"} + + subapp = FastAPI() + + @subapp.get("/sub") + def sub(): + return {"msg": "sub"} + + # Tentativa de montar subapp no router (não funciona) + router.mount("/subapi", subapp) + app.include_router(router) + + client = TestClient(app) + # A rota principal funciona + assert client.get("/api/main").status_code == 200 + # A rota da subaplicação NÃO funciona (deveria retornar 404) + assert client.get("/api/subapi/sub").status_code == 404 + + +def test_mount_subapp_on_app_should_work(): + app = FastAPI() + router = APIRouter(prefix="/api") + + @router.get("/main") + def main(): + return {"msg": "main"} + + subapp = FastAPI() + + @subapp.get("/sub") + def sub(): + return {"msg": "sub"} + + app.include_router(router) + # Montando corretamente no app principal + app.mount("/api/subapi", subapp) + + client = TestClient(app) + # A rota principal funciona + assert client.get("/api/main").status_code == 200 + # A rota da subaplicação funciona + assert client.get("/api/subapi/sub").status_code == 200 + + +# Testes adicionais para melhorar cobertura: + + +def test_mount_multiple_subapps(): + """Testa se é possível montar múltiplas subaplicações""" + app = FastAPI() + + # Primeira subaplicação + subapp1 = FastAPI() + + @subapp1.get("/test1") + def read_test1(): + return {"msg": "test1"} + + # Segunda subaplicação + subapp2 = FastAPI() + + @subapp2.get("/test2") + def read_test2(): + return {"msg": "test2"} + + # Montando ambas as subaplicações + app.mount("/sub1", subapp1) + app.mount("/sub2", subapp2) + + client = TestClient(app) + # Verifica se ambas as subaplicações funcionam + assert client.get("/sub1/test1").status_code == 200 + assert client.get("/sub2/test2").status_code == 200 + assert client.get("/sub1/test1").json() == {"msg": "test1"} + assert client.get("/sub2/test2").json() == {"msg": "test2"} + + +def test_nested_routes(): + """Testa rotas aninhadas em diferentes níveis""" + app = FastAPI() + + # Criando routers aninhados + router1 = APIRouter(prefix="/v1") + router2 = APIRouter(prefix="/api") + + @router2.get("/deep") + def read_deep(): + return {"msg": "deep route"} + + # Aninhando os routers + router1.include_router(router2) + app.include_router(router1) + + client = TestClient(app) + # Verifica se a rota aninhada funciona + response = client.get("/v1/api/deep") + assert response.status_code == 200 + assert response.json() == {"msg": "deep route"} + + # Verifica se uma rota inexistente retorna 404 + assert client.get("/v1/api/nonexistent").status_code == 404 + + +def test_method_not_allowed(): + """Testa se métodos HTTP não permitidos são tratados corretamente""" + app = FastAPI() + + @app.get("/only-get") + def read_only(): + return {"msg": "get only"} + + client = TestClient(app) + # Verifica se GET funciona + assert client.get("/only-get").status_code == 200 + + # Verifica se outros métodos retornam 405 (Method Not Allowed) + assert client.post("/only-get").status_code == 405 + assert client.put("/only-get").status_code == 405 + assert client.delete("/only-get").status_code == 405