Files
TimeTracker/test_quick_wins.py
Dries Peeters b1973ca49a feat: Add Quick Wins feature set - activity tracking, templates, and user preferences
This commit introduces several high-impact features to improve user experience
and productivity:

New Features:
- Activity Logging: Comprehensive audit trail tracking user actions across the
  system with Activity model, including IP address and user agent tracking
- Time Entry Templates: Reusable templates for frequently logged activities with
  usage tracking and quick-start functionality
- Saved Filters: Save and reuse common search/filter combinations across
  different views (projects, tasks, reports)
- User Preferences: Enhanced user settings including email notifications,
  timezone, date/time formats, week start day, and theme preferences
- Excel Export: Generate formatted Excel exports for time entries and reports
  with styling and proper formatting
- Email Notifications: Complete email system for task assignments, overdue
  invoices, comments, and weekly summaries with HTML templates
- Scheduled Tasks: Background task scheduler for periodic operations

Models Added:
- Activity: Tracks all user actions with detailed context and metadata
- TimeEntryTemplate: Stores reusable time entry configurations
- SavedFilter: Manages user-saved filter configurations

Routes Added:
- user.py: User profile and settings management
- saved_filters.py: CRUD operations for saved filters
- time_entry_templates.py: Template management endpoints

UI Enhancements:
- Bulk actions widget component
- Keyboard shortcuts help modal with advanced shortcuts
- Save filter widget component
- Email notification templates
- User profile and settings pages
- Saved filters management interface
- Time entry templates interface

Database Changes:
- Migration 022: Creates activities and time_entry_templates tables
- Adds user preference columns (notifications, timezone, date/time formats)
- Proper indexes for query optimization

Backend Updates:
- Enhanced keyboard shortcuts system (commands.js, keyboard-shortcuts-advanced.js)
- Updated projects, reports, and tasks routes with activity logging
- Safe database commit utilities integration
- Event tracking for analytics

Dependencies:
- Added openpyxl for Excel generation
- Added Flask-Mail dependencies
- Updated requirements.txt

All new features include proper error handling, activity logging integration,
and maintain existing functionality while adding new capabilities.
2025-10-23 09:05:07 +02:00

292 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Quick Wins Features - Validation Test Script
This script validates that all new features can be imported
and basic functionality works without errors.
"""
import sys
import os
# Add the app directory to the path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def test_imports():
"""Test that all new modules can be imported"""
print("🔍 Testing imports...")
try:
# Test model imports
from app.models import TimeEntryTemplate, Activity, SavedFilter, User
print("✅ Models imported successfully")
# Test route imports
from app.routes.user import user_bp
from app.routes.time_entry_templates import time_entry_templates_bp
from app.routes.saved_filters import saved_filters_bp
print("✅ Routes imported successfully")
# Test utility imports
from app.utils.email import mail, init_mail, send_email
from app.utils.excel_export import create_time_entries_excel, create_project_report_excel
from app.utils.scheduled_tasks import scheduler, check_overdue_invoices, register_scheduled_tasks
print("✅ Utilities imported successfully")
return True
except ImportError as e:
print(f"❌ Import error: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
def test_model_attributes():
"""Test that models have expected attributes"""
print("\n🔍 Testing model attributes...")
try:
from app.models import TimeEntryTemplate, Activity, SavedFilter, User
# Test TimeEntryTemplate attributes
template_attrs = ['id', 'user_id', 'name', 'project_id', 'task_id',
'default_duration_minutes', 'default_notes', 'tags',
'usage_count', 'last_used_at']
for attr in template_attrs:
assert hasattr(TimeEntryTemplate, attr), f"TimeEntryTemplate missing {attr}"
print("✅ TimeEntryTemplate has all attributes")
# Test Activity attributes
activity_attrs = ['id', 'user_id', 'action', 'entity_type', 'entity_id',
'description', 'metadata', 'created_at']
for attr in activity_attrs:
assert hasattr(Activity, attr), f"Activity missing {attr}"
print("✅ Activity has all attributes")
# Test SavedFilter attributes
filter_attrs = ['id', 'user_id', 'name', 'scope', 'payload',
'is_shared', 'created_at', 'updated_at']
for attr in filter_attrs:
assert hasattr(SavedFilter, attr), f"SavedFilter missing {attr}"
print("✅ SavedFilter has all attributes")
# Test User new attributes
user_new_attrs = ['email_notifications', 'notification_overdue_invoices',
'notification_task_assigned', 'notification_task_comments',
'notification_weekly_summary', 'timezone', 'date_format',
'time_format', 'week_start_day']
for attr in user_new_attrs:
assert hasattr(User, attr), f"User missing new attribute {attr}"
print("✅ User has all new preference attributes")
return True
except AssertionError as e:
print(f"❌ Attribute test failed: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
def test_model_methods():
"""Test that models have expected methods"""
print("\n🔍 Testing model methods...")
try:
from app.models import TimeEntryTemplate, Activity
# Test TimeEntryTemplate methods
template_methods = ['to_dict', 'record_usage', 'increment_usage']
for method in template_methods:
assert hasattr(TimeEntryTemplate, method), f"TimeEntryTemplate missing {method}"
print("✅ TimeEntryTemplate has all methods")
# Test Activity methods
activity_methods = ['log', 'get_recent', 'to_dict']
for method in activity_methods:
assert hasattr(Activity, method), f"Activity missing {method}"
print("✅ Activity has all methods")
return True
except AssertionError as e:
print(f"❌ Method test failed: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
def test_blueprint_registration():
"""Test that blueprints are properly configured"""
print("\n🔍 Testing blueprint registration...")
try:
from app.routes.user import user_bp
from app.routes.time_entry_templates import time_entry_templates_bp
from app.routes.saved_filters import saved_filters_bp
# Check blueprint names
assert user_bp.name == 'user', "user_bp has wrong name"
assert time_entry_templates_bp.name == 'time_entry_templates', "time_entry_templates_bp has wrong name"
assert saved_filters_bp.name == 'saved_filters', "saved_filters_bp has wrong name"
print("✅ All blueprints properly configured")
return True
except AssertionError as e:
print(f"❌ Blueprint test failed: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
def test_utility_functions():
"""Test that utility functions exist"""
print("\n🔍 Testing utility functions...")
try:
from app.utils.email import init_mail, send_email
from app.utils.excel_export import create_time_entries_excel, create_project_report_excel
from app.utils.scheduled_tasks import register_scheduled_tasks, check_overdue_invoices
# Check that functions are callable
assert callable(init_mail), "init_mail is not callable"
assert callable(send_email), "send_email is not callable"
assert callable(create_time_entries_excel), "create_time_entries_excel is not callable"
assert callable(create_project_report_excel), "create_project_report_excel is not callable"
assert callable(register_scheduled_tasks), "register_scheduled_tasks is not callable"
assert callable(check_overdue_invoices), "check_overdue_invoices is not callable"
print("✅ All utility functions are callable")
return True
except AssertionError as e:
print(f"❌ Utility function test failed: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
def test_template_files():
"""Test that template files exist"""
print("\n🔍 Testing template files...")
template_files = [
'app/templates/user/settings.html',
'app/templates/user/profile.html',
'app/templates/email/overdue_invoice.html',
'app/templates/email/task_assigned.html',
'app/templates/email/weekly_summary.html',
'app/templates/email/comment_mention.html',
'app/templates/time_entry_templates/list.html',
'app/templates/time_entry_templates/create.html',
'app/templates/time_entry_templates/edit.html',
'app/templates/saved_filters/list.html',
'app/templates/components/save_filter_widget.html',
'app/templates/components/bulk_actions_widget.html',
'app/templates/components/keyboard_shortcuts_help.html',
]
missing = []
for template in template_files:
if not os.path.exists(template):
missing.append(template)
if missing:
print(f"❌ Missing templates: {', '.join(missing)}")
return False
else:
print(f"✅ All {len(template_files)} template files exist")
return True
def test_migration_file():
"""Test that migration file exists and has correct structure"""
print("\n🔍 Testing migration file...")
migration_file = 'migrations/versions/add_quick_wins_features.py'
if not os.path.exists(migration_file):
print(f"❌ Migration file not found: {migration_file}")
return False
try:
with open(migration_file, 'r') as f:
content = f.read()
# Check for required elements
required = [
"revision = '022'",
"down_revision = '021'",
'def upgrade():',
'def downgrade():',
'time_entry_templates',
'activities',
]
for req in required:
if req not in content:
print(f"❌ Migration missing required element: {req}")
return False
print("✅ Migration file is valid")
return True
except Exception as e:
print(f"❌ Error reading migration file: {e}")
return False
def main():
"""Run all tests"""
print("="*60)
print("🚀 Quick Wins Features - Validation Test")
print("="*60)
tests = [
("Imports", test_imports),
("Model Attributes", test_model_attributes),
("Model Methods", test_model_methods),
("Blueprint Registration", test_blueprint_registration),
("Utility Functions", test_utility_functions),
("Template Files", test_template_files),
("Migration File", test_migration_file),
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"\n❌ Test '{test_name}' crashed: {e}")
results.append((test_name, False))
# Summary
print("\n" + "="*60)
print("📊 Test Summary")
print("="*60)
passed = sum(1 for _, result in results if result)
total = len(results)
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f"{status} - {test_name}")
print("="*60)
print(f"Results: {passed}/{total} tests passed ({passed/total*100:.0f}%)")
print("="*60)
if passed == total:
print("\n🎉 All tests passed! Ready for deployment.")
return 0
else:
print(f"\n⚠️ {total - passed} test(s) failed. Please fix before deployment.")
return 1
if __name__ == '__main__':
exit(main())