Files
TimeTracker/tests/test_time_rounding.py
Dries Peeters 90dde470da style: standardize code formatting and normalize line endings
- 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.
2025-11-28 20:05:37 +01:00

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