mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-20 05:10:26 -05:00
01dea80245
- days_remaining now counts only Mon-Fri when exclude_weekends=True - Fixes avg hours/day for 5-day goals (e.g. 30h shows 6h/day not 5h/day) - Add tests for exclude_weekends: creation, days_remaining, avg hours/day, weekend exclusion, and actual_hours excluding weekend entries Fixes GitHub Discussion #441
670 lines
24 KiB
Python
670 lines
24 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
|
|
from factories import TimeEntryFactory
|
|
|
|
|
|
# ============================================================================
|
|
# 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
|
|
TimeEntryFactory(
|
|
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,
|
|
)
|
|
TimeEntryFactory(
|
|
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,
|
|
)
|
|
|
|
# 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
|
|
TimeEntryFactory(
|
|
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.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
|
|
TimeEntryFactory(
|
|
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.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
|
|
TimeEntryFactory(
|
|
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.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
|
|
TimeEntryFactory(
|
|
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.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
|
|
TimeEntryFactory(
|
|
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,
|
|
)
|
|
|
|
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
|
|
TimeEntryFactory(
|
|
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,
|
|
)
|
|
|
|
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 (POST request happens outside app context)
|
|
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 - re-query within app context
|
|
with app.app_context():
|
|
goal = WeeklyTimeGoal.query.get(goal_id)
|
|
assert goal is not None, "Goal should still exist after edit"
|
|
assert goal.target_hours == 35.0, f"Expected 35.0, got {goal.target_hours}"
|
|
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 (POST request happens outside app context)
|
|
response = authenticated_client.post(f"/goals/{goal_id}/delete", follow_redirects=True)
|
|
assert response.status_code == 200
|
|
|
|
# Check goal was deleted - re-query within app context
|
|
with app.app_context():
|
|
deleted_goal = WeeklyTimeGoal.query.get(goal_id)
|
|
assert deleted_goal is None, f"Goal should be deleted but found: {deleted_goal}"
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_view_weekly_goal(authenticated_client, app, user):
|
|
"""Test viewing a specific weekly goal."""
|
|
# Create goal outside app context to ensure it persists
|
|
goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
|
|
with app.app_context():
|
|
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."""
|
|
# Create goal outside app context to ensure it persists
|
|
goal = WeeklyTimeGoal(user_id=user.id, target_hours=40.0)
|
|
with app.app_context():
|
|
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."""
|
|
# Create multiple goals
|
|
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()),
|
|
)
|
|
goals.append(goal)
|
|
|
|
with app.app_context():
|
|
for goal in goals:
|
|
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."""
|
|
# 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)
|
|
|
|
# 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)
|
|
|
|
with app.app_context():
|
|
db.session.add(goal1)
|
|
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
|
|
|
|
|
|
# ============================================================================
|
|
# WeeklyTimeGoal Exclude Weekends Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_exclude_weekends_creation(app, user):
|
|
"""Test weekly goal creation with exclude_weekends=True."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
assert goal.exclude_weekends is True
|
|
assert goal.week_start_date == week_start
|
|
# Week should end on Friday (4 days after Monday)
|
|
assert goal.week_end_date == week_start + timedelta(days=4)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_days_remaining_exclude_weekends_monday(app, user):
|
|
"""Test days_remaining calculation with exclude_weekends=True starting on Monday."""
|
|
with app.app_context():
|
|
# Create a goal starting on Monday
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# If today is Monday, there should be 5 days remaining (Mon-Fri)
|
|
# If today is Tuesday, there should be 4 days remaining (Tue-Fri)
|
|
# etc.
|
|
today = date.today()
|
|
if today >= week_start and today <= goal.week_end_date:
|
|
# Calculate expected days remaining (weekdays only)
|
|
expected_days = 0
|
|
current_date = today
|
|
while current_date <= goal.week_end_date:
|
|
if current_date.weekday() < 5: # Monday through Friday
|
|
expected_days += 1
|
|
current_date += timedelta(days=1)
|
|
assert goal.days_remaining == expected_days
|
|
# Should be between 1 and 5 days
|
|
assert 1 <= goal.days_remaining <= 5
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_average_hours_per_day_exclude_weekends(app, user):
|
|
"""Test average_hours_per_day calculation with exclude_weekends=True."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# With 30 hours target and 5 days (Mon-Fri), should be 6 hours per day
|
|
# But this depends on how many days are remaining
|
|
today = date.today()
|
|
if today >= week_start and today <= goal.week_end_date and goal.days_remaining > 0:
|
|
# If we're at the start of the week with 5 days remaining
|
|
# and 30 hours target, should be 6 hours per day
|
|
if goal.days_remaining == 5:
|
|
# No hours worked yet, so remaining = 30
|
|
assert goal.average_hours_per_day == 6.0
|
|
# Verify the calculation is correct
|
|
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_days_remaining_excludes_weekends(app, user):
|
|
"""Test that days_remaining excludes weekends when exclude_weekends=True."""
|
|
with app.app_context():
|
|
# Create a goal for a specific week (Monday to Friday)
|
|
# Use a known Monday date
|
|
week_start = date(2024, 1, 1) # Monday, January 1, 2024
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Verify week_end_date is Friday (Jan 5, 2024)
|
|
assert goal.week_end_date == date(2024, 1, 5)
|
|
|
|
# Test with different "today" dates
|
|
# If today is Monday (Jan 1), days_remaining should be 5 (Mon-Fri)
|
|
# If today is Wednesday (Jan 3), days_remaining should be 3 (Wed-Fri)
|
|
# If today is Saturday (Jan 6), days_remaining should be 0 (past week_end_date)
|
|
|
|
# Mock different dates by temporarily modifying the goal's week_end_date
|
|
# Actually, we can't easily mock local_now() in this test, so we'll test the logic
|
|
# by verifying the property works correctly for the current date
|
|
today = date.today()
|
|
if today >= week_start and today <= goal.week_end_date:
|
|
# Count weekdays manually
|
|
expected_weekdays = 0
|
|
current_date = today
|
|
while current_date <= goal.week_end_date:
|
|
if current_date.weekday() < 5:
|
|
expected_weekdays += 1
|
|
current_date += timedelta(days=1)
|
|
assert goal.days_remaining == expected_weekdays
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_weekly_goal_actual_hours_excludes_weekends(app, user, project):
|
|
"""Test that actual_hours calculation excludes weekends when exclude_weekends=True."""
|
|
with app.app_context():
|
|
week_start = date.today() - timedelta(days=date.today().weekday())
|
|
goal = WeeklyTimeGoal(
|
|
user_id=user.id, target_hours=30.0, week_start_date=week_start, exclude_weekends=True
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
|
|
# Add time entry on Monday (weekday)
|
|
TimeEntryFactory(
|
|
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,
|
|
)
|
|
|
|
# Add time entry on Saturday (weekend) - should be excluded
|
|
saturday = week_start + timedelta(days=5)
|
|
TimeEntryFactory(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.combine(saturday, datetime.min.time()),
|
|
end_time=datetime.combine(saturday, datetime.min.time()) + timedelta(hours=5),
|
|
duration_seconds=5 * 3600,
|
|
)
|
|
|
|
db.session.refresh(goal)
|
|
|
|
# Should only count Monday's 8 hours, not Saturday's 5 hours
|
|
assert goal.actual_hours == 8.0
|