mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-05 03:01:13 -06:00
- Fix keyboard shortcuts (like 'g r' for Go to Reports) incorrectly triggering while typing in input fields, textareas, and rich text editors - Enhance detection for popular rich text editors: * Toast UI Editor (used in project descriptions) * TinyMCE, Quill, CodeMirror, Summernote * All contenteditable elements - Allow specific global shortcuts even in input fields: * Ctrl+K / Cmd+K: Open command palette * Shift+?: Show keyboard shortcuts help * Ctrl+/: Focus search - Clear key sequences when user starts typing to prevent partial matches - Add debug logging for troubleshooting keyboard shortcut issues - Update JavaScript cache busting version numbers (v=2.0, v=2.2) Test improvements: - Add comprehensive test suite for keyboard shortcuts input fix * Test typing 'gr' in 'program' doesn't trigger navigation * Test rich text editor detection logic * Test allowed shortcuts in inputs - Refactor smoke tests to use admin_authenticated_client fixture instead of manual login (DRY principle) - Fix Windows PermissionError in test cleanup for temporary files - Add SESSION_COOKIE_HTTPONLY to test config for security - Update test secret key length to meet requirements - Remove duplicate admin user fixtures Resolves issue where typing words like 'program' or 'graphics' in forms would trigger unintended navigation shortcuts.
231 lines
7.7 KiB
Python
231 lines
7.7 KiB
Python
import pytest
|
|
from datetime import datetime, timedelta
|
|
from app import create_app, db
|
|
from app.models import Settings, TimeEntry, User, Project
|
|
from app.utils.timezone import get_app_timezone, utc_to_local, local_to_utc, now_in_app_timezone
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
"""Create application for testing"""
|
|
app = create_app({
|
|
'TESTING': True,
|
|
'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:',
|
|
'WTF_CSRF_ENABLED': False,
|
|
'SECRET_KEY': 'test-secret-key-for-testing-at-least-32-chars',
|
|
'FLASK_ENV': 'testing'
|
|
})
|
|
|
|
with app.app_context():
|
|
db.create_all()
|
|
yield app
|
|
db.drop_all()
|
|
|
|
|
|
@pytest.fixture
|
|
def client(app):
|
|
"""Create test client"""
|
|
return app.test_client()
|
|
|
|
|
|
@pytest.fixture
|
|
def user(app):
|
|
"""Create test user"""
|
|
with app.app_context():
|
|
user = User(username='testuser', role='user')
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
return user
|
|
|
|
|
|
@pytest.fixture
|
|
def project(app):
|
|
"""Create test project"""
|
|
with app.app_context():
|
|
project = Project(name='Test Project', client='Test Client', billable=True, hourly_rate=50.0)
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
return project
|
|
|
|
|
|
def test_timezone_default_from_environment(app):
|
|
"""Test that timezone defaults to environment variable when no database settings exist"""
|
|
with app.app_context():
|
|
# Clear any existing settings
|
|
Settings.query.delete()
|
|
db.session.commit()
|
|
|
|
# Test default timezone
|
|
timezone = get_app_timezone()
|
|
assert timezone == 'Europe/Rome' # Default from config
|
|
|
|
|
|
def test_timezone_from_database_settings(app):
|
|
"""Test that timezone is read from database settings"""
|
|
with app.app_context():
|
|
# Create settings with custom timezone
|
|
settings = Settings(timezone='America/New_York')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Test that timezone is read from database
|
|
timezone = get_app_timezone()
|
|
assert timezone == 'America/New_York'
|
|
|
|
|
|
@pytest.mark.xfail(reason="Timezone display test needs adjustment - comparing timezone-aware datetimes")
|
|
def test_timezone_change_affects_display(app, user, project):
|
|
"""Test that changing timezone affects how times are displayed"""
|
|
with app.app_context():
|
|
# Create settings with Europe/Rome timezone
|
|
settings = Settings(timezone='Europe/Rome')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Create a time entry at a specific UTC time
|
|
utc_time = datetime.utcnow().replace(hour=12, minute=0, second=0, microsecond=0)
|
|
|
|
# Refresh the user and project objects to ensure they're attached to the session
|
|
user = db.session.merge(user)
|
|
project = db.session.merge(project)
|
|
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=utc_time,
|
|
end_time=utc_time + timedelta(hours=2),
|
|
source='manual'
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
# Get time in Rome timezone (UTC+1 or UTC+2 depending on DST)
|
|
rome_time = utc_to_local(entry.start_time)
|
|
|
|
# Change timezone to America/New_York
|
|
settings.timezone = 'America/New_York'
|
|
db.session.commit()
|
|
|
|
# Get time in New York timezone (UTC-5 or UTC-4 depending on DST)
|
|
ny_time = utc_to_local(entry.start_time)
|
|
|
|
# Times should be different
|
|
assert rome_time != ny_time
|
|
|
|
# New York time should be earlier than Rome time (behind UTC)
|
|
# This is a basic check - actual difference depends on DST
|
|
assert ny_time.hour != rome_time.hour or abs(ny_time.hour - rome_time.hour) > 1
|
|
|
|
|
|
@pytest.mark.xfail(reason="Timezone offset calculation needs adjustment - allows for timezone differences")
|
|
def test_timezone_aware_current_time(app):
|
|
"""Test that current time is returned in the configured timezone"""
|
|
with app.app_context():
|
|
# Set timezone to Europe/Rome
|
|
settings = Settings(timezone='Europe/Rome')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Get current time in app timezone
|
|
app_time = now_in_app_timezone()
|
|
utc_time = datetime.now().replace(tzinfo=None)
|
|
|
|
# App time should be in Rome timezone
|
|
assert app_time.tzinfo is not None
|
|
assert 'Europe/Rome' in str(app_time.tzinfo)
|
|
|
|
# Times should be close (within a few seconds)
|
|
time_diff = abs((app_time.replace(tzinfo=None) - utc_time).total_seconds())
|
|
assert time_diff < 10
|
|
|
|
|
|
def test_timezone_conversion_utc_to_local(app):
|
|
"""Test UTC to local timezone conversion"""
|
|
with app.app_context():
|
|
# Set timezone to Asia/Tokyo
|
|
settings = Settings(timezone='Asia/Tokyo')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Create a UTC time
|
|
utc_time = datetime.utcnow().replace(hour=12, minute=0, second=0, microsecond=0)
|
|
|
|
# Convert to Tokyo time
|
|
tokyo_time = utc_to_local(utc_time)
|
|
|
|
# Tokyo time should be ahead of UTC (UTC+9)
|
|
assert tokyo_time.tzinfo is not None
|
|
assert 'Asia/Tokyo' in str(tokyo_time.tzinfo)
|
|
|
|
# Tokyo time should be ahead of UTC time
|
|
# Tokyo is UTC+9, so 12:00 UTC should be 21:00 in Tokyo
|
|
assert tokyo_time.hour == 21
|
|
|
|
|
|
def test_timezone_conversion_local_to_utc(app):
|
|
"""Test local timezone to UTC conversion"""
|
|
with app.app_context():
|
|
# Set timezone to Europe/London
|
|
settings = Settings(timezone='Europe/London')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Create a local time (assumed to be in London timezone)
|
|
local_time = datetime.now().replace(hour=15, minute=30, second=0, microsecond=0)
|
|
|
|
# Convert to UTC
|
|
utc_time = local_to_utc(local_time)
|
|
|
|
# UTC time should have timezone info
|
|
assert utc_time.tzinfo is not None
|
|
|
|
# Convert back to local to verify
|
|
back_to_local = utc_to_local(utc_time)
|
|
assert back_to_local.hour == 15
|
|
assert back_to_local.minute == 30
|
|
|
|
|
|
def test_invalid_timezone_fallback(app):
|
|
"""Test that invalid timezone falls back to UTC"""
|
|
with app.app_context():
|
|
# Set invalid timezone
|
|
settings = Settings(timezone='Invalid/Timezone')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Should fall back to UTC
|
|
timezone = get_app_timezone()
|
|
assert timezone == 'Invalid/Timezone' # Still stored in database
|
|
|
|
# But timezone object should fall back to UTC
|
|
from app.utils.timezone import get_timezone_obj
|
|
tz_obj = get_timezone_obj()
|
|
assert 'UTC' in str(tz_obj)
|
|
|
|
|
|
def test_timezone_settings_update(app):
|
|
"""Test that updating timezone settings takes effect immediately"""
|
|
with app.app_context():
|
|
# Create initial settings
|
|
settings = Settings(timezone='Europe/Rome')
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
|
|
# Verify initial timezone
|
|
assert get_app_timezone() == 'Europe/Rome'
|
|
|
|
# Update timezone
|
|
settings.timezone = 'America/Los_Angeles'
|
|
db.session.commit()
|
|
|
|
# Verify timezone change takes effect
|
|
assert get_app_timezone() == 'America/Los_Angeles'
|
|
|
|
# Test that time conversion uses new timezone
|
|
utc_time = datetime.utcnow().replace(hour=12, minute=0, second=0, microsecond=0)
|
|
la_time = utc_to_local(utc_time)
|
|
|
|
# LA time should be in Pacific timezone
|
|
assert la_time.tzinfo is not None
|
|
assert 'America/Los_Angeles' in str(la_time.tzinfo)
|