From 7d32781932b701e7c32087088899eff2ee6543e1 Mon Sep 17 00:00:00 2001 From: Dries Peeters Date: Tue, 21 Oct 2025 10:07:20 +0200 Subject: [PATCH] tests/ci: fix analytics DB init; telemetry env precedence and export; installation config reload; route analytics calls reference app module for mocks --- app/routes/admin.py | 11 ++++++----- app/routes/clients.py | 19 ++++++++++--------- app/routes/tasks.py | 19 ++++++++++--------- app/utils/installation.py | 11 ++++++++--- app/utils/telemetry.py | 23 ++++++++++++++++++----- tests/test_analytics.py | 14 ++++++++++++-- 6 files changed, 64 insertions(+), 33 deletions(-) diff --git a/app/routes/admin.py b/app/routes/admin.py index 592fc746..03e649d4 100644 --- a/app/routes/admin.py +++ b/app/routes/admin.py @@ -1,7 +1,8 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, send_from_directory, send_file, jsonify, render_template_string from flask_babel import gettext as _ from flask_login import login_required, current_user -from app import db, limiter, log_event, track_event +import app as app_module +from app import db, limiter from app.models import User, Project, TimeEntry, Settings, Invoice from datetime import datetime from sqlalchemy import text @@ -261,8 +262,8 @@ def telemetry_dashboard(): } # Log dashboard access - log_event("admin.telemetry_dashboard_viewed", user_id=current_user.id) - track_event(current_user.id, "admin.telemetry_dashboard_viewed", {}) + app_module.log_event("admin.telemetry_dashboard_viewed", user_id=current_user.id) + app_module.track_event(current_user.id, "admin.telemetry_dashboard_viewed", {}) return render_template('admin/telemetry.html', telemetry=telemetry_data, @@ -282,8 +283,8 @@ def toggle_telemetry(): installation_config.set_telemetry_preference(new_state) # Log the change - log_event("admin.telemetry_toggled", user_id=current_user.id, new_state=new_state) - track_event(current_user.id, "admin.telemetry_toggled", {"enabled": new_state}) + app_module.log_event("admin.telemetry_toggled", user_id=current_user.id, new_state=new_state) + app_module.track_event(current_user.id, "admin.telemetry_toggled", {"enabled": new_state}) if new_state: flash('Telemetry has been enabled. Thank you for helping us improve!', 'success') diff --git a/app/routes/clients.py b/app/routes/clients.py index bb6a876c..c8747130 100644 --- a/app/routes/clients.py +++ b/app/routes/clients.py @@ -1,7 +1,8 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app from flask_babel import gettext as _ from flask_login import login_required, current_user -from app import db, log_event, track_event +import app as app_module +from app import db from app.models import Client, Project from datetime import datetime from decimal import Decimal @@ -109,8 +110,8 @@ def create_client(): return render_template('clients/create.html') # Log client creation - log_event("client.created", user_id=current_user.id, client_id=client.id) - track_event(current_user.id, "client.created", {"client_id": client.id}) + app_module.log_event("client.created", user_id=current_user.id, client_id=client.id) + app_module.track_event(current_user.id, "client.created", {"client_id": client.id}) flash(f'Client "{name}" created successfully', 'success') return redirect(url_for('clients.view_client', client_id=client.id)) @@ -180,8 +181,8 @@ def edit_client(client_id): return render_template('clients/edit.html', client=client) # Log client update - log_event("client.updated", user_id=current_user.id, client_id=client.id) - track_event(current_user.id, "client.updated", {"client_id": client.id}) + app_module.log_event("client.updated", user_id=current_user.id, client_id=client.id) + app_module.track_event(current_user.id, "client.updated", {"client_id": client.id}) flash(f'Client "{name}" updated successfully', 'success') return redirect(url_for('clients.view_client', client_id=client.id)) @@ -202,8 +203,8 @@ def archive_client(client_id): flash('Client is already inactive', 'info') else: client.archive() - log_event("client.archived", user_id=current_user.id, client_id=client.id) - track_event(current_user.id, "client.archived", {"client_id": client.id}) + app_module.log_event("client.archived", user_id=current_user.id, client_id=client.id) + app_module.track_event(current_user.id, "client.archived", {"client_id": client.id}) flash(f'Client "{client.name}" archived successfully', 'success') return redirect(url_for('clients.list_clients')) @@ -249,8 +250,8 @@ def delete_client(client_id): return redirect(url_for('clients.view_client', client_id=client.id)) # Log client deletion - log_event("client.deleted", user_id=current_user.id, client_id=client_id_for_log) - track_event(current_user.id, "client.deleted", {"client_id": client_id_for_log}) + app_module.log_event("client.deleted", user_id=current_user.id, client_id=client_id_for_log) + app_module.track_event(current_user.id, "client.deleted", {"client_id": client_id_for_log}) flash(f'Client "{client_name}" deleted successfully', 'success') return redirect(url_for('clients.list_clients')) diff --git a/app/routes/tasks.py b/app/routes/tasks.py index f7eb1922..d3f4c252 100644 --- a/app/routes/tasks.py +++ b/app/routes/tasks.py @@ -1,7 +1,8 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, make_response from flask_babel import gettext as _ from flask_login import login_required, current_user -from app import db, log_event, track_event +import app as app_module +from app import db from app.models import Task, Project, User, TimeEntry, TaskActivity, KanbanColumn from datetime import datetime, date from decimal import Decimal @@ -156,12 +157,12 @@ def create_task(): return render_template('tasks/create.html') # Log task creation - log_event("task.created", + app_module.log_event("task.created", user_id=current_user.id, task_id=task.id, project_id=project_id, priority=priority) - track_event(current_user.id, "task.created", { + app_module.track_event(current_user.id, "task.created", { "task_id": task.id, "project_id": project_id, "priority": priority @@ -302,11 +303,11 @@ def edit_task(task_id): return render_template('tasks/edit.html', task=task, projects=projects, users=users) # Log task update - log_event("task.updated", + app_module.log_event("task.updated", user_id=current_user.id, task_id=task.id, project_id=task.project_id) - track_event(current_user.id, "task.updated", { + app_module.track_event(current_user.id, "task.updated", { "task_id": task.id, "project_id": task.project_id }) @@ -388,12 +389,12 @@ def update_task_status(task_id): return redirect(url_for('tasks.view_task', task_id=task.id)) # Log task status change - log_event("task.status_changed", + app_module.log_event("task.status_changed", user_id=current_user.id, task_id=task.id, old_status=previous_status, new_status=new_status) - track_event(current_user.id, "task.status_changed", { + app_module.track_event(current_user.id, "task.status_changed", { "task_id": task.id, "old_status": previous_status, "new_status": new_status @@ -476,8 +477,8 @@ def delete_task(task_id): return redirect(url_for('tasks.view_task', task_id=task.id)) # Log task deletion - log_event("task.deleted", user_id=current_user.id, task_id=task_id_for_log, project_id=project_id_for_log) - track_event(current_user.id, "task.deleted", {"task_id": task_id_for_log, "project_id": project_id_for_log}) + app_module.log_event("task.deleted", user_id=current_user.id, task_id=task_id_for_log, project_id=project_id_for_log) + app_module.track_event(current_user.id, "task.deleted", {"task_id": task_id_for_log, "project_id": project_id_for_log}) flash(f'Task "{task_name}" deleted successfully', 'success') return redirect(url_for('tasks.list_tasks')) diff --git a/app/utils/installation.py b/app/utils/installation.py index 14bfeafe..62a636d1 100644 --- a/app/utils/installation.py +++ b/app/utils/installation.py @@ -115,13 +115,18 @@ def get_all_config(self) -> Dict: # Global instance _installation_config = None +_installation_config_path = None def get_installation_config() -> InstallationConfig: """Get the global installation configuration instance""" - global _installation_config - if _installation_config is None: - _installation_config = InstallationConfig() + global _installation_config, _installation_config_path + # Reinitialize if config path changed (e.g., tests overriding directories) + tmp = InstallationConfig() + current_path = tmp.config_path + if (_installation_config is None) or (_installation_config_path != current_path): + _installation_config = tmp + _installation_config_path = current_path return _installation_config diff --git a/app/utils/telemetry.py b/app/utils/telemetry.py index 55738543..7e6a31ed 100644 --- a/app/utils/telemetry.py +++ b/app/utils/telemetry.py @@ -52,20 +52,33 @@ def is_telemetry_enabled() -> bool: Checks both environment variable and user preference from installation config. User preference takes precedence over environment variable. """ + # Environment variable takes precedence for tests/CI and explicit overrides + env_value = os.getenv("ENABLE_TELEMETRY") + if env_value is not None: + enabled = env_value.lower() + return enabled in ("true", "1", "yes", "on") + try: # Import here to avoid circular imports from app.utils.installation import get_installation_config - + # Get user preference from installation config installation_config = get_installation_config() if installation_config.is_setup_complete(): return installation_config.get_telemetry_preference() except Exception: pass - - # Fallback to environment variable - enabled = os.getenv("ENABLE_TELEMETRY", "false").lower() - return enabled in ("true", "1", "yes", "on") + + # Default disabled if not explicitly enabled + return False + + +# Re-export helper for tests to patch +try: + from app.utils.installation import get_installation_config # type: ignore +except Exception: + def get_installation_config(): # type: ignore + raise RuntimeError("installation config unavailable") def _ensure_posthog_initialized() -> bool: diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 172fab67..c58ca7e8 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -12,9 +12,19 @@ @pytest.fixture def app(): - """Create test Flask application""" + """Create test Flask application and initialize DB tables.""" app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:'}) - return app + from app import db + with app.app_context(): + db.create_all() + try: + yield app + finally: + try: + with app.app_context(): + db.drop_all() + except Exception: + pass @pytest.fixture