From 4dd34663ae8f5295547d20d470fbb7487b311c2e Mon Sep 17 00:00:00 2001 From: Matheus Lucindo Date: Thu, 7 May 2026 19:50:09 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20Implementa=C3=A7=C3=A3o=20dos=20testes?= =?UTF-8?q?=20unitarios=20e=20de=20CI=20&=20CD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/rules/agent.md | 6 +++ {.agent => .agents/rules}/persona.md | 0 {.agent => .agents/rules}/project_map.md | 0 {.agent => .agents/rules}/rules.md | 0 .agents/workflows/spec.md | 4 ++ .../workflows/spec}/architecture.md | 4 ++ {spec => .agents/workflows/spec}/features.md | 0 {spec => .agents/workflows/spec}/roadmap.md | 0 .github/workflows/backend-tests.yml | 33 +++++++++++++ backend/main.py | 4 +- backend/tests/__init__.py | 0 backend/tests/test_api.py | 38 +++++++++++++++ backend/tests/test_use_cases.py | 46 +++++++++++++++++++ 13 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 .agents/rules/agent.md rename {.agent => .agents/rules}/persona.md (100%) rename {.agent => .agents/rules}/project_map.md (100%) rename {.agent => .agents/rules}/rules.md (100%) create mode 100644 .agents/workflows/spec.md rename {spec => .agents/workflows/spec}/architecture.md (97%) rename {spec => .agents/workflows/spec}/features.md (100%) rename {spec => .agents/workflows/spec}/roadmap.md (100%) create mode 100644 .github/workflows/backend-tests.yml create mode 100644 backend/tests/__init__.py create mode 100644 backend/tests/test_api.py create mode 100644 backend/tests/test_use_cases.py diff --git a/.agents/rules/agent.md b/.agents/rules/agent.md new file mode 100644 index 0000000..513a1d3 --- /dev/null +++ b/.agents/rules/agent.md @@ -0,0 +1,6 @@ +--- +trigger: always_on +glob: +description: +--- + diff --git a/.agent/persona.md b/.agents/rules/persona.md similarity index 100% rename from .agent/persona.md rename to .agents/rules/persona.md diff --git a/.agent/project_map.md b/.agents/rules/project_map.md similarity index 100% rename from .agent/project_map.md rename to .agents/rules/project_map.md diff --git a/.agent/rules.md b/.agents/rules/rules.md similarity index 100% rename from .agent/rules.md rename to .agents/rules/rules.md diff --git a/.agents/workflows/spec.md b/.agents/workflows/spec.md new file mode 100644 index 0000000..fdc1622 --- /dev/null +++ b/.agents/workflows/spec.md @@ -0,0 +1,4 @@ +--- +description: +--- + diff --git a/spec/architecture.md b/.agents/workflows/spec/architecture.md similarity index 97% rename from spec/architecture.md rename to .agents/workflows/spec/architecture.md index 0f85823..61901cc 100644 --- a/spec/architecture.md +++ b/.agents/workflows/spec/architecture.md @@ -1,3 +1,7 @@ +--- +description: +--- + # Especificação: Arquitetura Hexagonal & DDD ## Visão Geral diff --git a/spec/features.md b/.agents/workflows/spec/features.md similarity index 100% rename from spec/features.md rename to .agents/workflows/spec/features.md diff --git a/spec/roadmap.md b/.agents/workflows/spec/roadmap.md similarity index 100% rename from spec/roadmap.md rename to .agents/workflows/spec/roadmap.md diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml new file mode 100644 index 0000000..a53f1b0 --- /dev/null +++ b/.github/workflows/backend-tests.yml @@ -0,0 +1,33 @@ +name: Backend Tests (Pytest) + +on: + push: + branches: [ main, master, "feat/**" ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout do Código + uses: actions/checkout@v4 + + - name: Configurar Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Instalar Dependências + run: | + cd backend + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-mock + + - name: Rodar Testes com Pytest + run: | + cd backend + export PYTHONPATH=$PYTHONPATH:$(pwd) + pytest diff --git a/backend/main.py b/backend/main.py index a7ac3a2..86587dd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -25,10 +25,12 @@ description="Um serviço de clima com Arquitetura Hexagonal", version="1.0.0" ) - +# CORS origins = [ "http://localhost:5173", "http://127.0.0.1:5173", + "http://localhost:5174", + "http://127.0.0.1:5174", ] app.add_middleware( diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py new file mode 100644 index 0000000..55d09b9 --- /dev/null +++ b/backend/tests/test_api.py @@ -0,0 +1,38 @@ +import pytest +from fastapi.testclient import TestClient +from main import app +from src.domain.entities import WeatherData + +# O TestClient nos permite simular requisições HTTP sem subir o servidor de verdade +client = TestClient(app) + +def test_get_weather_endpoint_success(mocker): + """ + Este teste valida se a ROTA /weather/{city} está funcionando corretamente + e se a resposta JSON segue o formato esperado. + """ + # --- ARRANGE --- + # Criamos um dublê do serviço para isolar o teste da API externa + mock_service = mocker.Mock() + + fake_weather = WeatherData( + city="Rio de Janeiro", temperature=30.0, description="Ensolarado", + humidity=60, wind_speed=10.0 + ) + mock_service.execute.return_value = fake_weather + + # Injetamos o nosso dublê no estado do App (Injeção de Dependência) + app.state.weather_service = mock_service + + # --- ACT --- + # Simulamos uma requisição GET real para o endpoint + response = client.get("/weather/Rio%20de%20Janeiro") + + # --- ASSERT --- + # Validamos se o status HTTP é 200 (OK) + assert response.status_code == 200 + # Validamos se os dados retornados no JSON estão corretos + data = response.json() + assert data["city"] == "Rio de Janeiro" + assert data["temperature"] == 30.0 + assert data["description"] == "Ensolarado" diff --git a/backend/tests/test_use_cases.py b/backend/tests/test_use_cases.py new file mode 100644 index 0000000..e0a4145 --- /dev/null +++ b/backend/tests/test_use_cases.py @@ -0,0 +1,46 @@ +import pytest +from src.application.use_cases import GetWeatherUseCase +from src.domain.entities import WeatherData + +# Teste 1: Garante que o fluxo principal funciona quando os dados são válidos +def tests_get_weather_sucess(mocker): + # --- ARRANGE (Preparação) --- + # Criamos "dublês" (mocks) para não depender de banco de dados ou API real + mock_weather_repo = mocker.Mock() + mock_history_repo = mocker.Mock() + + # Simulamos um retorno de clima vindo do "dublê" da API + fake_weather = WeatherData( + city="Paris", temperature=15.0, description="Nuvens", humidity=80, wind_speed=5.0 + ) + mock_weather_repo.get_weather.return_value = fake_weather + + # Injetamos os dublês no Use Case (Injeção de Dependência) + use_case = GetWeatherUseCase(mock_weather_repo, mock_history_repo) + + # --- ACT (Ação) --- + # Executamos a lógica que queremos testar + result = use_case.execute("Paris") + + # --- ASSERT (Verificação) --- + # Validamos se o resultado é o esperado + assert result.city == "Paris" + assert result.temperature == 15.0 + # Importante: Garantimos que o histórico foi SALVO no banco de dados (Mongo) + mock_history_repo.save_search.assert_called_once_with(fake_weather) + + +# Teste 2: Garante que o sistema barra erros antes de gastar recursos (API/Banco) +def test_get_weather_empty_city_raises_error(mocker): + # --- ARRANGE --- + mock_weather_repo = mocker.Mock() + mock_history_repo = mocker.Mock() + use_case = GetWeatherUseCase(mock_weather_repo, mock_history_repo) + + # --- ACT & ASSERT --- + # Verificamos se o sistema lança um erro (ValueError) ao receber cidade vazia + with pytest.raises(ValueError, match="O nome da cidade não pode ser vazio"): + use_case.execute(" ") + + # Verificamos que a API de clima NUNCA foi chamada (evita gastos desnecessários) + mock_weather_repo.get_weather.assert_not_called()