Files
TimeTracker/tests/test_basic.py
Dries Peeters 0752332ed6 feat: Implement comprehensive CI/CD pipeline with GitHub Actions
Implement a complete, production-ready CI/CD pipeline that runs 100% on
GitHub Actions with zero external dependencies. This replaces and consolidates
existing workflows with an optimized, streamlined pipeline.

## Major Changes
- Add 3 new workflows (ci-comprehensive, cd-development, cd-release)
- Remove 2 redundant workflows (backed up)
- Add 130+ tests across 4 new test files
- Add 8 documentation guides (60+ KB)
- Add developer tools and scripts
2025-10-09 13:02:39 +02:00

260 lines
7.3 KiB
Python

import pytest
from app import create_app, db
from app.models import User, Project, TimeEntry, Settings
from datetime import datetime, timedelta
@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'
})
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 runner(app):
"""Create test CLI runner"""
return app.test_cli_runner()
@pytest.fixture
def user(app):
"""Create a test user"""
user = User(username='testuser', role='user')
db.session.add(user)
db.session.commit()
return user
@pytest.fixture
def admin_user(app):
"""Create a test admin user"""
admin = User(username='admin', role='admin')
db.session.add(admin)
db.session.commit()
return admin
@pytest.fixture
def project(app):
"""Create a test project"""
project = Project(
name='Test Project',
client='Test Client',
description='Test project description',
billable=True,
hourly_rate=50.00
)
db.session.add(project)
db.session.commit()
return project
@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
assert db.engine.dialect.has_table(db.engine, 'users')
assert db.engine.dialect.has_table(db.engine, 'projects')
assert db.engine.dialect.has_table(db.engine, 'time_entries')
assert db.engine.dialect.has_table(db.engine, 'settings')
@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():
project = Project(
name='Test Project',
client='Test Client',
description='Test description',
billable=True,
hourly_rate=50.00
)
db.session.add(project)
db.session.commit()
assert project.id is not None
assert project.name == 'Test Project'
assert project.client == 'Test Client'
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"""
with app.app_context():
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"""
with app.app_context():
# 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()
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"""
with app.app_context():
# No active timer initially
assert user.active_timer is None
# 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()
# 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"""
with app.app_context():
# 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'
)
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'
)
db.session.add_all([entry1, entry2])
db.session.commit()
# Check totals
assert project.total_hours == 4.0
assert project.total_billable_hours == 4.0
assert float(project.estimated_cost) == 200.00 # 4 hours * 50 EUR
@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