From 1762c72ab9fa733fa77e8fb3f4af23a0caeba346 Mon Sep 17 00:00:00 2001 From: Arthur Mesquita Pickcius Date: Sat, 23 Aug 2025 15:34:45 -0300 Subject: [PATCH] Create get news endpoint - Add tests for get news endpoint - Add get news endpoint to router - Modify conftest to clear db session between tests --- app/routers/news/routes.py | 30 +++++- app/services/database/orm/news.py | 30 ++++++ tests/conftest.py | 13 ++- tests/test_news.py | 163 ++++++++++++++++++++++++++---- 4 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 app/services/database/orm/news.py diff --git a/app/routers/news/routes.py b/app/routers/news/routes.py index dcaf73c..5869a45 100644 --- a/app/routers/news/routes.py +++ b/app/routers/news/routes.py @@ -1,11 +1,17 @@ -from fastapi import APIRouter, status +from fastapi import APIRouter, Request, status from pydantic import BaseModel +from services.database.orm.news import get_news_by_query_params class NewsPostResponse(BaseModel): status: str = "News Criada" +class NewsGetResponse(BaseModel): + status: str = "Lista de News Obtida" + news_list: list = [] + + def setup(): router = APIRouter(prefix="/news", tags=["news"]) @@ -16,10 +22,30 @@ def setup(): summary="News endpoint", description="Creates news and returns a confirmation message", ) - async def news(): + async def post_news(): """ News endpoint that creates news and returns a confirmation message. """ return NewsPostResponse() + @router.get( + "", + response_model=NewsGetResponse, + status_code=status.HTTP_200_OK, + summary="Get News", + description="Retrieves news filtered by user and query params", + ) + async def get_news(request: Request): + """ + Get News endpoint that retrieves news filtered by user and query params. + """ + news_list = await get_news_by_query_params( + session=request.app.db_session_factory, + id=request.query_params.get("id"), + user_email=request.headers.get("user-email"), + category=request.query_params.get("category"), + tags=request.query_params.get("tags"), + ) + return NewsGetResponse(news_list=news_list) + return router diff --git a/app/services/database/orm/news.py b/app/services/database/orm/news.py new file mode 100644 index 0000000..ac73f9f --- /dev/null +++ b/app/services/database/orm/news.py @@ -0,0 +1,30 @@ +from typing import Optional + +from services.database.models import News +from sqlmodel import select +from sqlmodel.ext.asyncio.session import AsyncSession + + +async def get_news_by_query_params( + session: AsyncSession, + user_email: Optional[str] = None, + category: Optional[str] = None, + tags: Optional[str] = None, + id: Optional[str] = None, +) -> list[News]: + filters = [] + if user_email is not None: + filters.append(News.user_email == user_email) + if category is not None: + filters.append(News.category == category) + if tags is not None: + filters.append(News.tags == tags) + if id is not None: + filters.append(News.id == id) + + print("user_email:", user_email) + print("Filters:", filters) + + statement = select(News).where(*filters) + results = await session.exec(statement) + return results.all() diff --git a/tests/conftest.py b/tests/conftest.py index f939c1e..358c92b 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ -from collections.abc import AsyncGenerator, Generator -from unittest.mock import AsyncMock +from collections.abc import AsyncGenerator import pytest import pytest_asyncio @@ -10,7 +9,8 @@ from sqlmodel.ext.asyncio.session import AsyncSession from app.main import app -#from app.main import get_db_session + +# from app.main import get_db_session # Importar todos os modelos SQLModel a serem usados # (necessários para as validações de modelo) @@ -40,7 +40,9 @@ async def get_db_session_test() -> AsyncGenerator[AsyncSession, None]: async def setup_database(): async with test_engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) - yield test_engine + yield test_engine + async with test_engine.begin() as conn: + await conn.run_sync(SQLModel.metadata.drop_all) @pytest_asyncio.fixture(scope="function") @@ -50,10 +52,11 @@ async def session() -> AsyncGenerator[AsyncSession, None]: yield session await session.close() + @pytest_asyncio.fixture async def test_app(session) -> FastAPI: mock_db_connection = session - setattr(app, 'db_session_factory', mock_db_connection) + setattr(app, "db_session_factory", mock_db_connection) return app diff --git a/tests/test_news.py b/tests/test_news.py index 12768da..45442ee 100755 --- a/tests/test_news.py +++ b/tests/test_news.py @@ -11,7 +11,7 @@ @pytest_asyncio.fixture -async def community(session: AsyncSession): +async def community(session: AsyncSession) -> Community: community = Community(username="admin", email="a@a.com", password="123") session.add(community) await session.commit() @@ -19,22 +19,41 @@ async def community(session: AsyncSession): return community +@pytest_asyncio.fixture +async def news_list(community: Community) -> list[News]: + news_list = [ + News( + title="Python 3.12 Lançado!", + content="A nova versão do Python traz melhorias ...", + category="release", + user_email="dev@example.com", + source_url="https://python.org/news", + tags="python, release, programming", + social_media_url="https://linkedin.com/pythonista", + community_id=community.id, # Usando o ID da comunidade do fixture + ), + News( + title="FastAPI 0.100 Lançado!", + content="FastAPI agora suporta novas funcionalidades ...", + category="release", + user_email="example@pynews.com", + source_url="https://fastapi.com/news", + tags="fastapi, release, web", + social_media_url="https://twitter.com/fastapi", + likes=100, + ), + ] + return news_list + + @pytest.mark.asyncio -async def test_insert_news(session: AsyncSession, community: Community): +async def test_insert_news( + session: AsyncSession, community: Community, news_list: list +): """ Testa a inserção de uma notícia no banco de dados. """ - news = News( - title="Python 3.12 Lançado!", - content="A nova versão do Python traz melhorias ...", - category="release", - user_email="dev@example.com", - source_url="https://python.org/news", - tags="python, release, programming", - social_media_url="https://linkedin.com/pythonista", - community_id=community.id, # Usando o ID da comunidade do fixture - ) - session.add(news) + session.add(news_list[0]) await session.commit() statement = select(News).where(News.title == "Python 3.12 Lançado!") @@ -57,13 +76,11 @@ async def test_insert_news(session: AsyncSession, community: Community): assert found_news.updated_at >= found_news.created_at -# ADD like test case for News model - @pytest.mark.asyncio -async def test_news_endpoint( +async def test_post_news_endpoint( async_client: AsyncClient, mock_headers: Mapping[str, str] ): - """Test the news endpoint returns correct status and version.""" + """Test the news endpoint returns correct status.""" response = await async_client.post("/api/news", headers=mock_headers) assert response.status_code == status.HTTP_200_OK @@ -71,9 +88,113 @@ async def test_news_endpoint( @pytest.mark.asyncio -async def test_news_endpoint_without_auth(async_client: AsyncClient): - """Test the news endpoint without authentication headers.""" - response = await async_client.post("/api/news") +async def test_get_news_endpoint( + session: AsyncSession, + async_client: AsyncClient, + mock_headers: Mapping[str, str], + news_list: list, +): + session.add(news_list[0]) + session.add(news_list[1]) + await session.commit() + + """Test the news endpoint returns correct status and version.""" + response = await async_client.get( + "/api/news", + headers=mock_headers, + ) assert response.status_code == status.HTTP_200_OK - assert response.json() == {"status": "News Criada"} + assert "news_list" in response.json() + assert len(response.json()["news_list"]) == 2 + + +@pytest.mark.asyncio +async def test_get_news_by_category( + session: AsyncSession, + async_client: AsyncClient, + mock_headers: Mapping[str, str], + news_list: list, +): + # Add news to DB + session.add_all(news_list) + await session.commit() + + # Filter by category + response = await async_client.get( + "/api/news", + params={"category": "release"}, + headers={"Content-Type": "application/json"}, + ) + data = response.json() + assert response.status_code == status.HTTP_200_OK + assert "news_list" in data + assert len(data["news_list"]) == 2 + titles = [news["title"] for news in data["news_list"]] + assert "Python 3.12 Lançado!" in titles + assert "FastAPI 0.100 Lançado!" in titles + + +@pytest.mark.asyncio +async def test_get_news_by_user_email( + session: AsyncSession, async_client: AsyncClient, news_list: list +): + session.add_all(news_list) + await session.commit() + + response = await async_client.get( + "/api/news", + params={}, + headers={ + "Content-Type": "application/json", + "user-email": "dev@example.com", + }, + ) + data = response.json() + assert response.status_code == status.HTTP_200_OK + assert len(data["news_list"]) == 1 + assert data["news_list"][0]["user_email"] == "dev@example.com" + assert data["news_list"][0]["title"] == "Python 3.12 Lançado!" + + +@pytest.mark.asyncio +async def test_get_news_by_id( + session: AsyncSession, + async_client: AsyncClient, + mock_headers: Mapping[str, str], + news_list: list, +): + session.add_all(news_list) + await session.commit() + # Get the id from DB + statement = select(News).where(News.title == "Python 3.12 Lançado!") + result = await session.exec(statement) + news = result.first() + response = await async_client.get( + "/api/news", + params={"id": news.id}, + headers=mock_headers, + ) + data = response.json() + assert response.status_code == status.HTTP_200_OK + assert len(data["news_list"]) == 1 + assert data["news_list"][0]["id"] == news.id + assert data["news_list"][0]["title"] == "Python 3.12 Lançado!" + + +@pytest.mark.asyncio +async def test_get_news_empty_result( + async_client: AsyncClient, mock_headers: Mapping[str, str] +): + response = await async_client.get( + "/api/news", + params={"category": "notfound"}, + headers=mock_headers, + ) + data = response.json() + assert response.status_code == status.HTTP_200_OK + assert "news_list" in data + assert data["news_list"] == [] + + +# ADD like test case for News model