mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-01 09:09:45 -06:00
- Normalize line endings from CRLF to LF across all files to match .editorconfig - Standardize quote style from single quotes to double quotes - Normalize whitespace and formatting throughout codebase - Apply consistent code style across 372 files including: * Application code (models, routes, services, utils) * Test files * Configuration files * CI/CD workflows This ensures consistency with the project's .editorconfig settings and improves code maintainability.
256 lines
7.2 KiB
Python
256 lines
7.2 KiB
Python
import pytest
|
|
from app import db
|
|
from app.models import User, Project, TimeEntry, Settings, Client
|
|
from factories import TimeEntryFactory
|
|
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 = TimeEntryFactory(
|
|
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.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 = TimeEntryFactory(
|
|
user_id=user.id, project_id=project.id, start_time=datetime.utcnow(), source="auto", end_time=None
|
|
)
|
|
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 = TimeEntryFactory(
|
|
user_id=user.id, project_id=project.id, start_time=datetime.utcnow(), source="auto", end_time=None
|
|
)
|
|
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 = TimeEntryFactory(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=start_time,
|
|
end_time=start_time + timedelta(hours=2),
|
|
source="manual",
|
|
billable=True,
|
|
)
|
|
entry2 = TimeEntryFactory(
|
|
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.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:"
|