diff --git a/.gitignore b/.gitignore index e22df14..298fd04 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ __pycache__/ .venv .vscode +.ruff_cache/ diff --git a/api-service/app.py b/api-service/app.py index 77703d8..ab0d1a7 100644 --- a/api-service/app.py +++ b/api-service/app.py @@ -1,7 +1,6 @@ from dotenv import load_dotenv -from flask import * +from flask import Flask import logging -from langchain_classic.prompts import PromptTemplate from routes.NewsRoutes import routes # Load environment variables diff --git a/api-service/pytest.ini b/api-service/pytest.ini new file mode 100644 index 0000000..a635c5c --- /dev/null +++ b/api-service/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . diff --git a/api-service/requirements.txt b/api-service/requirements.txt index 712dd84..d278473 100644 --- a/api-service/requirements.txt +++ b/api-service/requirements.txt @@ -19,3 +19,5 @@ sentence-transformers==5.6.0 gunicorn==26.0.0 transformers==5.12.1 torch==2.12.1 +pytest==8.3.5 +pytest-mock==3.14.1 diff --git a/api-service/routes/NewsRoutes.py b/api-service/routes/NewsRoutes.py index f3800cc..da04d8d 100644 --- a/api-service/routes/NewsRoutes.py +++ b/api-service/routes/NewsRoutes.py @@ -1,15 +1,15 @@ -from flask import * -import logging -from models.SubscriberModel import SubscriberDB -from models.SourceModel import SourceDB -from models.NewsModel import CybernewsDB -from agents.notification import KNOWN_INTEREST_TAGS import json import os + import redis +from flask import Blueprint, jsonify, render_template, request + +from agents.notification import KNOWN_INTEREST_TAGS +from models.NewsModel import CybernewsDB +from models.SourceModel import SourceDB +from models.SubscriberModel import SubscriberDB routes = Blueprint("routes", __name__) -logger = logging.getLogger(__name__) REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0") SESSION_TTL = int(os.getenv("SESSION_TTL", "3600")) diff --git a/api-service/tests/test_notification.py b/api-service/tests/test_notification.py index d7c4701..ae72476 100644 --- a/api-service/tests/test_notification.py +++ b/api-service/tests/test_notification.py @@ -93,6 +93,7 @@ def test_default_frequency_is_daily(self, mocker): state = make_state(user_input="subscribe vishak@example.com to malware") result = notification_agent(state) mock_db.create_subscriber.assert_called_once_with(email="vishak@example.com", frequency="daily", interests=["malware"]) + assert result["notification_triggered"] is True def test_newly_added_interest_tags_matched(self, mocker): from agents import notification as notification_module diff --git a/api-service/tests/unitTest.py b/api-service/tests/unitTest.py deleted file mode 100644 index 670c103..0000000 --- a/api-service/tests/unitTest.py +++ /dev/null @@ -1,47 +0,0 @@ -import unittest -from flask import Flask -from flask.testing import FlaskClient - -from dotenv import load_dotenv -import os -import sys - -# Get the current file path -current_file_path = os.path.abspath(__file__) - -# Get the grandparent directory -src_directory = os.path.dirname(os.path.dirname(current_file_path)) - -# Add the parent directory to sys.path -sys.path.append(src_directory) - -dotenv_path = os.path.join(src_directory, '.env') -load_dotenv(dotenv_path) - -# Import the Flask application -from app import app - - -class FlaskAppTestCase(unittest.TestCase): - def setUp(self): - # Create a test client - self.app = app.test_client() - - def tearDown(self): - pass - - def test_news(self): - response = self.app.get('/news') - self.assertEqual(response.status_code, 200) - - def test_news_keywords(self): - response = self.app.get('/news_keywords?keywords=firewall') - self.assertEqual(response.status_code, 200) - - def test_invalid_route(self): - response = self.app.get('/xxx') - self.assertEqual(response.status_code, 404) - - -if __name__ == '__main__': - unittest.main() diff --git a/api-service/tests/unitTest_cybernews.py b/api-service/tests/unitTest_cybernews.py deleted file mode 100644 index 4162958..0000000 --- a/api-service/tests/unitTest_cybernews.py +++ /dev/null @@ -1,28 +0,0 @@ -""" - Unittest class for CyberNews -""" -import unittest - -from cybernews.CyberNews import CyberNews - - -class TestCyberNews(unittest.TestCase): - def setUp(self): - self.news = CyberNews() - self.valid_news = self.news.get_news_types - self.invalid_news = ["", "Invalid"] - - def test_init(self): - self.assertIsNotNone(self.news.get_news_types) - - def test_get_news(self): - [self.assertIsNotNone(self.news.get_news(news)) for news in self.valid_news] - - def test_get_news_invalid_type(self): - for news in self.invalid_news: - with self.assertRaises(ValueError): - self.news.get_news(news) - - -if __name__ == "__main__": - unittest.main() diff --git a/ingestion-service/embeddings.py b/ingestion-service/embeddings.py index e30afcd..442e887 100644 --- a/ingestion-service/embeddings.py +++ b/ingestion-service/embeddings.py @@ -5,7 +5,7 @@ """ import logging -from config import EMBEDDING_DIM, EMBEDDING_MODEL, MAX_EMBEDDING_INPUT_CHARS +from config import EMBEDDING_MODEL, MAX_EMBEDDING_INPUT_CHARS logger = logging.getLogger(__name__) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..0023a25 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,2 @@ +# Local/CI quality tools. Runtime deps stay in each service's requirements.txt. +ruff==0.12.11 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..de108d2 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,26 @@ +target-version = "py311" +line-length = 100 + +exclude = [ + ".git", + ".venv", + ".pytest_cache", + ".ruff_cache", + "__pycache__", + "api-service/.venv", + "ingestion-service/.venv", + "notification-service/.venv", + "api-service/cybernews", + "api-service/db_update", +] + +[lint] +select = ["E", "F"] +ignore = [ + "E501", # existing lines are already long; don't restyle the whole repo +] + +[lint.per-file-ignores] +# Tests and the summarizer job import after sys.path / load_dotenv on purpose. +"**/tests/**" = ["E402", "F401"] +"api-service/jobs/summarize_articles.py" = ["E402"]