Skip to content

Commit

Permalink
Merge pull request #43 from MinaFoundation/add-cicd-test
Browse files Browse the repository at this point in the history
Add cicd test
  • Loading branch information
berkingurcan authored Oct 29, 2024
2 parents 52c07c1 + eb3585d commit d02399c
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 48 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Smoke Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
smoke-tests:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx
- name: Run Smoke Tests
run: invoke smoke

- name: Archive test reports
if: always()
uses: actions/upload-artifact@v3
with:
name: test-reports
path: reports/
4 changes: 1 addition & 3 deletions github_tracker_bot/ai_decide_commits.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ def validate_date_format(date_str: str) -> bool:
return False


async def decide_daily_commits(
date: str, data_array: List[CommitData], seed: int = 42
):
async def decide_daily_commits(date: str, data_array: List[CommitData], seed: int = 42):
if not validate_date_format(date):
raise ValueError("Incorrect date format, should be YYYY-MM-DD")

Expand Down
7 changes: 7 additions & 0 deletions github_tracker_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ async def job():

@app.middleware("http")
async def check_auth_token(request: Request, call_next):
if request.url.path == "/health":
return await call_next(request)
auth_token = config.SHARED_SECRET

request_token = request.headers.get("Authorization")
Expand All @@ -128,6 +130,11 @@ async def check_auth_token(request: Request, call_next):
return response


@app.get("/health")
async def health_check():
return JSONResponse(status_code=200, content={"status": "OK"})


@app.post("/run-task")
async def run_task(time_frame: TaskTimeFrame):
try:
Expand Down
2 changes: 1 addition & 1 deletion github_tracker_bot/mongo_data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,4 +665,4 @@ def delete_ai_decisions_and_clean_users(
logger.error(
f"Failed to delete ai_decisions and clean users between {since_date} and {until_date}: {e}"
)
raise
raise
14 changes: 14 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[pytest]
env =
OPENAI_API_KEY=sk-mock_value,
MONGO_HOST= mongodb://localhost:27017/,
MONGO_DB=test_db,
MONGO_COLLECTION=my_collection
SHARED_SECRET=123

filterwarnings =
ignore::DeprecationWarning
markers =
smoke: mark test as a smoke test.

asyncio_mode = auto
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ pylint==3.2.6
pymongo==4.8.0
PyNaCl==1.5.0
pyparsing==3.1.2
pytest==8.2.2
pytest==8.3.3
pytest-asyncio==0.24.0
pytest-env==1.1.5
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-multipart==0.0.9
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

setup(
name="pgt_leaderbot",
version="0.4.0",
version="0.4.1",
packages=find_packages(),
)
5 changes: 5 additions & 0 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,8 @@ def dbf(ctx):
@task
def lbf(ctx):
ctx.run("python leader_bot/leaderboard_functions.py")


@task
def smoke(ctx):
ctx.run("pytest -m smoke tests/smoke")
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
import sys

import pytest

project_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "..")
)
if project_root not in sys.path:
sys.path.insert(0, project_root)
Empty file added tests/smoke/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions tests/smoke/test_auth_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
import sys
from fastapi.testclient import TestClient
import pytest
from unittest import mock

from httpx import AsyncClient, ASGITransport

project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if project_root not in sys.path:
sys.path.insert(0, project_root)

from github_tracker_bot.bot import app

client = TestClient(app)


@pytest.mark.smoke
@pytest.mark.asyncio
async def test_authentication_required():
headers = {"Authorization": "Bearer invalid_token"}
response = client.post("/run-task", headers=headers)
assert response.status_code == 401
assert response.json()["message"] == "Unauthorized"
16 changes: 16 additions & 0 deletions tests/smoke/test_scheduler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from fastapi.testclient import TestClient
import pytest
import os
import sys
from unittest import mock

project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if project_root not in sys.path:
sys.path.insert(0, project_root)

from github_tracker_bot.bot import app


@pytest.mark.smoke
def test_scheduler_exists():
assert hasattr(app.state, "scheduler_task")
33 changes: 33 additions & 0 deletions tests/smoke/test_startup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sys
import os
import pytest
from httpx import AsyncClient, ASGITransport
from unittest import mock

project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if project_root not in sys.path:
sys.path.insert(0, project_root)

with mock.patch("github_tracker_bot.ai_decide_commits.OpenAI") as mock_openai_client:
from github_tracker_bot.bot import app


@pytest.mark.smoke
@pytest.mark.asyncio
async def test_app_startup():
transport = ASGITransport(app=app)
shared_secret = os.environ.get("SHARED_SECRET")
headers = {"Authorization": f"Bearer {shared_secret}"}

async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/non-existing-endpoint", headers=headers)
assert response.status_code == 401


@pytest.mark.smoke
@pytest.mark.asyncio
async def test_app_health_check():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "OK"}
51 changes: 14 additions & 37 deletions tests/test_bot_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import patch, AsyncMock, MagicMock
from datetime import datetime, timedelta, timezone
from fastapi.testclient import TestClient
import config
import github_tracker_bot.bot as bot

client = TestClient(bot.app)
Expand All @@ -25,45 +26,39 @@ def test_run_scheduled_task(self, mock_get_results, mock_get_dates):
asyncio.run(bot.run_scheduled_task())
mock_get_results.assert_awaited_once()

def test_get_dates_for_today(self):
since_date, until_date = bot.get_dates_for_today()
today = datetime.now(timezone.utc).replace(
hour=0, minute=0, second=0, microsecond=0
)
expected_until_date = today + timedelta(days=1)

self.assertEqual(since_date, today.isoformat())
self.assertEqual(until_date, expected_until_date.isoformat())

@patch("github_tracker_bot.bot.scheduler", new_callable=AsyncMock)
def test_control_scheduler_start(self):
def test_control_scheduler_start(self, mock_scheduler):
headers = {"Authorization": config.SHARED_SECRET}

response = client.post(
"/control-scheduler", json={"action": "start", "interval_minutes": 5}
"/control-scheduler", json={"action": "start"}, headers=headers
)
self.assertEqual(response.status_code, 200)
self.assertIn(
"Scheduler started with interval of 5 minutes",
response.json().get("message"),
)

@patch("github_tracker_bot.bot.scheduler", new_callable=AsyncMock)
def test_control_scheduler_stop(self):
response = client.post("/control-scheduler", json={"action": "stop"})
def test_control_scheduler_stop(self, mock_scheduler):
headers = {"Authorization": config.SHARED_SECRET}

response = client.post(
"/control-scheduler", json={"action": "stop"}, headers=headers
)
self.assertEqual(response.status_code, 200)
self.assertIn("Scheduler stopped", response.json().get("message"))

@patch(
"github_tracker_bot.bot.get_all_results_from_sheet_by_date",
new_callable=AsyncMock,
)
def test_run_task(self, mock_get_results):
mock_get_results.return_value = None
# Add the correct authorization token
headers = {"Authorization": config.SHARED_SECRET}
response = client.post(
"/run-task",
json={
"since": "2023-01-01T00:00:00+00:00",
"until": "2023-01-02T00:00:00+00:00",
},
headers=headers, # Pass the headers with the token
)
self.assertEqual(response.status_code, 200)
self.assertIn(
Expand All @@ -81,24 +76,6 @@ def test_validate_datetime(self):
since="2023-02-29T00:00:00+00:00", until="2023-01-02T00:00:00+00:00"
)

def test_scheduler(self):
with patch("aioschedule.every") as mock_every, patch(
"github_tracker_bot.bot.run_scheduled_task", new_callable=AsyncMock
) as mock_run_task:
mock_job = MagicMock()
mock_every.return_value.minutes.do.return_value = mock_job

async def run_scheduler():
schedule_task = asyncio.create_task(bot.scheduler(1))
await asyncio.sleep(0.1)
schedule_task.cancel()

asyncio.run(run_scheduler())
mock_every.return_value.minutes.do.assert_called_once_with(
bot.run_scheduled_task
)
mock_run_task.assert_not_awaited()


if __name__ == "__main__":
unittest.main()
11 changes: 8 additions & 3 deletions tests/test_mongo_data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,14 @@ def test_update_all_contribution_datas_from_ai_decisions(self):
self.mongo_handler.add_ai_decisions_by_user("test_handle", ai_decisions_1)
self.mongo_handler.add_ai_decisions_by_user("test_handle", ai_decisions_2)

self.mongo_handler.update_all_contribution_datas_from_ai_decisions(
"test_handle"
)
with patch(
"leader_bot.sheet_functions.get_repositories_from_user"
) as mock_get_repos:
mock_get_repos.return_value = []
self.mongo_handler.update_all_contribution_datas_from_ai_decisions(
"test_handle"
)

user = self.mongo_handler.get_user("test_handle")

self.assertEqual(user.total_daily_contribution_number, 4)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_process_commits.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ async def test_handle_403_api_rate_limit(self, mock_get, mock_time, mock_sleep):

# Assert that sleep was called twice:
# 1. Once for the rate limit (expected_sleep_time)
# 2. Once for the tenacity retry (fixed 2 seconds)
# 2. Once for the tenacity retry (fixed 5 seconds)
self.assertEqual(mock_sleep.call_count, 2)
mock_sleep.assert_has_calls([call(expected_sleep_time), call(2.0)])
mock_sleep.assert_has_calls([call(expected_sleep_time), call(5.0)])

# Ensure that the second call to `aiohttp.get` was successful
self.assertEqual(mock_get.call_count, 2)
Expand Down

0 comments on commit d02399c

Please sign in to comment.