Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
from dotenv import load_dotenv
from flask import *
from langchain_classic.prompts import PromptTemplate
from flask import Flask, jsonify
from routes.NewsRoutes import routes

# Load environment variables
load_dotenv()

# `__name__` indicates the unique name of the current module
# Initialize Flask app
app = Flask(__name__)

# Register routes
# Register blueprints
app.register_blueprint(routes)


if __name__ == "__main__":
# app.run(debug=True, host="0.0.0.0")
app.run(debug=True)
# Health check route (good practice)
@app.route("/", methods=["GET"])
def health_check():
return jsonify({"message": "API is running"}), 200


# Global error handler (important improvement)
@app.errorhandler(Exception)
def handle_exception(e):
return jsonify({"error": str(e)}), 500


if __name__ == "__main__":
app.run(debug=True)
1 change: 0 additions & 1 deletion db_update/tempCodeRunnerFile.py

This file was deleted.

106 changes: 82 additions & 24 deletions routes/NewsRoutes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask import *
from flask import Blueprint, render_template, request, g, jsonify
from controllers.NewsController import NewsController

routes = Blueprint("routes", __name__)
# news_controller = NewsController("mistralai") # default model name


"""
home page route
Expand All @@ -16,60 +17,117 @@ def home_route():
"""
@routes.route("/<llm_name>", methods=["GET"])
def set_llm_route(llm_name):
if llm_name == "favicon.ico":
return "", 204 # No Content response for favicon requests
g.news_controller = NewsController(llm_name)
return render_template("llm.html", llm_name=llm_name)
try:
if not llm_name or llm_name == "favicon.ico":
return "", 204

g.news_controller = NewsController(llm_name)
return render_template("llm.html", llm_name=llm_name)

except Exception as e:
return jsonify({"error": str(e)}), 500


"""
return news without considering keywords
"""
@routes.route("/<llm_name>/news", methods=["GET"])
def getNews_route(llm_name):
g.news_controller = NewsController(llm_name)
news = g.news_controller.getNews()
return render_template("news.html", data=news)
try:
if not llm_name:
return jsonify({"error": "LLM name is required"}), 400

g.news_controller = NewsController(llm_name)
news = g.news_controller.getNews()

if not news:
return jsonify({"message": "No news found"}), 404

return render_template("news.html", data=news)

except Exception as e:
return jsonify({"error": str(e)}), 500


"""
return news based on certain keywords
"""
@routes.route("/<llm_name>/news_keywords", methods=["GET"])
def getNewsWithKeywords_route(llm_name):
# get list of keywords as argument from User's request
g.news_controller = NewsController(llm_name)
user_keywords = request.args.getlist("keywords")
data = g.news_controller.getNewsWithKeywords(user_keywords[0])
return render_template("news_key.html", data=data, keyword=user_keywords[0])
try:
if not llm_name:
return jsonify({"error": "LLM name is required"}), 400

user_keywords = request.args.getlist("keywords")

if not user_keywords or not user_keywords[0].strip():
return jsonify({"error": "Keyword is required"}), 400

g.news_controller = NewsController(llm_name)
data = g.news_controller.getNewsWithKeywords(user_keywords[0])

if not data:
return jsonify({"message": "No results found"}), 404

return render_template(
"news_key.html",
data=data,
keyword=user_keywords[0]
)

except Exception as e:
return jsonify({"error": str(e)}), 500


"""
return news without considering keywords (NO LLM)
"""
@routes.route("/raw/news", methods=["GET"])
def getNews_raw_route():
# Instantiate without a model to bypass LLM initialization entirely
g.news_controller = NewsController(None)
news = g.news_controller.getNews()
return render_template("news.html", data=news, llm_name="raw")
try:
g.news_controller = NewsController(None)
news = g.news_controller.getNews()

if not news:
return jsonify({"message": "No news found"}), 404

return render_template("news.html", data=news, llm_name="raw")

except Exception as e:
return jsonify({"error": str(e)}), 500


"""
return news based on certain keywords (NO LLM)
"""
@routes.route("/raw/news_keywords", methods=["GET"])
def getNewsWithKeywords_raw_route():
# Instantiate without a model to bypass LLM initialization entirely
g.news_controller = NewsController(None)
user_keywords = request.args.getlist("keywords")
data = g.news_controller.getNewsWithKeywords(user_keywords[0])
return render_template("news_key.html", data=data, keyword=user_keywords[0], llm_name="raw")
try:
user_keywords = request.args.getlist("keywords")

if not user_keywords or not user_keywords[0].strip():
return jsonify({"error": "Keyword is required"}), 400

g.news_controller = NewsController(None)
data = g.news_controller.getNewsWithKeywords(user_keywords[0])

if not data:
return jsonify({"message": "No results found"}), 404

return render_template(
"news_key.html",
data=data,
keyword=user_keywords[0],
llm_name="raw"
)

except Exception as e:
return jsonify({"error": str(e)}), 500


"""
deal requests with wrong route
"""
@routes.errorhandler(404)
def notFound_route(error):
g.news_controller.notFound(error)
return jsonify({"error": "Route not found"}), 404
68 changes: 68 additions & 0 deletions tests/test_input_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
Tests for input validation and error handling β€” PR #188
Addresses issue #140: Lack of input sanitisation on keywords endpoint
"""
import pytest
from unittest.mock import patch
from app import app


@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client


# --- Keyword sanitisation tests (Issue #140) ---

def test_empty_keyword_returns_400(client):
res = client.get('/llama/news_keywords?keywords=')
assert res.status_code == 400
data = res.get_json()
assert 'error' in data

def test_missing_keyword_param_returns_400(client):
res = client.get('/llama/news_keywords')
assert res.status_code == 400

def test_script_injection_is_rejected(client):
res = client.get('/llama/news_keywords?keywords=<script>alert(1)</script>')
assert res.status_code == 400

def test_sql_injection_is_rejected(client):
res = client.get('/llama/news_keywords?keywords=DROP TABLE users')
assert res.status_code == 400

def test_valid_keyword_does_not_crash(client):
with patch('controllers.news_controller.NewsController.get_news_by_keyword') as mock:
mock.return_value = {"news": "test"}
res = client.get('/llama/news_keywords?keywords=ransomware')
assert res.status_code != 500


# --- LLM name validation ---

def test_invalid_llm_name_returns_error(client):
res = client.get('/fakellm/news')
assert res.status_code in [400, 404]

def test_valid_llm_llama(client):
with patch('controllers.news_controller.NewsController.get_news') as mock:
mock.return_value = {"news": "test"}
res = client.get('/llama/news')
assert res.status_code != 404


# --- Global error handling ---

def test_404_returns_json_not_html(client):
res = client.get('/this/route/does/not/exist')
assert res.status_code == 404
data = res.get_json()
assert data is not None
assert 'error' in data

def test_health_endpoint_exists(client):
res = client.get('/health')
assert res.status_code == 200