import pytest from app import db from app.models import User, Project, TimeEntry, Settings, Client from datetime import datetime, timedelta from decimal import Decimal # Note: All fixtures are now imported from conftest.py # No duplicate fixtures needed here @pytest.mark.smoke @pytest.mark.unit def test_app_creation(app): """Test that the app can be created""" assert app is not None assert app.config['TESTING'] is True @pytest.mark.unit @pytest.mark.database def test_database_creation(app): """Test that database tables can be created""" with app.app_context(): # Check that tables exist using inspect from sqlalchemy import inspect inspector = inspect(db.engine) tables = inspector.get_table_names() assert 'users' in tables assert 'projects' in tables assert 'time_entries' in tables assert 'settings' in tables @pytest.mark.unit @pytest.mark.models def test_user_creation(app): """Test user creation""" with app.app_context(): user = User(username='testuser', role='user') db.session.add(user) db.session.commit() assert user.id is not None assert user.username == 'testuser' assert user.role == 'user' assert user.is_admin is False @pytest.mark.unit @pytest.mark.models def test_admin_user(app): """Test admin user properties""" with app.app_context(): admin = User(username='admin', role='admin') db.session.add(admin) db.session.commit() assert admin.is_admin is True @pytest.mark.unit @pytest.mark.models def test_project_creation(app): """Test project creation""" with app.app_context(): # Create a client first client = Client(name='Test Client', default_hourly_rate=Decimal('50.00')) db.session.add(client) db.session.commit() project = Project( name='Test Project', client_id=client.id, description='Test description', billable=True, hourly_rate=Decimal('50.00') ) db.session.add(project) db.session.commit() assert project.id is not None assert project.name == 'Test Project' assert project.client_id == client.id assert project.billable is True assert float(project.hourly_rate) == 50.00 @pytest.mark.unit @pytest.mark.models def test_time_entry_creation(app, user, project): """Test time entry creation""" start_time = datetime.utcnow() end_time = start_time + timedelta(hours=2) entry = TimeEntry( user_id=user.id, project_id=project.id, start_time=start_time, end_time=end_time, notes='Test entry', tags='test,work', source='manual' ) db.session.add(entry) db.session.commit() assert entry.id is not None assert entry.duration_hours == 2.0 assert entry.duration_formatted == '02:00:00' assert entry.tag_list == ['test', 'work'] @pytest.mark.unit @pytest.mark.models def test_active_timer(app, user, project): """Test active timer functionality""" # Create active timer timer = TimeEntry( user_id=user.id, project_id=project.id, start_time=datetime.utcnow(), source='auto' ) db.session.add(timer) db.session.commit() assert timer.is_active is True assert timer.end_time is None # Stop timer timer.stop_timer() db.session.commit() assert timer.is_active is False assert timer.end_time is not None assert timer.duration_seconds > 0 @pytest.mark.unit @pytest.mark.models def test_user_active_timer_property(app, user, project): """Test user active timer property""" # Refresh user to check initial state db.session.refresh(user) # Create active timer timer = TimeEntry( user_id=user.id, project_id=project.id, start_time=datetime.utcnow(), source='auto' ) db.session.add(timer) db.session.commit() # Refresh user to load relationships db.session.expire(user) db.session.refresh(user) # Check active timer assert user.active_timer is not None assert user.active_timer.id == timer.id @pytest.mark.integration @pytest.mark.models def test_project_totals(app, user, project): """Test project total calculations""" # Create time entries start_time = datetime.utcnow() entry1 = TimeEntry( user_id=user.id, project_id=project.id, start_time=start_time, end_time=start_time + timedelta(hours=2), source='manual', billable=True ) entry2 = TimeEntry( user_id=user.id, project_id=project.id, start_time=start_time + timedelta(hours=3), end_time=start_time + timedelta(hours=5), source='manual', billable=True ) db.session.add_all([entry1, entry2]) db.session.commit() # Refresh project to load relationships db.session.expire(project) db.session.refresh(project) # Check totals assert project.total_hours == 4.0 assert project.total_billable_hours == 4.0 expected_cost = 4.0 * float(project.hourly_rate) assert float(project.estimated_cost) == expected_cost @pytest.mark.unit @pytest.mark.models def test_settings_singleton(app): """Test settings singleton pattern""" with app.app_context(): # Get settings (should create if not exists) settings1 = Settings.get_settings() settings2 = Settings.get_settings() assert settings1.id == settings2.id assert settings1 is settings2 @pytest.mark.smoke @pytest.mark.routes def test_health_check(client): """Test health check endpoint""" response = client.get('/_health') assert response.status_code == 200 data = response.get_json() assert data['status'] == 'healthy' @pytest.mark.smoke @pytest.mark.routes def test_login_page(client): """Test login page accessibility""" response = client.get('/login') assert response.status_code == 200 @pytest.mark.unit @pytest.mark.routes def test_protected_route_redirect(client): """Test that protected routes redirect to login""" response = client.get('/dashboard', follow_redirects=False) assert response.status_code == 302 assert '/login' in response.location @pytest.mark.smoke @pytest.mark.unit def test_testing_config_respects_database_url(): """Test that TestingConfig respects DATABASE_URL environment variable This test verifies the fix for GitHub Actions migration validation where DATABASE_URL is set to PostgreSQL but TestingConfig was hardcoded to SQLite. Note: This test runs with whatever DATABASE_URL is currently set in the environment. In CI/CD with DATABASE_URL set to PostgreSQL, it will use PostgreSQL. Locally without DATABASE_URL, it will use SQLite. """ import os from app.config import TestingConfig config = TestingConfig() # Verify that the config uses the DATABASE_URL if set, otherwise defaults to SQLite if 'DATABASE_URL' in os.environ: # In CI/CD or when DATABASE_URL is explicitly set assert config.SQLALCHEMY_DATABASE_URI == os.environ['DATABASE_URL'] else: # Local development/testing without DATABASE_URL assert config.SQLALCHEMY_DATABASE_URI == 'sqlite:///:memory:'