Skip to content
Merged
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
12 changes: 11 additions & 1 deletion routes/main_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
from collections import OrderedDict
from urllib.parse import unquote
main = Blueprint('main', __name__)


def _decode_path_name(name):
"""Decode one path segment left encoded after Flask's URL decoding."""
return unquote(name)


# -----------------------------------------------------------------------------
# MODEL GROUPING FOR NAVIGATION DROPDOWN
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -667,7 +674,7 @@ def models_in_group(group_name):
model_database = current_app.config.get('MODEL_DATABASE', {})
# Some clients and previously rendered links can encode the path segment
# twice, leaving a literal ``%20`` after Flask's first URL decode.
group_name = unquote(group_name)
group_name = _decode_path_name(group_name)
# Validate group_name
if group_name not in MODEL_GROUPS:
flash(f"Invalid model group: {group_name}", "danger")
Expand All @@ -690,6 +697,7 @@ def models_in_group(group_name):
def model_details(model_name):
"""Display details for a specific model"""
try:
model_name = _decode_path_name(model_name)
model_info = get_model_details(model_name)
if model_info:
return render_template('model_details.html',
Expand All @@ -703,6 +711,7 @@ def model_details(model_name):
def model_interpretation(model_name):
"""Display interpretation guide for a specific model"""
try:
model_name = _decode_path_name(model_name)
model_info = get_model_details(model_name)
if not model_info:
return render_template('error.html', error="Model not found")
Expand All @@ -720,6 +729,7 @@ def model_interpretation(model_name):
def download_interpretation(model_name):
"""Generate and download interpretation guide as HTML file"""
try:
model_name = _decode_path_name(model_name)
model_info = get_model_details(model_name)
if not model_info:
return render_template('error.html', error="Model not found")
Expand Down
40 changes: 40 additions & 0 deletions tests/test_main_routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""
Test the main routes and analysis functionality.
"""
import sys
from types import ModuleType

from models import Analysis


class TestMainRoutes:
"""Test main application routes."""
def test_home_page(self, client):
Expand All @@ -20,6 +25,41 @@ def test_double_encoded_model_group_url(self, client):
assert response.status_code == 200
assert b'Invalid model group' not in response.data
assert b'Linear Regression' in response.data

def test_double_encoded_model_detail_urls(self, client, monkeypatch):
"""Encoded model links work across detail and interpretation routes."""
interpretation_module = ModuleType('utils.interpretation')
interpretation_module.generate_interpretation_data = (
lambda _model_name, _model_info: {}
)
monkeypatch.setitem(
sys.modules,
'utils.interpretation',
interpretation_module,
)

detail_response = client.get('/model/Linear%2520Regression')
interpretation_response = client.get(
'/model/Linear%2520Regression/interpretation'
)
download_response = client.get(
'/model/Linear%2520Regression/download-interpretation'
)

for response in (
detail_response,
interpretation_response,
download_response,
):
assert response.status_code == 200
assert b'Model not found' not in response.data

assert b'Linear Regression' in detail_response.data
assert (
'Linear_Regression_interpretation_guide.html'
in download_response.headers['Content-Disposition']
)

def test_analysis_form_submission_authenticated(self, authenticated_client, sample_analysis_data, app):
"""Test analysis form submission with authenticated user."""
response = authenticated_client.post('/results', data=sample_analysis_data, follow_redirects=True)
Expand Down