mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-05 11:09:55 -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.
204 lines
8.0 KiB
Python
204 lines
8.0 KiB
Python
"""Unit tests for time rounding functionality"""
|
|
|
|
import pytest
|
|
from app.utils.time_rounding import (
|
|
round_time_duration,
|
|
apply_user_rounding,
|
|
format_rounding_interval,
|
|
get_available_rounding_intervals,
|
|
get_available_rounding_methods,
|
|
get_user_rounding_settings,
|
|
)
|
|
|
|
|
|
class TestRoundTimeDuration:
|
|
"""Test the core time rounding function"""
|
|
|
|
def test_no_rounding_when_interval_is_one(self):
|
|
"""Test that rounding_minutes=1 returns exact duration"""
|
|
assert round_time_duration(3720, 1, "nearest") == 3720
|
|
assert round_time_duration(3722, 1, "up") == 3722
|
|
assert round_time_duration(3718, 1, "down") == 3718
|
|
|
|
def test_round_to_nearest_5_minutes(self):
|
|
"""Test rounding to nearest 5 minute interval"""
|
|
# 62 minutes should round to 60 minutes (nearest 5-min interval)
|
|
assert round_time_duration(3720, 5, "nearest") == 3600
|
|
# 63 minutes should round to 65 minutes
|
|
assert round_time_duration(3780, 5, "nearest") == 3900
|
|
# 2 minutes should round to 0
|
|
assert round_time_duration(120, 5, "nearest") == 0
|
|
# 3 minutes should round to 5
|
|
assert round_time_duration(180, 5, "nearest") == 300
|
|
|
|
def test_round_to_nearest_15_minutes(self):
|
|
"""Test rounding to nearest 15 minute interval"""
|
|
# 62 minutes should round to 60 minutes
|
|
assert round_time_duration(3720, 15, "nearest") == 3600
|
|
# 68 minutes should round to 75 minutes
|
|
assert round_time_duration(4080, 15, "nearest") == 4500
|
|
# 7 minutes should round to 0
|
|
assert round_time_duration(420, 15, "nearest") == 0
|
|
# 8 minutes should round to 15
|
|
assert round_time_duration(480, 15, "nearest") == 900
|
|
|
|
def test_round_up(self):
|
|
"""Test always rounding up (ceiling)"""
|
|
# 62 minutes with 15-min interval rounds up to 75
|
|
assert round_time_duration(3720, 15, "up") == 4500
|
|
# 60 minutes with 15-min interval stays 60 (exact match)
|
|
assert round_time_duration(3600, 15, "up") == 3600
|
|
# 61 minutes with 15-min interval rounds up to 75
|
|
assert round_time_duration(3660, 15, "up") == 4500
|
|
# 1 minute with 5-min interval rounds up to 5
|
|
assert round_time_duration(60, 5, "up") == 300
|
|
|
|
def test_round_down(self):
|
|
"""Test always rounding down (floor)"""
|
|
# 62 minutes with 15-min interval rounds down to 60
|
|
assert round_time_duration(3720, 15, "down") == 3600
|
|
# 74 minutes with 15-min interval rounds down to 60
|
|
assert round_time_duration(4440, 15, "down") == 3600
|
|
# 75 minutes with 15-min interval stays 75 (exact match)
|
|
assert round_time_duration(4500, 15, "down") == 4500
|
|
|
|
def test_round_to_hour(self):
|
|
"""Test rounding to 1 hour intervals"""
|
|
# 62 minutes rounds to 60 minutes (nearest hour)
|
|
assert round_time_duration(3720, 60, "nearest") == 3600
|
|
# 90 minutes rounds to 120 minutes (nearest hour)
|
|
assert round_time_duration(5400, 60, "nearest") == 7200
|
|
# 89 minutes rounds to 60 minutes (nearest hour)
|
|
assert round_time_duration(5340, 60, "nearest") == 3600
|
|
|
|
def test_invalid_rounding_method_defaults_to_nearest(self):
|
|
"""Test that invalid rounding method falls back to 'nearest'"""
|
|
result = round_time_duration(3720, 15, "invalid")
|
|
expected = round_time_duration(3720, 15, "nearest")
|
|
assert result == expected
|
|
|
|
def test_zero_duration(self):
|
|
"""Test handling of zero duration"""
|
|
assert round_time_duration(0, 15, "nearest") == 0
|
|
assert round_time_duration(0, 15, "up") == 0
|
|
assert round_time_duration(0, 15, "down") == 0
|
|
|
|
def test_very_small_durations(self):
|
|
"""Test rounding of very small durations"""
|
|
# 30 seconds with 5-min rounding
|
|
assert round_time_duration(30, 5, "nearest") == 0
|
|
assert round_time_duration(30, 5, "up") == 300 # Rounds up to 5 minutes
|
|
assert round_time_duration(30, 5, "down") == 0
|
|
|
|
def test_very_large_durations(self):
|
|
"""Test rounding of large durations"""
|
|
# 8 hours 7 minutes (487 minutes) with 15-min rounding
|
|
# 487 / 15 = 32.47 -> rounds to 32 intervals = 480 minutes = 28800 seconds
|
|
assert round_time_duration(29220, 15, "nearest") == 28800 # 480 minutes (8 hours)
|
|
# 8 hours 8 minutes (488 minutes) with 15-min rounding
|
|
# 488 / 15 = 32.53 -> rounds to 33 intervals = 495 minutes = 29700 seconds
|
|
assert round_time_duration(29280, 15, "nearest") == 29700 # 495 minutes (8 hours 15 min)
|
|
|
|
|
|
class TestApplyUserRounding:
|
|
"""Test applying user-specific rounding preferences"""
|
|
|
|
def test_with_rounding_disabled(self):
|
|
"""Test that rounding is skipped when disabled for user"""
|
|
|
|
class MockUser:
|
|
time_rounding_enabled = False
|
|
time_rounding_minutes = 15
|
|
time_rounding_method = "nearest"
|
|
|
|
user = MockUser()
|
|
assert apply_user_rounding(3720, user) == 3720
|
|
|
|
def test_with_rounding_enabled(self):
|
|
"""Test that rounding is applied when enabled"""
|
|
|
|
class MockUser:
|
|
time_rounding_enabled = True
|
|
time_rounding_minutes = 15
|
|
time_rounding_method = "nearest"
|
|
|
|
user = MockUser()
|
|
# 62 minutes should round to 60 with 15-min interval
|
|
assert apply_user_rounding(3720, user) == 3600
|
|
|
|
def test_different_user_preferences(self):
|
|
"""Test that different users can have different rounding settings"""
|
|
|
|
class MockUser1:
|
|
time_rounding_enabled = True
|
|
time_rounding_minutes = 5
|
|
time_rounding_method = "up"
|
|
|
|
class MockUser2:
|
|
time_rounding_enabled = True
|
|
time_rounding_minutes = 15
|
|
time_rounding_method = "down"
|
|
|
|
duration = 3720 # 62 minutes
|
|
|
|
# User 1: 5-min up -> 65 minutes
|
|
assert apply_user_rounding(duration, MockUser1()) == 3900
|
|
|
|
# User 2: 15-min down -> 60 minutes
|
|
assert apply_user_rounding(duration, MockUser2()) == 3600
|
|
|
|
def test_get_user_rounding_settings(self):
|
|
"""Test retrieving user rounding settings"""
|
|
|
|
class MockUser:
|
|
time_rounding_enabled = True
|
|
time_rounding_minutes = 10
|
|
time_rounding_method = "up"
|
|
|
|
settings = get_user_rounding_settings(MockUser())
|
|
assert settings["enabled"] is True
|
|
assert settings["minutes"] == 10
|
|
assert settings["method"] == "up"
|
|
|
|
def test_get_user_rounding_settings_with_defaults(self):
|
|
"""Test default values when attributes don't exist"""
|
|
|
|
class MockUser:
|
|
pass
|
|
|
|
settings = get_user_rounding_settings(MockUser())
|
|
assert settings["enabled"] is True
|
|
assert settings["minutes"] == 1
|
|
assert settings["method"] == "nearest"
|
|
|
|
|
|
class TestFormattingFunctions:
|
|
"""Test formatting and helper functions"""
|
|
|
|
def test_format_rounding_interval(self):
|
|
"""Test formatting of rounding intervals"""
|
|
assert format_rounding_interval(1) == "No rounding (exact time)"
|
|
assert format_rounding_interval(5) == "5 minutes"
|
|
assert format_rounding_interval(15) == "15 minutes"
|
|
assert format_rounding_interval(30) == "30 minutes"
|
|
assert format_rounding_interval(60) == "1 hour"
|
|
assert format_rounding_interval(120) == "2 hours"
|
|
|
|
def test_get_available_rounding_intervals(self):
|
|
"""Test getting available rounding intervals"""
|
|
intervals = get_available_rounding_intervals()
|
|
assert len(intervals) == 6
|
|
assert (1, "No rounding (exact time)") in intervals
|
|
assert (5, "5 minutes") in intervals
|
|
assert (60, "1 hour") in intervals
|
|
|
|
def test_get_available_rounding_methods(self):
|
|
"""Test getting available rounding methods"""
|
|
methods = get_available_rounding_methods()
|
|
assert len(methods) == 3
|
|
|
|
method_values = [m[0] for m in methods]
|
|
assert "nearest" in method_values
|
|
assert "up" in method_values
|
|
assert "down" in method_values
|