mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-06 03:30:25 -06:00
600 lines
19 KiB
Python
600 lines
19 KiB
Python
"""
|
|
Test suite for Weekly Time Goals feature.
|
|
Tests model creation, calculations, relationships, routes, and business logic.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime, timedelta, date
|
|
from app.models import WeeklyTimeGoal, TimeEntry, User, Project
|
|
from app import db
|
|
|
|
|
|
# ============================================================================
|
|
# WeeklyTimeGoal Model Tests
|
|
# ============================================================================
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
@pytest.mark.smoke
|
|
def test_weekly_goal_creation(app, user):
|
|
"""Test basic weekly time goal creation."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
assert goal.id is not None
|
|
assert goal.target_hours == 40.0
|
|
assert goal.week_start_date == week_start
|
|
assert goal.week_end_date == week_start + timedelta(days=6)
|
|
assert goal.status == 'active'
|
|
assert goal.created_at is not None
|
|
assert goal.updated_at is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_default_week(app, user):
|
|
"""Test weekly goal creation with default week (current week)."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Should default to current week's Monday
|
|
today = date.today()
|
|
expected_week_start = today - timedelta(days=today.weekday())
|
|
|
|
assert goal.week_start_date == expected_week_start
|
|
assert goal.week_end_date == expected_week_start + timedelta(days=6)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_with_notes(app, user):
|
|
"""Test weekly goal with notes."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=35.0,
|
|
notes="Vacation week, reduced hours"
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
assert goal.notes == "Vacation week, reduced hours"
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_actual_hours_calculation(app, user, project):
|
|
"""Test calculation of actual hours worked."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entries for the week
|
|
entry1 = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=8),
|
|
duration_seconds=8 * 3600
|
|
)
|
|
entry2 = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start + timedelta(days=1), datetime.min.time()),
|
|
end_time=datetime.combine(week_start + timedelta(days=1), datetime.min.time()) + timedelta(hours=7),
|
|
duration_seconds=7 * 3600
|
|
)
|
|
db.session.add_all([entry1, entry2])
|
|
db.session.commit()
|
|
|
|
# Refresh goal to get calculated properties
|
|
db.session.refresh(goal)
|
|
|
|
assert goal.actual_hours == 15.0
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_progress_percentage(app, user, project):
|
|
"""Test progress percentage calculation."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entry
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
|
|
duration_seconds=20 * 3600
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
db.session.refresh(goal)
|
|
|
|
# 20 hours out of 40 = 50%
|
|
assert goal.progress_percentage == 50.0
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_remaining_hours(app, user, project):
|
|
"""Test remaining hours calculation."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entry
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=15),
|
|
duration_seconds=15 * 3600
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
db.session.refresh(goal)
|
|
|
|
assert goal.remaining_hours == 25.0
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_is_completed(app, user, project):
|
|
"""Test is_completed property."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=20.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
db.session.refresh(goal)
|
|
assert goal.is_completed is False
|
|
|
|
# Add time entry to complete goal
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
|
|
duration_seconds=20 * 3600
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
db.session.refresh(goal)
|
|
assert goal.is_completed is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_average_hours_per_day(app, user, project):
|
|
"""Test average hours per day calculation."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entry for 10 hours
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=10),
|
|
duration_seconds=10 * 3600
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
db.session.refresh(goal)
|
|
|
|
# Remaining: 30 hours, Days remaining: depends on current day
|
|
if goal.days_remaining > 0:
|
|
expected_avg = round(goal.remaining_hours / goal.days_remaining, 2)
|
|
assert goal.average_hours_per_day == expected_avg
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_week_label(app, user):
|
|
"""Test week label generation."""
|
|
with app.app_context():
|
|
week_start = date(2024, 1, 1) # A Monday
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
assert "Jan 01" in goal.week_label
|
|
assert "Jan 07" in goal.week_label
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_status_update_completed(app, user, project):
|
|
"""Test automatic status update to completed."""
|
|
with app.app_context():
|
|
# Create goal for past week
|
|
week_start = date.today() - timedelta(days=14)
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=20.0,
|
|
week_start_date=week_start,
|
|
status='active'
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entry to meet goal
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
|
|
duration_seconds=20 * 3600
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
goal.update_status()
|
|
db.session.commit()
|
|
|
|
assert goal.status == 'completed'
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_status_update_failed(app, user, project):
|
|
"""Test automatic status update to failed."""
|
|
with app.app_context():
|
|
# Create goal for past week
|
|
week_start = date.today() - timedelta(days=14)
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start,
|
|
status='active'
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entry that doesn't meet goal
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(week_start, datetime.min.time()),
|
|
end_time=datetime.combine(week_start, datetime.min.time()) + timedelta(hours=20),
|
|
duration_seconds=20 * 3600
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
goal.update_status()
|
|
db.session.commit()
|
|
|
|
assert goal.status == 'failed'
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_get_current_week(app, user):
|
|
"""Test getting current week's goal."""
|
|
with app.app_context():
|
|
# Create goal for current week
|
|
today = date.today()
|
|
week_start = today - timedelta(days=today.weekday())
|
|
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=week_start
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Get current week goal
|
|
current_goal = WeeklyTimeGoal.get_current_week_goal(user.id)
|
|
|
|
assert current_goal is not None
|
|
assert current_goal.id == goal.id
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_to_dict(app, user):
|
|
"""Test goal serialization to dictionary."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
notes="Test notes"
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
goal_dict = goal.to_dict()
|
|
|
|
assert 'id' in goal_dict
|
|
assert 'user_id' in goal_dict
|
|
assert 'target_hours' in goal_dict
|
|
assert 'actual_hours' in goal_dict
|
|
assert 'week_start_date' in goal_dict
|
|
assert 'week_end_date' in goal_dict
|
|
assert 'status' in goal_dict
|
|
assert 'notes' in goal_dict
|
|
assert 'progress_percentage' in goal_dict
|
|
assert 'remaining_hours' in goal_dict
|
|
assert 'is_completed' in goal_dict
|
|
|
|
assert goal_dict['target_hours'] == 40.0
|
|
assert goal_dict['notes'] == "Test notes"
|
|
|
|
|
|
# ============================================================================
|
|
# WeeklyTimeGoal Routes Tests
|
|
# ============================================================================
|
|
|
|
@pytest.mark.smoke
|
|
def test_weekly_goals_index_page(authenticated_client):
|
|
"""Test weekly goals index page loads."""
|
|
response = authenticated_client.get('/goals')
|
|
assert response.status_code == 200
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_weekly_goals_create_page(authenticated_client):
|
|
"""Test weekly goals create page loads."""
|
|
response = authenticated_client.get('/goals/create')
|
|
assert response.status_code == 200
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_create_weekly_goal_via_form(authenticated_client, app, user):
|
|
"""Test creating a weekly goal via form submission."""
|
|
with app.app_context():
|
|
data = {
|
|
'target_hours': 40.0,
|
|
'notes': 'Test goal'
|
|
}
|
|
response = authenticated_client.post('/goals/create', data=data, follow_redirects=True)
|
|
assert response.status_code == 200
|
|
|
|
# Check goal was created
|
|
goal = WeeklyTimeGoal.query.filter_by(user_id=user.id).first()
|
|
assert goal is not None
|
|
assert goal.target_hours == 40.0
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_edit_weekly_goal(authenticated_client, app, user):
|
|
"""Test editing a weekly goal."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
goal_id = goal.id
|
|
|
|
# Update goal
|
|
data = {
|
|
'target_hours': 35.0,
|
|
'notes': 'Updated notes',
|
|
'status': 'active'
|
|
}
|
|
response = authenticated_client.post(f'/goals/{goal_id}/edit', data=data, follow_redirects=True)
|
|
assert response.status_code == 200
|
|
|
|
# Check goal was updated
|
|
db.session.refresh(goal)
|
|
assert goal.target_hours == 35.0
|
|
assert goal.notes == 'Updated notes'
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_delete_weekly_goal(authenticated_client, app, user):
|
|
"""Test deleting a weekly goal."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
goal_id = goal.id
|
|
|
|
# Delete goal
|
|
response = authenticated_client.post(f'/goals/{goal_id}/delete', follow_redirects=True)
|
|
assert response.status_code == 200
|
|
|
|
# Check goal was deleted
|
|
deleted_goal = WeeklyTimeGoal.query.get(goal_id)
|
|
assert deleted_goal is None
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_view_weekly_goal(authenticated_client, app, user):
|
|
"""Test viewing a specific weekly goal."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
goal_id = goal.id
|
|
|
|
response = authenticated_client.get(f'/goals/{goal_id}')
|
|
assert response.status_code == 200
|
|
|
|
|
|
# ============================================================================
|
|
# API Endpoints Tests
|
|
# ============================================================================
|
|
|
|
@pytest.mark.smoke
|
|
def test_api_get_current_goal(authenticated_client, app, user):
|
|
"""Test API endpoint for getting current week's goal."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
response = authenticated_client.get('/api/goals/current')
|
|
assert response.status_code == 200
|
|
|
|
data = response.get_json()
|
|
assert 'target_hours' in data
|
|
assert data['target_hours'] == 40.0
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_api_list_goals(authenticated_client, app, user):
|
|
"""Test API endpoint for listing goals."""
|
|
with app.app_context():
|
|
# Create multiple goals
|
|
for i in range(3):
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=date.today() - timedelta(weeks=i, days=date.today().weekday())
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
response = authenticated_client.get('/api/goals')
|
|
assert response.status_code == 200
|
|
|
|
data = response.get_json()
|
|
assert isinstance(data, list)
|
|
assert len(data) == 3
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_api_get_goal_stats(authenticated_client, app, user, project):
|
|
"""Test API endpoint for goal statistics."""
|
|
with app.app_context():
|
|
# Create a few goals (their actual status will be determined by update_status)
|
|
# Goal 1: Completed in the past with enough hours
|
|
past_week_start = date.today() - timedelta(days=14)
|
|
goal1 = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=past_week_start
|
|
)
|
|
db.session.add(goal1)
|
|
|
|
# Goal 2: Active week
|
|
current_week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal2 = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0,
|
|
week_start_date=current_week_start
|
|
)
|
|
db.session.add(goal2)
|
|
|
|
db.session.commit()
|
|
|
|
response = authenticated_client.get('/api/goals/stats')
|
|
assert response.status_code == 200
|
|
|
|
data = response.get_json()
|
|
# Verify the structure is correct
|
|
assert 'total_goals' in data
|
|
assert 'completed' in data
|
|
assert 'failed' in data
|
|
assert 'active' in data
|
|
assert 'completion_rate' in data
|
|
assert data['total_goals'] == 2
|
|
# Verify counts are consistent (completed + failed + active + cancelled should equal total)
|
|
assert (data.get('completed', 0) + data.get('failed', 0) +
|
|
data.get('active', 0) + data.get('cancelled', 0)) == data['total_goals']
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_weekly_goal_user_relationship(app, user):
|
|
"""Test weekly goal user relationship."""
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id,
|
|
target_hours=40.0
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
db.session.refresh(goal)
|
|
assert goal.user is not None
|
|
assert goal.user.id == user.id
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_has_weekly_goals_relationship(app, user):
|
|
"""Test that user has weekly_goals relationship."""
|
|
with app.app_context():
|
|
# Re-query the user to ensure it's in the current session
|
|
from app.models import User
|
|
user_obj = User.query.get(user.id)
|
|
|
|
goal1 = WeeklyTimeGoal(user_id=user_obj.id, target_hours=40.0)
|
|
goal2 = WeeklyTimeGoal(
|
|
user_id=user_obj.id,
|
|
target_hours=35.0,
|
|
week_start_date=date.today() - timedelta(weeks=1, days=date.today().weekday())
|
|
)
|
|
db.session.add_all([goal1, goal2])
|
|
db.session.commit()
|
|
|
|
db.session.refresh(user_obj)
|
|
assert user_obj.weekly_goals.count() >= 2
|
|
|