Cleanup other functions to use extern corelibs
Remove tests for parts that have moved to stand alone libraries
This commit is contained in:
@@ -8,9 +8,9 @@ import re
|
||||
import configparser
|
||||
from typing import Any, Tuple, Sequence, cast
|
||||
from pathlib import Path
|
||||
from corelibs_var.var_helpers import is_int, is_float, str_to_bool
|
||||
from corelibs.logging_handling.log import Log
|
||||
from corelibs.iterator_handling.list_helpers import convert_to_list, is_list_in_list
|
||||
from corelibs.var_handling.var_helpers import is_int, is_float, str_to_bool
|
||||
from corelibs.config_handling.settings_loader_handling.settings_loader_check import SettingsLoaderCheck
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ show_position(file pos optional)
|
||||
import time
|
||||
from typing import Literal
|
||||
from math import floor
|
||||
from corelibs.datetime_handling.timestamp_convert import convert_timestamp
|
||||
from corelibs_datetime.timestamp_convert import convert_timestamp
|
||||
from corelibs.string_handling.byte_helpers import format_bytes
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Unit tests for encryption_handling module
|
||||
"""
|
||||
@@ -1,205 +0,0 @@
|
||||
"""
|
||||
Unit tests for convert_to_seconds function from timestamp_strings module.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from corelibs.datetime_handling.timestamp_convert import convert_to_seconds, TimeParseError, TimeUnitError
|
||||
|
||||
|
||||
class TestConvertToSeconds:
|
||||
"""Test class for convert_to_seconds function."""
|
||||
|
||||
def test_numeric_input_int(self):
|
||||
"""Test with integer input."""
|
||||
assert convert_to_seconds(42) == 42
|
||||
assert convert_to_seconds(0) == 0
|
||||
assert convert_to_seconds(-5) == -5
|
||||
|
||||
def test_numeric_input_float(self):
|
||||
"""Test with float input."""
|
||||
assert convert_to_seconds(42.7) == 43 # rounds to 43
|
||||
assert convert_to_seconds(42.3) == 42 # rounds to 42
|
||||
assert convert_to_seconds(42.5) == 42 # rounds to 42 (banker's rounding)
|
||||
assert convert_to_seconds(0.0) == 0
|
||||
assert convert_to_seconds(-5.7) == -6
|
||||
|
||||
def test_numeric_string_input(self):
|
||||
"""Test with numeric string input."""
|
||||
assert convert_to_seconds("42") == 42
|
||||
assert convert_to_seconds("42.7") == 43
|
||||
assert convert_to_seconds("42.3") == 42
|
||||
assert convert_to_seconds("0") == 0
|
||||
assert convert_to_seconds("-5.7") == -6
|
||||
|
||||
def test_single_unit_seconds(self):
|
||||
"""Test with seconds unit."""
|
||||
assert convert_to_seconds("30s") == 30
|
||||
assert convert_to_seconds("1s") == 1
|
||||
assert convert_to_seconds("0s") == 0
|
||||
|
||||
def test_single_unit_minutes(self):
|
||||
"""Test with minutes unit."""
|
||||
assert convert_to_seconds("5m") == 300 # 5 * 60
|
||||
assert convert_to_seconds("1m") == 60
|
||||
assert convert_to_seconds("0m") == 0
|
||||
|
||||
def test_single_unit_hours(self):
|
||||
"""Test with hours unit."""
|
||||
assert convert_to_seconds("2h") == 7200 # 2 * 3600
|
||||
assert convert_to_seconds("1h") == 3600
|
||||
assert convert_to_seconds("0h") == 0
|
||||
|
||||
def test_single_unit_days(self):
|
||||
"""Test with days unit."""
|
||||
assert convert_to_seconds("1d") == 86400 # 1 * 86400
|
||||
assert convert_to_seconds("2d") == 172800 # 2 * 86400
|
||||
assert convert_to_seconds("0d") == 0
|
||||
|
||||
def test_single_unit_months(self):
|
||||
"""Test with months unit (30 days * 12 = 1 year)."""
|
||||
# Note: The code has M: 2592000 * 12 which is 1 year, not 1 month
|
||||
# This seems like a bug in the original code, but testing what it actually does
|
||||
assert convert_to_seconds("1M") == 31104000 # 2592000 * 12
|
||||
assert convert_to_seconds("2M") == 62208000 # 2 * 2592000 * 12
|
||||
|
||||
def test_single_unit_years(self):
|
||||
"""Test with years unit."""
|
||||
assert convert_to_seconds("1Y") == 31536000 # 365 * 86400
|
||||
assert convert_to_seconds("2Y") == 63072000 # 2 * 365 * 86400
|
||||
|
||||
def test_long_unit_names(self):
|
||||
"""Test with long unit names."""
|
||||
assert convert_to_seconds("1year") == 31536000
|
||||
assert convert_to_seconds("2years") == 63072000
|
||||
assert convert_to_seconds("1month") == 31104000
|
||||
assert convert_to_seconds("2months") == 62208000
|
||||
assert convert_to_seconds("1day") == 86400
|
||||
assert convert_to_seconds("2days") == 172800
|
||||
assert convert_to_seconds("1hour") == 3600
|
||||
assert convert_to_seconds("2hours") == 7200
|
||||
assert convert_to_seconds("1minute") == 60
|
||||
assert convert_to_seconds("2minutes") == 120
|
||||
assert convert_to_seconds("30min") == 1800
|
||||
assert convert_to_seconds("1second") == 1
|
||||
assert convert_to_seconds("2seconds") == 2
|
||||
assert convert_to_seconds("30sec") == 30
|
||||
|
||||
def test_multiple_units(self):
|
||||
"""Test with multiple units combined."""
|
||||
assert convert_to_seconds("1h30m") == 5400 # 3600 + 1800
|
||||
assert convert_to_seconds("1d2h") == 93600 # 86400 + 7200
|
||||
assert convert_to_seconds("1h30m45s") == 5445 # 3600 + 1800 + 45
|
||||
assert convert_to_seconds("2d3h4m5s") == 183845 # 172800 + 10800 + 240 + 5
|
||||
|
||||
def test_multiple_units_with_spaces(self):
|
||||
"""Test with multiple units and spaces."""
|
||||
assert convert_to_seconds("1h 30m") == 5400
|
||||
assert convert_to_seconds("1d 2h") == 93600
|
||||
assert convert_to_seconds("1h 30m 45s") == 5445
|
||||
assert convert_to_seconds("2d 3h 4m 5s") == 183845
|
||||
|
||||
def test_mixed_unit_formats(self):
|
||||
"""Test with mixed short and long unit names."""
|
||||
assert convert_to_seconds("1hour 30min") == 5400
|
||||
assert convert_to_seconds("1day 2hours") == 93600
|
||||
assert convert_to_seconds("1h 30minutes 45sec") == 5445
|
||||
|
||||
def test_negative_values(self):
|
||||
"""Test with negative time strings."""
|
||||
assert convert_to_seconds("-30s") == -30
|
||||
assert convert_to_seconds("-1h") == -3600
|
||||
assert convert_to_seconds("-1h30m") == -5400
|
||||
assert convert_to_seconds("-2d3h4m5s") == -183845
|
||||
|
||||
def test_case_insensitive_long_names(self):
|
||||
"""Test that long unit names are case insensitive."""
|
||||
assert convert_to_seconds("1Hour") == 3600
|
||||
assert convert_to_seconds("1MINUTE") == 60
|
||||
assert convert_to_seconds("1Day") == 86400
|
||||
assert convert_to_seconds("2YEARS") == 63072000
|
||||
|
||||
def test_duplicate_units_error(self):
|
||||
"""Test that duplicate units raise TimeParseError."""
|
||||
with pytest.raises(TimeParseError, match="Unit 'h' appears more than once"):
|
||||
convert_to_seconds("1h2h")
|
||||
|
||||
with pytest.raises(TimeParseError, match="Unit 's' appears more than once"):
|
||||
convert_to_seconds("30s45s")
|
||||
|
||||
with pytest.raises(TimeParseError, match="Unit 'm' appears more than once"):
|
||||
convert_to_seconds("1m30m")
|
||||
|
||||
def test_invalid_units_error(self):
|
||||
"""Test that invalid units raise TimeUnitError."""
|
||||
with pytest.raises(TimeUnitError, match="Unit 'x' is not a valid unit name"):
|
||||
convert_to_seconds("30x")
|
||||
|
||||
with pytest.raises(TimeUnitError, match="Unit 'invalid' is not a valid unit name"):
|
||||
convert_to_seconds("1invalid")
|
||||
|
||||
with pytest.raises(TimeUnitError, match="Unit 'z' is not a valid unit name"):
|
||||
convert_to_seconds("1h30z")
|
||||
|
||||
def test_empty_string(self):
|
||||
"""Test with empty string."""
|
||||
assert convert_to_seconds("") == 0
|
||||
|
||||
def test_no_matches(self):
|
||||
"""Test with string that has no time units."""
|
||||
assert convert_to_seconds("hello") == 0
|
||||
assert convert_to_seconds("no time here") == 0
|
||||
|
||||
def test_zero_values(self):
|
||||
"""Test with zero values for different units."""
|
||||
assert convert_to_seconds("0s") == 0
|
||||
assert convert_to_seconds("0m") == 0
|
||||
assert convert_to_seconds("0h") == 0
|
||||
assert convert_to_seconds("0d") == 0
|
||||
assert convert_to_seconds("0h0m0s") == 0
|
||||
|
||||
def test_large_values(self):
|
||||
"""Test with large time values."""
|
||||
assert convert_to_seconds("999d") == 86313600 # 999 * 86400
|
||||
assert convert_to_seconds("100Y") == 3153600000 # 100 * 31536000
|
||||
|
||||
def test_order_independence(self):
|
||||
"""Test that order of units doesn't matter."""
|
||||
assert convert_to_seconds("30m1h") == 5400 # same as 1h30m
|
||||
assert convert_to_seconds("45s30m1h") == 5445 # same as 1h30m45s
|
||||
assert convert_to_seconds("5s4m3h2d") == 183845 # same as 2d3h4m5s
|
||||
|
||||
def test_whitespace_handling(self):
|
||||
"""Test various whitespace scenarios."""
|
||||
assert convert_to_seconds("1 h") == 3600
|
||||
assert convert_to_seconds("1h 30m") == 5400
|
||||
assert convert_to_seconds(" 1h30m ") == 5400
|
||||
assert convert_to_seconds("1h\t30m") == 5400
|
||||
|
||||
def test_mixed_case_short_units(self):
|
||||
"""Test that short units work with different cases."""
|
||||
# Note: The regex only matches [a-zA-Z]+ so case matters for the lookup
|
||||
with pytest.raises(TimeUnitError, match="Unit 'H' is not a valid unit name"):
|
||||
convert_to_seconds("1H") # 'H' is not in unit_factors, raises error
|
||||
assert convert_to_seconds("1h") == 3600 # lowercase works
|
||||
|
||||
def test_boundary_conditions(self):
|
||||
"""Test boundary conditions and edge cases."""
|
||||
# Test with leading zeros
|
||||
assert convert_to_seconds("01h") == 3600
|
||||
assert convert_to_seconds("001m") == 60
|
||||
|
||||
# Test very small values
|
||||
assert convert_to_seconds("1s") == 1
|
||||
|
||||
def test_negative_with_multiple_units(self):
|
||||
"""Test negative values with multiple units."""
|
||||
assert convert_to_seconds("-1h30m45s") == -5445
|
||||
assert convert_to_seconds("-2d3h") == -183600
|
||||
|
||||
def test_duplicate_with_long_names(self):
|
||||
"""Test duplicate detection with long unit names."""
|
||||
with pytest.raises(TimeParseError, match="Unit 'h' appears more than once"):
|
||||
convert_to_seconds("1hour2h") # both resolve to 'h'
|
||||
|
||||
with pytest.raises(TimeParseError, match="Unit 's' appears more than once"):
|
||||
convert_to_seconds("1second30sec") # both resolve to 's'
|
||||
@@ -1,737 +0,0 @@
|
||||
"""
|
||||
PyTest: datetime_handling/datetime_helpers
|
||||
"""
|
||||
|
||||
from datetime import datetime, time
|
||||
from zoneinfo import ZoneInfo
|
||||
import pytest
|
||||
|
||||
from corelibs.datetime_handling.datetime_helpers import (
|
||||
create_time,
|
||||
get_system_timezone,
|
||||
parse_timezone_data,
|
||||
get_datetime_iso8601,
|
||||
validate_date,
|
||||
parse_flexible_date,
|
||||
compare_dates,
|
||||
find_newest_datetime_in_list,
|
||||
parse_day_of_week_range,
|
||||
parse_time_range,
|
||||
times_overlap_or_connect,
|
||||
is_time_in_range,
|
||||
reorder_weekdays_from_today,
|
||||
DAYS_OF_WEEK_LONG_TO_SHORT,
|
||||
DAYS_OF_WEEK_ISO,
|
||||
DAYS_OF_WEEK_ISO_REVERSED,
|
||||
)
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""Test suite for module constants"""
|
||||
|
||||
def test_days_of_week_long_to_short(self):
|
||||
"""Test DAYS_OF_WEEK_LONG_TO_SHORT dictionary"""
|
||||
assert DAYS_OF_WEEK_LONG_TO_SHORT['Monday'] == 'Mon'
|
||||
assert DAYS_OF_WEEK_LONG_TO_SHORT['Tuesday'] == 'Tue'
|
||||
assert DAYS_OF_WEEK_LONG_TO_SHORT['Friday'] == 'Fri'
|
||||
assert DAYS_OF_WEEK_LONG_TO_SHORT['Sunday'] == 'Sun'
|
||||
assert len(DAYS_OF_WEEK_LONG_TO_SHORT) == 7
|
||||
|
||||
def test_days_of_week_iso(self):
|
||||
"""Test DAYS_OF_WEEK_ISO dictionary"""
|
||||
assert DAYS_OF_WEEK_ISO[1] == 'Mon'
|
||||
assert DAYS_OF_WEEK_ISO[5] == 'Fri'
|
||||
assert DAYS_OF_WEEK_ISO[7] == 'Sun'
|
||||
assert len(DAYS_OF_WEEK_ISO) == 7
|
||||
|
||||
def test_days_of_week_iso_reversed(self):
|
||||
"""Test DAYS_OF_WEEK_ISO_REVERSED dictionary"""
|
||||
assert DAYS_OF_WEEK_ISO_REVERSED['Mon'] == 1
|
||||
assert DAYS_OF_WEEK_ISO_REVERSED['Fri'] == 5
|
||||
assert DAYS_OF_WEEK_ISO_REVERSED['Sun'] == 7
|
||||
assert len(DAYS_OF_WEEK_ISO_REVERSED) == 7
|
||||
|
||||
|
||||
class TestCreateTime:
|
||||
"""Test suite for create_time function"""
|
||||
|
||||
def test_create_time_default_format(self):
|
||||
"""Test create_time with default format"""
|
||||
timestamp = 1609459200.0 # 2021-01-01 00:00:00 UTC
|
||||
result = create_time(timestamp)
|
||||
# Result depends on system timezone, so just check format
|
||||
assert len(result) == 19
|
||||
assert '-' in result
|
||||
assert ':' in result
|
||||
|
||||
def test_create_time_custom_format(self):
|
||||
"""Test create_time with custom format"""
|
||||
timestamp = 1609459200.0
|
||||
result = create_time(timestamp, "%Y/%m/%d")
|
||||
# Check basic format structure
|
||||
assert '/' in result
|
||||
assert len(result) == 10
|
||||
|
||||
def test_create_time_with_microseconds(self):
|
||||
"""Test create_time with microseconds in format"""
|
||||
timestamp = 1609459200.123456
|
||||
result = create_time(timestamp, "%Y-%m-%d %H:%M:%S")
|
||||
assert len(result) == 19
|
||||
|
||||
|
||||
class TestGetSystemTimezone:
|
||||
"""Test suite for get_system_timezone function"""
|
||||
|
||||
def test_get_system_timezone_returns_tuple(self):
|
||||
"""Test that get_system_timezone returns a tuple"""
|
||||
result = get_system_timezone()
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_get_system_timezone_returns_valid_data(self):
|
||||
"""Test that get_system_timezone returns valid timezone info"""
|
||||
system_tz, timezone_name = get_system_timezone()
|
||||
assert system_tz is not None
|
||||
assert isinstance(timezone_name, str)
|
||||
assert len(timezone_name) > 0
|
||||
|
||||
|
||||
class TestParseTimezoneData:
|
||||
"""Test suite for parse_timezone_data function"""
|
||||
|
||||
def test_parse_timezone_data_valid_timezone(self):
|
||||
"""Test parse_timezone_data with valid timezone string"""
|
||||
result = parse_timezone_data('Asia/Tokyo')
|
||||
assert isinstance(result, ZoneInfo)
|
||||
assert str(result) == 'Asia/Tokyo'
|
||||
|
||||
def test_parse_timezone_data_utc(self):
|
||||
"""Test parse_timezone_data with UTC"""
|
||||
result = parse_timezone_data('UTC')
|
||||
assert isinstance(result, ZoneInfo)
|
||||
assert str(result) == 'UTC'
|
||||
|
||||
def test_parse_timezone_data_empty_string(self):
|
||||
"""Test parse_timezone_data with empty string falls back to system timezone"""
|
||||
result = parse_timezone_data('')
|
||||
assert isinstance(result, ZoneInfo)
|
||||
|
||||
def test_parse_timezone_data_invalid_timezone(self):
|
||||
"""Test parse_timezone_data with invalid timezone falls back to system timezone"""
|
||||
# Invalid timezones fall back to system timezone or UTC
|
||||
result = parse_timezone_data('Invalid/Timezone')
|
||||
assert isinstance(result, ZoneInfo)
|
||||
# Should be either system timezone or UTC
|
||||
|
||||
def test_parse_timezone_data_none(self):
|
||||
"""Test parse_timezone_data with None falls back to system timezone"""
|
||||
result = parse_timezone_data()
|
||||
assert isinstance(result, ZoneInfo)
|
||||
|
||||
def test_parse_timezone_data_various_timezones(self):
|
||||
"""Test parse_timezone_data with various timezone strings"""
|
||||
timezones = ['America/New_York', 'Europe/London', 'Asia/Seoul']
|
||||
for tz in timezones:
|
||||
result = parse_timezone_data(tz)
|
||||
assert isinstance(result, ZoneInfo)
|
||||
assert str(result) == tz
|
||||
|
||||
|
||||
class TestGetDatetimeIso8601:
|
||||
"""Test suite for get_datetime_iso8601 function"""
|
||||
|
||||
def test_get_datetime_iso8601_default_params(self):
|
||||
"""Test get_datetime_iso8601 with default parameters"""
|
||||
result = get_datetime_iso8601()
|
||||
# Should be in ISO 8601 format with T separator and microseconds
|
||||
assert 'T' in result
|
||||
assert '.' in result # microseconds
|
||||
# Check basic ISO 8601 format
|
||||
datetime.fromisoformat(result) # Should not raise
|
||||
|
||||
def test_get_datetime_iso8601_custom_timezone_string(self):
|
||||
"""Test get_datetime_iso8601 with custom timezone string"""
|
||||
result = get_datetime_iso8601('UTC')
|
||||
assert '+00:00' in result or 'Z' in result or result.endswith('+00:00')
|
||||
|
||||
def test_get_datetime_iso8601_custom_timezone_zoneinfo(self):
|
||||
"""Test get_datetime_iso8601 with ZoneInfo object"""
|
||||
tz = ZoneInfo('Asia/Tokyo')
|
||||
result = get_datetime_iso8601(tz)
|
||||
assert 'T' in result
|
||||
datetime.fromisoformat(result) # Should not raise
|
||||
|
||||
def test_get_datetime_iso8601_custom_separator(self):
|
||||
"""Test get_datetime_iso8601 with custom separator"""
|
||||
result = get_datetime_iso8601(sep=' ')
|
||||
assert ' ' in result
|
||||
assert 'T' not in result
|
||||
|
||||
def test_get_datetime_iso8601_different_timespec(self):
|
||||
"""Test get_datetime_iso8601 with different timespec values"""
|
||||
result_seconds = get_datetime_iso8601(timespec='seconds')
|
||||
assert '.' not in result_seconds # No microseconds
|
||||
|
||||
result_milliseconds = get_datetime_iso8601(timespec='milliseconds')
|
||||
# Should have milliseconds (3 digits after decimal)
|
||||
assert '.' in result_milliseconds
|
||||
|
||||
|
||||
class TestValidateDate:
|
||||
"""Test suite for validate_date function"""
|
||||
|
||||
def test_validate_date_valid_hyphen_format(self):
|
||||
"""Test validate_date with valid Y-m-d format"""
|
||||
assert validate_date('2023-12-25') is True
|
||||
assert validate_date('2024-01-01') is True
|
||||
|
||||
def test_validate_date_valid_slash_format(self):
|
||||
"""Test validate_date with valid Y/m/d format"""
|
||||
assert validate_date('2023/12/25') is True
|
||||
assert validate_date('2024/01/01') is True
|
||||
|
||||
def test_validate_date_invalid_format(self):
|
||||
"""Test validate_date with invalid format"""
|
||||
assert validate_date('25-12-2023') is False
|
||||
assert validate_date('2023.12.25') is False
|
||||
assert validate_date('invalid') is False
|
||||
|
||||
def test_validate_date_invalid_date(self):
|
||||
"""Test validate_date with invalid date values"""
|
||||
assert validate_date('2023-13-01') is False # Invalid month
|
||||
assert validate_date('2023-02-30') is False # Invalid day
|
||||
|
||||
def test_validate_date_with_not_before(self):
|
||||
"""Test validate_date with not_before constraint"""
|
||||
not_before = datetime(2023, 12, 1)
|
||||
assert validate_date('2023-12-25', not_before=not_before) is True
|
||||
assert validate_date('2023-11-25', not_before=not_before) is False
|
||||
|
||||
def test_validate_date_with_not_after(self):
|
||||
"""Test validate_date with not_after constraint"""
|
||||
not_after = datetime(2023, 12, 31)
|
||||
assert validate_date('2023-12-25', not_after=not_after) is True
|
||||
assert validate_date('2024-01-01', not_after=not_after) is False
|
||||
|
||||
def test_validate_date_with_both_constraints(self):
|
||||
"""Test validate_date with both not_before and not_after constraints"""
|
||||
not_before = datetime(2023, 12, 1)
|
||||
not_after = datetime(2023, 12, 31)
|
||||
assert validate_date('2023-12-15', not_before=not_before, not_after=not_after) is True
|
||||
assert validate_date('2023-11-30', not_before=not_before, not_after=not_after) is False
|
||||
assert validate_date('2024-01-01', not_before=not_before, not_after=not_after) is False
|
||||
|
||||
|
||||
class TestParseFlexibleDate:
|
||||
"""Test suite for parse_flexible_date function"""
|
||||
|
||||
def test_parse_flexible_date_iso8601_full(self):
|
||||
"""Test parse_flexible_date with full ISO 8601 format"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
assert result.month == 12
|
||||
assert result.day == 25
|
||||
assert result.hour == 15
|
||||
assert result.minute == 30
|
||||
assert result.second == 45
|
||||
|
||||
def test_parse_flexible_date_iso8601_with_microseconds(self):
|
||||
"""Test parse_flexible_date with microseconds"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45.123456')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.microsecond == 123456
|
||||
|
||||
def test_parse_flexible_date_simple_date(self):
|
||||
"""Test parse_flexible_date with simple date format"""
|
||||
result = parse_flexible_date('2023-12-25')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
assert result.month == 12
|
||||
assert result.day == 25
|
||||
|
||||
def test_parse_flexible_date_with_timezone_string(self):
|
||||
"""Test parse_flexible_date with timezone string"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45', timezone_tz='Asia/Tokyo')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.tzinfo is not None
|
||||
|
||||
def test_parse_flexible_date_with_timezone_zoneinfo(self):
|
||||
"""Test parse_flexible_date with ZoneInfo object"""
|
||||
tz = ZoneInfo('UTC')
|
||||
result = parse_flexible_date('2023-12-25T15:30:45', timezone_tz=tz)
|
||||
assert isinstance(result, datetime)
|
||||
assert result.tzinfo is not None
|
||||
|
||||
def test_parse_flexible_date_with_timezone_no_shift(self):
|
||||
"""Test parse_flexible_date with timezone but no shift"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45', timezone_tz='UTC', shift_time_zone=False)
|
||||
assert isinstance(result, datetime)
|
||||
assert result.hour == 15 # Should not shift
|
||||
|
||||
def test_parse_flexible_date_with_timezone_shift(self):
|
||||
"""Test parse_flexible_date with timezone shift"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45+00:00', timezone_tz='Asia/Tokyo', shift_time_zone=True)
|
||||
assert isinstance(result, datetime)
|
||||
assert result.tzinfo is not None
|
||||
|
||||
def test_parse_flexible_date_missing_t_with_timezone_shift(self):
|
||||
"""Test parse_flexible_date with timezone shift"""
|
||||
result = parse_flexible_date('2023-12-25 15:30:45+00:00', timezone_tz='Asia/Tokyo', shift_time_zone=True)
|
||||
assert isinstance(result, datetime)
|
||||
assert result.tzinfo is not None
|
||||
|
||||
def test_parse_flexible_date_space_separated_datetime(self):
|
||||
"""Test parse_flexible_date with space-separated datetime format"""
|
||||
result = parse_flexible_date('2023-12-25 15:30:45')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
assert result.month == 12
|
||||
assert result.day == 25
|
||||
assert result.hour == 15
|
||||
assert result.minute == 30
|
||||
assert result.second == 45
|
||||
|
||||
def test_parse_flexible_date_space_separated_with_microseconds(self):
|
||||
"""Test parse_flexible_date with space-separated datetime and microseconds"""
|
||||
result = parse_flexible_date('2023-12-25 15:30:45.123456')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
assert result.month == 12
|
||||
assert result.day == 25
|
||||
assert result.hour == 15
|
||||
assert result.minute == 30
|
||||
assert result.second == 45
|
||||
assert result.microsecond == 123456
|
||||
|
||||
def test_parse_flexible_date_t_separated_datetime(self):
|
||||
"""Test parse_flexible_date with T-separated datetime (alternative ISO format)"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
assert result.month == 12
|
||||
assert result.day == 25
|
||||
assert result.hour == 15
|
||||
assert result.minute == 30
|
||||
assert result.second == 45
|
||||
|
||||
def test_parse_flexible_date_t_separated_with_microseconds(self):
|
||||
"""Test parse_flexible_date with T-separated datetime and microseconds"""
|
||||
result = parse_flexible_date('2023-12-25T15:30:45.123456')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
assert result.microsecond == 123456
|
||||
|
||||
def test_parse_flexible_date_invalid_format(self):
|
||||
"""Test parse_flexible_date with invalid format returns None"""
|
||||
result = parse_flexible_date('invalid-date')
|
||||
assert result is None
|
||||
|
||||
def test_parse_flexible_date_whitespace(self):
|
||||
"""Test parse_flexible_date with whitespace"""
|
||||
result = parse_flexible_date(' 2023-12-25 ')
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2023
|
||||
|
||||
|
||||
class TestCompareDates:
|
||||
"""Test suite for compare_dates function"""
|
||||
|
||||
def test_compare_dates_first_newer(self):
|
||||
"""Test compare_dates when first date is newer"""
|
||||
result = compare_dates('2024-01-02', '2024-01-01')
|
||||
assert result is True
|
||||
|
||||
def test_compare_dates_first_older(self):
|
||||
"""Test compare_dates when first date is older"""
|
||||
result = compare_dates('2024-01-01', '2024-01-02')
|
||||
assert result is False
|
||||
|
||||
def test_compare_dates_equal(self):
|
||||
"""Test compare_dates when dates are equal"""
|
||||
result = compare_dates('2024-01-01', '2024-01-01')
|
||||
assert result is False
|
||||
|
||||
def test_compare_dates_with_time(self):
|
||||
"""Test compare_dates with time components (should only compare dates)"""
|
||||
result = compare_dates('2024-01-02T10:00:00', '2024-01-01T23:59:59')
|
||||
assert result is True
|
||||
|
||||
def test_compare_dates_invalid_first_date(self):
|
||||
"""Test compare_dates with invalid first date"""
|
||||
result = compare_dates('invalid', '2024-01-01')
|
||||
assert result is None
|
||||
|
||||
def test_compare_dates_invalid_second_date(self):
|
||||
"""Test compare_dates with invalid second date"""
|
||||
result = compare_dates('2024-01-01', 'invalid')
|
||||
assert result is None
|
||||
|
||||
def test_compare_dates_both_invalid(self):
|
||||
"""Test compare_dates with both dates invalid"""
|
||||
result = compare_dates('invalid1', 'invalid2')
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestFindNewestDatetimeInList:
|
||||
"""Test suite for find_newest_datetime_in_list function"""
|
||||
|
||||
def test_find_newest_datetime_in_list_basic(self):
|
||||
"""Test find_newest_datetime_in_list with basic list"""
|
||||
dates = [
|
||||
'2023-12-25T10:00:00',
|
||||
'2024-01-01T12:00:00',
|
||||
'2023-11-15T08:00:00'
|
||||
]
|
||||
result = find_newest_datetime_in_list(dates)
|
||||
assert result == '2024-01-01T12:00:00'
|
||||
|
||||
def test_find_newest_datetime_in_list_with_timezone(self):
|
||||
"""Test find_newest_datetime_in_list with timezone-aware dates"""
|
||||
dates = [
|
||||
'2025-08-06T16:17:39.747+09:00',
|
||||
'2025-08-05T16:17:39.747+09:00',
|
||||
'2025-08-07T16:17:39.747+09:00'
|
||||
]
|
||||
result = find_newest_datetime_in_list(dates)
|
||||
assert result == '2025-08-07T16:17:39.747+09:00'
|
||||
|
||||
def test_find_newest_datetime_in_list_empty_list(self):
|
||||
"""Test find_newest_datetime_in_list with empty list"""
|
||||
result = find_newest_datetime_in_list([])
|
||||
assert result is None
|
||||
|
||||
def test_find_newest_datetime_in_list_single_date(self):
|
||||
"""Test find_newest_datetime_in_list with single date"""
|
||||
dates = ['2024-01-01T12:00:00']
|
||||
result = find_newest_datetime_in_list(dates)
|
||||
assert result == '2024-01-01T12:00:00'
|
||||
|
||||
def test_find_newest_datetime_in_list_with_invalid_dates(self):
|
||||
"""Test find_newest_datetime_in_list with some invalid dates"""
|
||||
dates = [
|
||||
'2023-12-25T10:00:00',
|
||||
'invalid-date',
|
||||
'2024-01-01T12:00:00'
|
||||
]
|
||||
result = find_newest_datetime_in_list(dates)
|
||||
assert result == '2024-01-01T12:00:00'
|
||||
|
||||
def test_find_newest_datetime_in_list_all_invalid(self):
|
||||
"""Test find_newest_datetime_in_list with all invalid dates"""
|
||||
dates = ['invalid1', 'invalid2', 'invalid3']
|
||||
result = find_newest_datetime_in_list(dates)
|
||||
assert result is None
|
||||
|
||||
def test_find_newest_datetime_in_list_mixed_formats(self):
|
||||
"""Test find_newest_datetime_in_list with mixed date formats"""
|
||||
dates = [
|
||||
'2023-12-25',
|
||||
'2024-01-01T12:00:00',
|
||||
'2023-11-15T08:00:00.123456'
|
||||
]
|
||||
result = find_newest_datetime_in_list(dates)
|
||||
assert result == '2024-01-01T12:00:00'
|
||||
|
||||
|
||||
class TestParseDayOfWeekRange:
|
||||
"""Test suite for parse_day_of_week_range function"""
|
||||
|
||||
def test_parse_day_of_week_range_single_day(self):
|
||||
"""Test parse_day_of_week_range with single day"""
|
||||
result = parse_day_of_week_range('Mon')
|
||||
assert result == [(1, 'Mon')]
|
||||
|
||||
def test_parse_day_of_week_range_multiple_days(self):
|
||||
"""Test parse_day_of_week_range with multiple days"""
|
||||
result = parse_day_of_week_range('Mon,Wed,Fri')
|
||||
assert len(result) == 3
|
||||
assert (1, 'Mon') in result
|
||||
assert (3, 'Wed') in result
|
||||
assert (5, 'Fri') in result
|
||||
|
||||
def test_parse_day_of_week_range_simple_range(self):
|
||||
"""Test parse_day_of_week_range with simple range"""
|
||||
result = parse_day_of_week_range('Mon-Fri')
|
||||
assert len(result) == 5
|
||||
assert result[0] == (1, 'Mon')
|
||||
assert result[-1] == (5, 'Fri')
|
||||
|
||||
def test_parse_day_of_week_range_weekend_spanning(self):
|
||||
"""Test parse_day_of_week_range with weekend-spanning range"""
|
||||
result = parse_day_of_week_range('Fri-Mon')
|
||||
assert len(result) == 4
|
||||
assert (5, 'Fri') in result
|
||||
assert (6, 'Sat') in result
|
||||
assert (7, 'Sun') in result
|
||||
assert (1, 'Mon') in result
|
||||
|
||||
def test_parse_day_of_week_range_long_names(self):
|
||||
"""Test parse_day_of_week_range with long day names - only works in ranges"""
|
||||
# Long names only work in ranges, not as standalone days
|
||||
# This is a limitation of the current implementation
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_day_of_week_range('Monday,Wednesday')
|
||||
assert 'Invalid day of week entry found' in str(exc_info.value)
|
||||
|
||||
def test_parse_day_of_week_range_mixed_format(self):
|
||||
"""Test parse_day_of_week_range with short names and ranges"""
|
||||
result = parse_day_of_week_range('Mon,Wed-Fri')
|
||||
assert len(result) == 4
|
||||
assert (1, 'Mon') in result
|
||||
assert (3, 'Wed') in result
|
||||
assert (4, 'Thu') in result
|
||||
assert (5, 'Fri') in result
|
||||
|
||||
def test_parse_day_of_week_range_invalid_day(self):
|
||||
"""Test parse_day_of_week_range with invalid day"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_day_of_week_range('InvalidDay')
|
||||
assert 'Invalid day of week entry found' in str(exc_info.value)
|
||||
|
||||
def test_parse_day_of_week_range_duplicate_days(self):
|
||||
"""Test parse_day_of_week_range with duplicate days"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_day_of_week_range('Mon,Mon')
|
||||
assert 'Duplicate day of week entries found' in str(exc_info.value)
|
||||
|
||||
def test_parse_day_of_week_range_whitespace_handling(self):
|
||||
"""Test parse_day_of_week_range with extra whitespace"""
|
||||
result = parse_day_of_week_range(' Mon , Wed , Fri ')
|
||||
assert len(result) == 3
|
||||
assert (1, 'Mon') in result
|
||||
|
||||
|
||||
class TestParseTimeRange:
|
||||
"""Test suite for parse_time_range function"""
|
||||
|
||||
def test_parse_time_range_valid(self):
|
||||
"""Test parse_time_range with valid time range"""
|
||||
start, end = parse_time_range('09:00-17:00')
|
||||
assert start == time(9, 0)
|
||||
assert end == time(17, 0)
|
||||
|
||||
def test_parse_time_range_different_times(self):
|
||||
"""Test parse_time_range with different time values"""
|
||||
start, end = parse_time_range('08:30-12:45')
|
||||
assert start == time(8, 30)
|
||||
assert end == time(12, 45)
|
||||
|
||||
def test_parse_time_range_invalid_block(self):
|
||||
"""Test parse_time_range with invalid block format"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_time_range('09:00')
|
||||
assert 'Invalid time block' in str(exc_info.value)
|
||||
|
||||
def test_parse_time_range_invalid_format(self):
|
||||
"""Test parse_time_range with invalid time format"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_time_range('25:00-26:00')
|
||||
assert 'Invalid time block format' in str(exc_info.value)
|
||||
|
||||
def test_parse_time_range_start_after_end(self):
|
||||
"""Test parse_time_range with start time after end time"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_time_range('17:00-09:00')
|
||||
assert 'start time after end time' in str(exc_info.value)
|
||||
|
||||
def test_parse_time_range_equal_times(self):
|
||||
"""Test parse_time_range with equal start and end times"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_time_range('09:00-09:00')
|
||||
assert 'start time after end time or equal' in str(exc_info.value)
|
||||
|
||||
def test_parse_time_range_custom_format(self):
|
||||
"""Test parse_time_range with custom time format"""
|
||||
start, end = parse_time_range('09:00:00-17:00:00', time_format='%H:%M:%S')
|
||||
assert start == time(9, 0, 0)
|
||||
assert end == time(17, 0, 0)
|
||||
|
||||
def test_parse_time_range_whitespace(self):
|
||||
"""Test parse_time_range with whitespace"""
|
||||
start, end = parse_time_range(' 09:00-17:00 ')
|
||||
assert start == time(9, 0)
|
||||
assert end == time(17, 0)
|
||||
|
||||
|
||||
class TestTimesOverlapOrConnect:
|
||||
"""Test suite for times_overlap_or_connect function"""
|
||||
|
||||
def test_times_overlap_or_connect_clear_overlap(self):
|
||||
"""Test times_overlap_or_connect with clear overlap"""
|
||||
time1 = (time(9, 0), time(12, 0))
|
||||
time2 = (time(10, 0), time(14, 0))
|
||||
assert times_overlap_or_connect(time1, time2) is True
|
||||
|
||||
def test_times_overlap_or_connect_no_overlap(self):
|
||||
"""Test times_overlap_or_connect with no overlap"""
|
||||
time1 = (time(9, 0), time(12, 0))
|
||||
time2 = (time(13, 0), time(17, 0))
|
||||
assert times_overlap_or_connect(time1, time2) is False
|
||||
|
||||
def test_times_overlap_or_connect_touching_not_allowed(self):
|
||||
"""Test times_overlap_or_connect with touching ranges (not allowed)"""
|
||||
time1 = (time(8, 0), time(10, 0))
|
||||
time2 = (time(10, 0), time(12, 0))
|
||||
assert times_overlap_or_connect(time1, time2, allow_touching=False) is True
|
||||
|
||||
def test_times_overlap_or_connect_touching_allowed(self):
|
||||
"""Test times_overlap_or_connect with touching ranges (allowed)"""
|
||||
time1 = (time(8, 0), time(10, 0))
|
||||
time2 = (time(10, 0), time(12, 0))
|
||||
assert times_overlap_or_connect(time1, time2, allow_touching=True) is False
|
||||
|
||||
def test_times_overlap_or_connect_one_contains_other(self):
|
||||
"""Test times_overlap_or_connect when one range contains the other"""
|
||||
time1 = (time(9, 0), time(17, 0))
|
||||
time2 = (time(10, 0), time(12, 0))
|
||||
assert times_overlap_or_connect(time1, time2) is True
|
||||
|
||||
def test_times_overlap_or_connect_same_start(self):
|
||||
"""Test times_overlap_or_connect with same start time"""
|
||||
time1 = (time(9, 0), time(12, 0))
|
||||
time2 = (time(9, 0), time(14, 0))
|
||||
assert times_overlap_or_connect(time1, time2) is True
|
||||
|
||||
def test_times_overlap_or_connect_same_end(self):
|
||||
"""Test times_overlap_or_connect with same end time"""
|
||||
time1 = (time(9, 0), time(12, 0))
|
||||
time2 = (time(10, 0), time(12, 0))
|
||||
assert times_overlap_or_connect(time1, time2) is True
|
||||
|
||||
|
||||
class TestIsTimeInRange:
|
||||
"""Test suite for is_time_in_range function"""
|
||||
|
||||
def test_is_time_in_range_within_range(self):
|
||||
"""Test is_time_in_range with time within range"""
|
||||
assert is_time_in_range('10:00:00', '09:00:00', '17:00:00') is True
|
||||
|
||||
def test_is_time_in_range_at_start(self):
|
||||
"""Test is_time_in_range with time at start of range"""
|
||||
assert is_time_in_range('09:00:00', '09:00:00', '17:00:00') is True
|
||||
|
||||
def test_is_time_in_range_at_end(self):
|
||||
"""Test is_time_in_range with time at end of range"""
|
||||
assert is_time_in_range('17:00:00', '09:00:00', '17:00:00') is True
|
||||
|
||||
def test_is_time_in_range_before_range(self):
|
||||
"""Test is_time_in_range with time before range"""
|
||||
assert is_time_in_range('08:00:00', '09:00:00', '17:00:00') is False
|
||||
|
||||
def test_is_time_in_range_after_range(self):
|
||||
"""Test is_time_in_range with time after range"""
|
||||
assert is_time_in_range('18:00:00', '09:00:00', '17:00:00') is False
|
||||
|
||||
def test_is_time_in_range_crosses_midnight(self):
|
||||
"""Test is_time_in_range with range crossing midnight"""
|
||||
# Range from 22:00 to 06:00
|
||||
assert is_time_in_range('23:00:00', '22:00:00', '06:00:00') is True
|
||||
assert is_time_in_range('03:00:00', '22:00:00', '06:00:00') is True
|
||||
assert is_time_in_range('12:00:00', '22:00:00', '06:00:00') is False
|
||||
|
||||
def test_is_time_in_range_midnight_boundary(self):
|
||||
"""Test is_time_in_range at midnight"""
|
||||
assert is_time_in_range('00:00:00', '22:00:00', '06:00:00') is True
|
||||
|
||||
|
||||
class TestReorderWeekdaysFromToday:
|
||||
"""Test suite for reorder_weekdays_from_today function"""
|
||||
|
||||
def test_reorder_weekdays_from_monday(self):
|
||||
"""Test reorder_weekdays_from_today starting from Monday"""
|
||||
result = reorder_weekdays_from_today('Mon')
|
||||
values = list(result.values())
|
||||
assert values[0] == 'Mon'
|
||||
assert values[-1] == 'Sun'
|
||||
assert len(result) == 7
|
||||
|
||||
def test_reorder_weekdays_from_wednesday(self):
|
||||
"""Test reorder_weekdays_from_today starting from Wednesday"""
|
||||
result = reorder_weekdays_from_today('Wed')
|
||||
values = list(result.values())
|
||||
assert values[0] == 'Wed'
|
||||
assert values[1] == 'Thu'
|
||||
assert values[-1] == 'Tue'
|
||||
|
||||
def test_reorder_weekdays_from_sunday(self):
|
||||
"""Test reorder_weekdays_from_today starting from Sunday"""
|
||||
result = reorder_weekdays_from_today('Sun')
|
||||
values = list(result.values())
|
||||
assert values[0] == 'Sun'
|
||||
assert values[-1] == 'Sat'
|
||||
|
||||
def test_reorder_weekdays_from_long_name(self):
|
||||
"""Test reorder_weekdays_from_today with long day name"""
|
||||
result = reorder_weekdays_from_today('Friday')
|
||||
values = list(result.values())
|
||||
assert values[0] == 'Fri'
|
||||
assert values[-1] == 'Thu'
|
||||
|
||||
def test_reorder_weekdays_invalid_day(self):
|
||||
"""Test reorder_weekdays_from_today with invalid day name"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
reorder_weekdays_from_today('InvalidDay')
|
||||
assert 'Invalid day name provided' in str(exc_info.value)
|
||||
|
||||
def test_reorder_weekdays_preserves_all_days(self):
|
||||
"""Test that reorder_weekdays_from_today preserves all 7 days"""
|
||||
for day in ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']:
|
||||
result = reorder_weekdays_from_today(day)
|
||||
assert len(result) == 7
|
||||
assert set(result.values()) == {'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'}
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Test suite for edge cases and integration scenarios"""
|
||||
|
||||
def test_parse_flexible_date_with_various_iso_formats(self):
|
||||
"""Test parse_flexible_date handles various ISO format variations"""
|
||||
formats = [
|
||||
'2023-12-25',
|
||||
'2023-12-25T15:30:45',
|
||||
'2023-12-25T15:30:45.123456',
|
||||
]
|
||||
for date_str in formats:
|
||||
result = parse_flexible_date(date_str)
|
||||
assert result is not None
|
||||
assert isinstance(result, datetime)
|
||||
|
||||
def test_timezone_consistency_across_functions(self):
|
||||
"""Test timezone handling consistency across functions"""
|
||||
tz_str = 'Asia/Tokyo'
|
||||
tz_obj = parse_timezone_data(tz_str)
|
||||
|
||||
# Both should work with get_datetime_iso8601
|
||||
result1 = get_datetime_iso8601(tz_str)
|
||||
result2 = get_datetime_iso8601(tz_obj)
|
||||
|
||||
assert result1 is not None
|
||||
assert result2 is not None
|
||||
|
||||
def test_date_validation_and_parsing_consistency(self):
|
||||
"""Test that validate_date and parse_flexible_date agree"""
|
||||
valid_dates = ['2023-12-25', '2024/01/01']
|
||||
for date_str in valid_dates:
|
||||
# normalize format for parse_flexible_date
|
||||
normalized = date_str.replace('/', '-')
|
||||
assert validate_date(date_str) is True
|
||||
assert parse_flexible_date(normalized) is not None
|
||||
|
||||
def test_day_of_week_range_complex_scenario(self):
|
||||
"""Test parse_day_of_week_range with complex mixed input"""
|
||||
result = parse_day_of_week_range('Mon,Wed-Fri,Sun')
|
||||
assert len(result) == 5
|
||||
assert (1, 'Mon') in result
|
||||
assert (3, 'Wed') in result
|
||||
assert (4, 'Thu') in result
|
||||
assert (5, 'Fri') in result
|
||||
assert (7, 'Sun') in result
|
||||
|
||||
def test_time_range_boundary_conditions(self):
|
||||
"""Test parse_time_range with boundary times"""
|
||||
start, end = parse_time_range('00:00-23:59')
|
||||
assert start == time(0, 0)
|
||||
assert end == time(23, 59)
|
||||
|
||||
# __END__
|
||||
@@ -1,462 +0,0 @@
|
||||
"""
|
||||
PyTest: datetime_handling/timestamp_convert - seconds_to_string and convert_timestamp functions
|
||||
"""
|
||||
|
||||
from corelibs.datetime_handling.timestamp_convert import seconds_to_string, convert_timestamp
|
||||
|
||||
|
||||
class TestSecondsToString:
|
||||
"""Test suite for seconds_to_string function"""
|
||||
|
||||
def test_basic_integer_seconds(self):
|
||||
"""Test conversion of basic integer seconds"""
|
||||
assert seconds_to_string(0) == "0s"
|
||||
assert seconds_to_string(1) == "1s"
|
||||
assert seconds_to_string(30) == "30s"
|
||||
assert seconds_to_string(59) == "59s"
|
||||
|
||||
def test_minutes_conversion(self):
|
||||
"""Test conversion involving minutes"""
|
||||
assert seconds_to_string(60) == "1m"
|
||||
assert seconds_to_string(90) == "1m 30s"
|
||||
assert seconds_to_string(120) == "2m"
|
||||
assert seconds_to_string(3599) == "59m 59s"
|
||||
|
||||
def test_hours_conversion(self):
|
||||
"""Test conversion involving hours"""
|
||||
assert seconds_to_string(3600) == "1h"
|
||||
assert seconds_to_string(3660) == "1h 1m"
|
||||
assert seconds_to_string(3661) == "1h 1m 1s"
|
||||
assert seconds_to_string(7200) == "2h"
|
||||
assert seconds_to_string(7260) == "2h 1m"
|
||||
|
||||
def test_days_conversion(self):
|
||||
"""Test conversion involving days"""
|
||||
assert seconds_to_string(86400) == "1d"
|
||||
assert seconds_to_string(86401) == "1d 1s"
|
||||
assert seconds_to_string(90000) == "1d 1h"
|
||||
assert seconds_to_string(90061) == "1d 1h 1m 1s"
|
||||
assert seconds_to_string(172800) == "2d"
|
||||
|
||||
def test_complex_combinations(self):
|
||||
"""Test complex time combinations"""
|
||||
# 1 day, 2 hours, 3 minutes, 4 seconds
|
||||
total = 86400 + 7200 + 180 + 4
|
||||
assert seconds_to_string(total) == "1d 2h 3m 4s"
|
||||
|
||||
# 5 days, 23 hours, 59 minutes, 59 seconds
|
||||
total = 5 * 86400 + 23 * 3600 + 59 * 60 + 59
|
||||
assert seconds_to_string(total) == "5d 23h 59m 59s"
|
||||
|
||||
def test_fractional_seconds_default_precision(self):
|
||||
"""Test fractional seconds with default precision (3 decimal places)"""
|
||||
assert seconds_to_string(0.1) == "0.1s"
|
||||
assert seconds_to_string(0.123) == "0.123s"
|
||||
assert seconds_to_string(0.1234) == "0.123s"
|
||||
assert seconds_to_string(1.5) == "1.5s"
|
||||
assert seconds_to_string(1.567) == "1.567s"
|
||||
assert seconds_to_string(1.5678) == "1.568s"
|
||||
|
||||
def test_fractional_seconds_microsecond_precision(self):
|
||||
"""Test fractional seconds with microsecond precision"""
|
||||
assert seconds_to_string(0.1, show_microseconds=True) == "0.1s"
|
||||
assert seconds_to_string(0.123456, show_microseconds=True) == "0.123456s"
|
||||
assert seconds_to_string(0.1234567, show_microseconds=True) == "0.123457s"
|
||||
assert seconds_to_string(1.5, show_microseconds=True) == "1.5s"
|
||||
assert seconds_to_string(1.567890, show_microseconds=True) == "1.56789s"
|
||||
|
||||
def test_fractional_seconds_with_larger_units(self):
|
||||
"""Test fractional seconds combined with larger time units"""
|
||||
# 1 minute and 30.5 seconds
|
||||
assert seconds_to_string(90.5) == "1m 30.5s"
|
||||
assert seconds_to_string(90.5, show_microseconds=True) == "1m 30.5s"
|
||||
|
||||
# 1 hour, 1 minute, and 1.123 seconds
|
||||
total = 3600 + 60 + 1.123
|
||||
assert seconds_to_string(total) == "1h 1m 1.123s"
|
||||
assert seconds_to_string(total, show_microseconds=True) == "1h 1m 1.123s"
|
||||
|
||||
def test_negative_values(self):
|
||||
"""Test negative time values"""
|
||||
assert seconds_to_string(-1) == "-1s"
|
||||
assert seconds_to_string(-60) == "-1m"
|
||||
assert seconds_to_string(-90) == "-1m 30s"
|
||||
assert seconds_to_string(-3661) == "-1h 1m 1s"
|
||||
assert seconds_to_string(-86401) == "-1d 1s"
|
||||
assert seconds_to_string(-1.5) == "-1.5s"
|
||||
assert seconds_to_string(-90.123) == "-1m 30.123s"
|
||||
|
||||
def test_zero_handling(self):
|
||||
"""Test various zero values"""
|
||||
assert seconds_to_string(0) == "0s"
|
||||
assert seconds_to_string(0.0) == "0s"
|
||||
assert seconds_to_string(-0) == "0s"
|
||||
assert seconds_to_string(-0.0) == "0s"
|
||||
|
||||
def test_float_input_types(self):
|
||||
"""Test various float input types"""
|
||||
assert seconds_to_string(1.0) == "1s"
|
||||
assert seconds_to_string(60.0) == "1m"
|
||||
assert seconds_to_string(3600.0) == "1h"
|
||||
assert seconds_to_string(86400.0) == "1d"
|
||||
|
||||
def test_large_values(self):
|
||||
"""Test handling of large time values"""
|
||||
# 365 days (1 year)
|
||||
year_seconds = 365 * 86400
|
||||
assert seconds_to_string(year_seconds) == "365d"
|
||||
|
||||
# 1000 days
|
||||
assert seconds_to_string(1000 * 86400) == "1000d"
|
||||
|
||||
# Large number with all units
|
||||
large_time = 999 * 86400 + 23 * 3600 + 59 * 60 + 59.999
|
||||
result = seconds_to_string(large_time)
|
||||
assert result.startswith("999d")
|
||||
assert "23h" in result
|
||||
assert "59m" in result
|
||||
assert "59.999s" in result
|
||||
|
||||
def test_rounding_behavior(self):
|
||||
"""Test rounding behavior for fractional seconds"""
|
||||
# Default precision (3 decimal places) - values are truncated via rstrip
|
||||
assert seconds_to_string(1.0004) == "1s" # Truncates trailing zeros after rstrip
|
||||
assert seconds_to_string(1.0005) == "1s" # Truncates trailing zeros after rstrip
|
||||
assert seconds_to_string(1.9999) == "2s" # Rounds up and strips .000
|
||||
|
||||
# Microsecond precision (6 decimal places)
|
||||
assert seconds_to_string(1.0000004, show_microseconds=True) == "1s"
|
||||
assert seconds_to_string(1.0000005, show_microseconds=True) == "1.000001s"
|
||||
|
||||
def test_trailing_zero_removal(self):
|
||||
"""Test that trailing zeros are properly removed"""
|
||||
assert seconds_to_string(1.100) == "1.1s"
|
||||
assert seconds_to_string(1.120) == "1.12s"
|
||||
assert seconds_to_string(1.123) == "1.123s"
|
||||
assert seconds_to_string(1.100000, show_microseconds=True) == "1.1s"
|
||||
assert seconds_to_string(1.123000, show_microseconds=True) == "1.123s"
|
||||
|
||||
def test_invalid_input_types(self):
|
||||
"""Test handling of invalid input types"""
|
||||
# String inputs should be returned as-is
|
||||
assert seconds_to_string("invalid") == "invalid"
|
||||
assert seconds_to_string("not a number") == "not a number"
|
||||
assert seconds_to_string("") == ""
|
||||
|
||||
def test_edge_cases_boundary_values(self):
|
||||
"""Test edge cases at unit boundaries"""
|
||||
# Exactly 1 minute - 1 second
|
||||
assert seconds_to_string(59) == "59s"
|
||||
assert seconds_to_string(59.999) == "59.999s"
|
||||
|
||||
# Exactly 1 hour - 1 second
|
||||
assert seconds_to_string(3599) == "59m 59s"
|
||||
assert seconds_to_string(3599.999) == "59m 59.999s"
|
||||
|
||||
# Exactly 1 day - 1 second
|
||||
assert seconds_to_string(86399) == "23h 59m 59s"
|
||||
assert seconds_to_string(86399.999) == "23h 59m 59.999s"
|
||||
|
||||
def test_very_small_fractional_seconds(self):
|
||||
"""Test very small fractional values"""
|
||||
assert seconds_to_string(0.001) == "0.001s"
|
||||
assert seconds_to_string(0.0001) == "0s" # Below default precision
|
||||
assert seconds_to_string(0.000001, show_microseconds=True) == "0.000001s"
|
||||
assert seconds_to_string(0.0000001, show_microseconds=True) == "0s" # Below microsecond precision
|
||||
|
||||
def test_precision_consistency(self):
|
||||
"""Test that precision is consistent across different scenarios"""
|
||||
# With other units present
|
||||
assert seconds_to_string(61.123456) == "1m 1.123s"
|
||||
assert seconds_to_string(61.123456, show_microseconds=True) == "1m 1.123456s"
|
||||
|
||||
# Large values with fractional seconds
|
||||
large_val = 90061.123456 # 1d 1h 1m 1.123456s
|
||||
assert seconds_to_string(large_val) == "1d 1h 1m 1.123s"
|
||||
assert seconds_to_string(large_val, show_microseconds=True) == "1d 1h 1m 1.123456s"
|
||||
|
||||
def test_string_numeric_inputs(self):
|
||||
"""Test string inputs that represent numbers"""
|
||||
# String inputs should be returned as-is, even if they look like numbers
|
||||
assert seconds_to_string("60") == "60"
|
||||
assert seconds_to_string("1.5") == "1.5"
|
||||
assert seconds_to_string("0") == "0"
|
||||
assert seconds_to_string("-60") == "-60"
|
||||
|
||||
|
||||
class TestConvertTimestamp:
|
||||
"""Test suite for convert_timestamp function"""
|
||||
|
||||
def test_basic_integer_seconds(self):
|
||||
"""Test conversion of basic integer seconds"""
|
||||
assert convert_timestamp(0) == "0s 0ms"
|
||||
assert convert_timestamp(1) == "1s 0ms"
|
||||
assert convert_timestamp(30) == "30s 0ms"
|
||||
assert convert_timestamp(59) == "59s 0ms"
|
||||
|
||||
def test_basic_without_microseconds(self):
|
||||
"""Test conversion without showing microseconds"""
|
||||
assert convert_timestamp(0, show_microseconds=False) == "0s"
|
||||
assert convert_timestamp(1, show_microseconds=False) == "1s"
|
||||
assert convert_timestamp(30, show_microseconds=False) == "30s"
|
||||
assert convert_timestamp(59, show_microseconds=False) == "59s"
|
||||
|
||||
def test_minutes_conversion(self):
|
||||
"""Test conversion involving minutes"""
|
||||
assert convert_timestamp(60) == "1m 0s 0ms"
|
||||
assert convert_timestamp(90) == "1m 30s 0ms"
|
||||
assert convert_timestamp(120) == "2m 0s 0ms"
|
||||
assert convert_timestamp(3599) == "59m 59s 0ms"
|
||||
|
||||
def test_minutes_conversion_without_microseconds(self):
|
||||
"""Test conversion involving minutes without microseconds"""
|
||||
assert convert_timestamp(60, show_microseconds=False) == "1m 0s"
|
||||
assert convert_timestamp(90, show_microseconds=False) == "1m 30s"
|
||||
assert convert_timestamp(120, show_microseconds=False) == "2m 0s"
|
||||
|
||||
def test_hours_conversion(self):
|
||||
"""Test conversion involving hours"""
|
||||
assert convert_timestamp(3600) == "1h 0m 0s 0ms"
|
||||
assert convert_timestamp(3660) == "1h 1m 0s 0ms"
|
||||
assert convert_timestamp(3661) == "1h 1m 1s 0ms"
|
||||
assert convert_timestamp(7200) == "2h 0m 0s 0ms"
|
||||
assert convert_timestamp(7260) == "2h 1m 0s 0ms"
|
||||
|
||||
def test_hours_conversion_without_microseconds(self):
|
||||
"""Test conversion involving hours without microseconds"""
|
||||
assert convert_timestamp(3600, show_microseconds=False) == "1h 0m 0s"
|
||||
assert convert_timestamp(3660, show_microseconds=False) == "1h 1m 0s"
|
||||
assert convert_timestamp(3661, show_microseconds=False) == "1h 1m 1s"
|
||||
|
||||
def test_days_conversion(self):
|
||||
"""Test conversion involving days"""
|
||||
assert convert_timestamp(86400) == "1d 0h 0m 0s 0ms"
|
||||
assert convert_timestamp(86401) == "1d 0h 0m 1s 0ms"
|
||||
assert convert_timestamp(90000) == "1d 1h 0m 0s 0ms"
|
||||
assert convert_timestamp(90061) == "1d 1h 1m 1s 0ms"
|
||||
assert convert_timestamp(172800) == "2d 0h 0m 0s 0ms"
|
||||
|
||||
def test_days_conversion_without_microseconds(self):
|
||||
"""Test conversion involving days without microseconds"""
|
||||
assert convert_timestamp(86400, show_microseconds=False) == "1d 0h 0m 0s"
|
||||
assert convert_timestamp(86401, show_microseconds=False) == "1d 0h 0m 1s"
|
||||
assert convert_timestamp(90000, show_microseconds=False) == "1d 1h 0m 0s"
|
||||
|
||||
def test_complex_combinations(self):
|
||||
"""Test complex time combinations"""
|
||||
# 1 day, 2 hours, 3 minutes, 4 seconds
|
||||
total = 86400 + 7200 + 180 + 4
|
||||
assert convert_timestamp(total) == "1d 2h 3m 4s 0ms"
|
||||
|
||||
# 5 days, 23 hours, 59 minutes, 59 seconds
|
||||
total = 5 * 86400 + 23 * 3600 + 59 * 60 + 59
|
||||
assert convert_timestamp(total) == "5d 23h 59m 59s 0ms"
|
||||
|
||||
def test_fractional_seconds_with_microseconds(self):
|
||||
"""Test fractional seconds showing microseconds"""
|
||||
# Note: ms value is the integer of the decimal part string after rounding to 4 places
|
||||
assert convert_timestamp(0.1) == "0s 1ms" # 0.1 → "0.1" → ms=1
|
||||
assert convert_timestamp(0.123) == "0s 123ms" # 0.123 → "0.123" → ms=123
|
||||
assert convert_timestamp(0.1234) == "0s 1234ms" # 0.1234 → "0.1234" → ms=1234
|
||||
assert convert_timestamp(1.5) == "1s 5ms" # 1.5 → "1.5" → ms=5
|
||||
assert convert_timestamp(1.567) == "1s 567ms" # 1.567 → "1.567" → ms=567
|
||||
assert convert_timestamp(1.5678) == "1s 5678ms" # 1.5678 rounds to 1.5678 → ms=5678
|
||||
|
||||
def test_fractional_seconds_rounding(self):
|
||||
"""Test rounding of fractional seconds to 4 decimal places"""
|
||||
# The function rounds to 4 decimal places before splitting
|
||||
assert convert_timestamp(0.12345) == "0s 1235ms" # Rounds to 0.1235
|
||||
assert convert_timestamp(0.123456) == "0s 1235ms" # Rounds to 0.1235
|
||||
assert convert_timestamp(1.99999) == "2s 0ms" # Rounds to 2.0
|
||||
|
||||
def test_fractional_seconds_with_larger_units(self):
|
||||
"""Test fractional seconds combined with larger time units"""
|
||||
# 1 minute and 30.5 seconds
|
||||
assert convert_timestamp(90.5) == "1m 30s 5ms"
|
||||
|
||||
# 1 hour, 1 minute, and 1.123 seconds
|
||||
total = 3600 + 60 + 1.123
|
||||
assert convert_timestamp(total) == "1h 1m 1s 123ms"
|
||||
|
||||
def test_negative_values(self):
|
||||
"""Test negative time values"""
|
||||
assert convert_timestamp(-1) == "-1s 0ms"
|
||||
assert convert_timestamp(-60) == "-1m 0s 0ms"
|
||||
assert convert_timestamp(-90) == "-1m 30s 0ms"
|
||||
assert convert_timestamp(-3661) == "-1h 1m 1s 0ms"
|
||||
assert convert_timestamp(-86401) == "-1d 0h 0m 1s 0ms"
|
||||
assert convert_timestamp(-1.5) == "-1s 5ms"
|
||||
assert convert_timestamp(-90.123) == "-1m 30s 123ms"
|
||||
|
||||
def test_negative_without_microseconds(self):
|
||||
"""Test negative values without microseconds"""
|
||||
assert convert_timestamp(-1, show_microseconds=False) == "-1s"
|
||||
assert convert_timestamp(-60, show_microseconds=False) == "-1m 0s"
|
||||
assert convert_timestamp(-90.123, show_microseconds=False) == "-1m 30s"
|
||||
|
||||
def test_zero_handling(self):
|
||||
"""Test various zero values"""
|
||||
assert convert_timestamp(0) == "0s 0ms"
|
||||
assert convert_timestamp(0.0) == "0s 0ms"
|
||||
assert convert_timestamp(-0) == "0s 0ms"
|
||||
assert convert_timestamp(-0.0) == "0s 0ms"
|
||||
|
||||
def test_zero_filling_behavior(self):
|
||||
"""Test that zeros are filled between set values"""
|
||||
# If we have days and seconds, hours and minutes should be 0
|
||||
assert convert_timestamp(86401) == "1d 0h 0m 1s 0ms"
|
||||
|
||||
# If we have hours and seconds, minutes should be 0
|
||||
assert convert_timestamp(3601) == "1h 0m 1s 0ms"
|
||||
|
||||
# If we have days and hours, minutes and seconds should be 0
|
||||
assert convert_timestamp(90000) == "1d 1h 0m 0s 0ms"
|
||||
|
||||
def test_milliseconds_display(self):
|
||||
"""Test milliseconds are always shown when show_microseconds=True"""
|
||||
# Even with no fractional part, 0ms should be shown
|
||||
assert convert_timestamp(1) == "1s 0ms"
|
||||
assert convert_timestamp(60) == "1m 0s 0ms"
|
||||
assert convert_timestamp(3600) == "1h 0m 0s 0ms"
|
||||
|
||||
# With fractional part, ms should be shown
|
||||
assert convert_timestamp(1.001) == "1s 1ms" # "1.001" → ms=1
|
||||
assert convert_timestamp(1.0001) == "1s 1ms" # "1.0001" → ms=1
|
||||
|
||||
def test_float_input_types(self):
|
||||
"""Test various float input types"""
|
||||
assert convert_timestamp(1.0) == "1s 0ms"
|
||||
assert convert_timestamp(60.0) == "1m 0s 0ms"
|
||||
assert convert_timestamp(3600.0) == "1h 0m 0s 0ms"
|
||||
assert convert_timestamp(86400.0) == "1d 0h 0m 0s 0ms"
|
||||
|
||||
def test_large_values(self):
|
||||
"""Test handling of large time values"""
|
||||
# 365 days (1 year)
|
||||
year_seconds = 365 * 86400
|
||||
assert convert_timestamp(year_seconds) == "365d 0h 0m 0s 0ms"
|
||||
|
||||
# 1000 days
|
||||
assert convert_timestamp(1000 * 86400) == "1000d 0h 0m 0s 0ms"
|
||||
|
||||
# Large number with all units
|
||||
large_time = 999 * 86400 + 23 * 3600 + 59 * 60 + 59.999
|
||||
result = convert_timestamp(large_time)
|
||||
assert result.startswith("999d")
|
||||
assert "23h" in result
|
||||
assert "59m" in result
|
||||
assert "59s" in result
|
||||
assert "999ms" in result # 59.999 rounds to 59.999, ms=999
|
||||
|
||||
def test_invalid_input_types(self):
|
||||
"""Test handling of invalid input types"""
|
||||
# String inputs should be returned as-is
|
||||
assert convert_timestamp("invalid") == "invalid"
|
||||
assert convert_timestamp("not a number") == "not a number"
|
||||
assert convert_timestamp("") == ""
|
||||
|
||||
def test_string_numeric_inputs(self):
|
||||
"""Test string inputs that represent numbers"""
|
||||
# String inputs should be returned as-is, even if they look like numbers
|
||||
assert convert_timestamp("60") == "60"
|
||||
assert convert_timestamp("1.5") == "1.5"
|
||||
assert convert_timestamp("0") == "0"
|
||||
assert convert_timestamp("-60") == "-60"
|
||||
|
||||
def test_edge_cases_boundary_values(self):
|
||||
"""Test edge cases at unit boundaries"""
|
||||
# Exactly 1 minute - 1 second
|
||||
assert convert_timestamp(59) == "59s 0ms"
|
||||
assert convert_timestamp(59.999) == "59s 999ms"
|
||||
|
||||
# Exactly 1 hour - 1 second
|
||||
assert convert_timestamp(3599) == "59m 59s 0ms"
|
||||
assert convert_timestamp(3599.999) == "59m 59s 999ms"
|
||||
|
||||
# Exactly 1 day - 1 second
|
||||
assert convert_timestamp(86399) == "23h 59m 59s 0ms"
|
||||
assert convert_timestamp(86399.999) == "23h 59m 59s 999ms"
|
||||
|
||||
def test_very_small_fractional_seconds(self):
|
||||
"""Test very small fractional values"""
|
||||
assert convert_timestamp(0.001) == "0s 1ms" # 0.001 → "0.001" → ms=1
|
||||
assert convert_timestamp(0.0001) == "0s 1ms" # 0.0001 → "0.0001" → ms=1
|
||||
assert convert_timestamp(0.00005) == "0s 1ms" # 0.00005 rounds to 0.0001 → ms=1
|
||||
assert convert_timestamp(0.00004) == "0s 0ms" # 0.00004 rounds to 0.0 → ms=0
|
||||
|
||||
def test_milliseconds_extraction(self):
|
||||
"""Test that milliseconds are correctly extracted from fractional part"""
|
||||
# The ms value is the integer of the decimal part string, not a conversion
|
||||
# So 0.1 → "0.1" → ms=1, NOT 100ms as you might expect
|
||||
assert convert_timestamp(0.1) == "0s 1ms"
|
||||
# 0.01 seconds → "0.01" → ms=1 (int("01") = 1)
|
||||
assert convert_timestamp(0.01) == "0s 1ms"
|
||||
# 0.001 seconds → "0.001" → ms=1
|
||||
assert convert_timestamp(0.001) == "0s 1ms"
|
||||
# 0.0001 seconds → "0.0001" → ms=1
|
||||
assert convert_timestamp(0.0001) == "0s 1ms"
|
||||
# 0.00004 seconds rounds to "0.0" → ms=0
|
||||
assert convert_timestamp(0.00004) == "0s 0ms"
|
||||
|
||||
def test_comparison_with_seconds_to_string(self):
|
||||
"""Test differences between convert_timestamp and seconds_to_string"""
|
||||
# convert_timestamp fills zeros and adds ms
|
||||
# seconds_to_string omits zeros and no ms
|
||||
assert convert_timestamp(86401) == "1d 0h 0m 1s 0ms"
|
||||
assert seconds_to_string(86401) == "1d 1s"
|
||||
|
||||
assert convert_timestamp(3661) == "1h 1m 1s 0ms"
|
||||
assert seconds_to_string(3661) == "1h 1m 1s"
|
||||
|
||||
# With microseconds disabled, still different due to zero-filling
|
||||
assert convert_timestamp(86401, show_microseconds=False) == "1d 0h 0m 1s"
|
||||
assert seconds_to_string(86401) == "1d 1s"
|
||||
|
||||
def test_precision_consistency(self):
|
||||
"""Test that precision is consistent across different scenarios"""
|
||||
# With other units present
|
||||
assert convert_timestamp(61.123456) == "1m 1s 1235ms" # Rounds to 61.1235
|
||||
|
||||
# Large values with fractional seconds
|
||||
large_val = 90061.123456 # 1d 1h 1m 1.123456s
|
||||
assert convert_timestamp(large_val) == "1d 1h 1m 1s 1235ms" # Rounds to .1235
|
||||
|
||||
def test_microseconds_flag_consistency(self):
|
||||
"""Test that show_microseconds flag works consistently"""
|
||||
test_values = [0, 1, 60, 3600, 86400, 1.5, 90.123, -60]
|
||||
|
||||
for val in test_values:
|
||||
with_ms = convert_timestamp(val, show_microseconds=True)
|
||||
without_ms = convert_timestamp(val, show_microseconds=False)
|
||||
|
||||
# With microseconds should contain 'ms', without should not
|
||||
assert "ms" in with_ms
|
||||
assert "ms" not in without_ms
|
||||
|
||||
# Both should start with same sign if negative
|
||||
if val < 0:
|
||||
assert with_ms.startswith("-")
|
||||
assert without_ms.startswith("-")
|
||||
|
||||
def test_format_consistency(self):
|
||||
"""Test that output format is consistent"""
|
||||
# All outputs should have consistent spacing and unit ordering
|
||||
# Format should be: [d ]h m s[ ms]
|
||||
result = convert_timestamp(93784.5678) # 1d 2h 3m 4.5678s
|
||||
# 93784.5678 rounds to 93784.5678, splits to ["93784", "5678"]
|
||||
assert result == "1d 2h 3m 4s 5678ms"
|
||||
|
||||
# Verify parts are in correct order
|
||||
parts = result.split()
|
||||
# Extract units properly: last 1-2 chars that are letters
|
||||
units: list[str] = []
|
||||
for p in parts:
|
||||
if p.endswith('ms'):
|
||||
units.append('ms')
|
||||
elif p[-1].isalpha():
|
||||
units.append(p[-1])
|
||||
# Should be in order: d, h, m, s, ms
|
||||
expected_order = ['d', 'h', 'm', 's', 'ms']
|
||||
assert units == expected_order
|
||||
|
||||
# __END__
|
||||
@@ -1,194 +0,0 @@
|
||||
"""
|
||||
PyTest: datetime_handling/timestamp_strings
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
from zoneinfo import ZoneInfo
|
||||
import pytest
|
||||
|
||||
# Assuming the class is in a file called timestamp_strings.py
|
||||
from corelibs.datetime_handling.timestamp_strings import TimestampStrings
|
||||
|
||||
|
||||
class TestTimestampStrings:
|
||||
"""Test suite for TimestampStrings class"""
|
||||
|
||||
def test_default_initialization(self):
|
||||
"""Test initialization with default timezone"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.time_zone == 'Asia/Tokyo'
|
||||
assert ts.timestamp_now == mock_now
|
||||
assert ts.today == '2023-12-25'
|
||||
assert ts.timestamp == '2023-12-25 15:30:45'
|
||||
assert ts.timestamp_file == '2023-12-25_153045'
|
||||
|
||||
def test_custom_timezone_initialization(self):
|
||||
"""Test initialization with custom timezone"""
|
||||
custom_tz = 'America/New_York'
|
||||
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings(time_zone=custom_tz)
|
||||
|
||||
assert ts.time_zone == custom_tz
|
||||
assert ts.timestamp_now == mock_now
|
||||
|
||||
def test_invalid_timezone_raises_error(self):
|
||||
"""Test that invalid timezone raises ValueError"""
|
||||
invalid_tz = 'Invalid/Timezone'
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
TimestampStrings(time_zone=invalid_tz)
|
||||
|
||||
assert 'Zone could not be loaded [Invalid/Timezone]' in str(exc_info.value)
|
||||
|
||||
def test_timestamp_formats(self):
|
||||
"""Test various timestamp format outputs"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
# Mock both datetime.now() calls
|
||||
mock_now = datetime(2023, 12, 25, 9, 5, 3)
|
||||
mock_now_tz = datetime(2023, 12, 25, 23, 5, 3, tzinfo=ZoneInfo('Asia/Tokyo'))
|
||||
|
||||
mock_datetime.now.side_effect = [mock_now, mock_now_tz]
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.today == '2023-12-25'
|
||||
assert ts.timestamp == '2023-12-25 09:05:03'
|
||||
assert ts.timestamp_file == '2023-12-25_090503'
|
||||
assert 'JST' in ts.timestamp_tz or 'Asia/Tokyo' in ts.timestamp_tz
|
||||
|
||||
def test_different_timezones_produce_different_results(self):
|
||||
"""Test that different timezones produce different timestamp_tz values"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 12, 0, 0)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
# Create instances with different timezones
|
||||
ts_tokyo = TimestampStrings(time_zone='Asia/Tokyo')
|
||||
ts_ny = TimestampStrings(time_zone='America/New_York')
|
||||
|
||||
# The timezone-aware timestamps should be different
|
||||
assert ts_tokyo.time_zone != ts_ny.time_zone
|
||||
# Note: The actual timestamp_tz values will depend on the mocked datetime
|
||||
|
||||
def test_class_default_timezone(self):
|
||||
"""Test that class default timezone is correctly set"""
|
||||
assert TimestampStrings.TIME_ZONE == 'Asia/Tokyo'
|
||||
|
||||
def test_none_timezone_uses_default(self):
|
||||
"""Test that passing None for timezone uses class default"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings(time_zone=None)
|
||||
|
||||
assert ts.time_zone == 'Asia/Tokyo'
|
||||
|
||||
def test_timestamp_file_format_no_colons(self):
|
||||
"""Test that timestamp_file format doesn't contain colons (safe for filenames)"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ':' not in ts.timestamp_file
|
||||
assert ' ' not in ts.timestamp_file
|
||||
assert ts.timestamp_file == '2023-12-25_153045'
|
||||
|
||||
def test_multiple_instances_independent(self):
|
||||
"""Test that multiple instances don't interfere with each other"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts1 = TimestampStrings(time_zone='Asia/Tokyo')
|
||||
ts2 = TimestampStrings(time_zone='Europe/London')
|
||||
|
||||
assert ts1.time_zone == 'Asia/Tokyo'
|
||||
assert ts2.time_zone == 'Europe/London'
|
||||
assert ts1.time_zone != ts2.time_zone
|
||||
|
||||
def test_zoneinfo_called_correctly_with_string(self):
|
||||
"""Test that ZoneInfo is called with correct timezone when passing string"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.ZoneInfo') as mock_zoneinfo:
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
custom_tz = 'Europe/Paris'
|
||||
ts = TimestampStrings(time_zone=custom_tz)
|
||||
assert ts.time_zone == custom_tz
|
||||
|
||||
mock_zoneinfo.assert_called_with(custom_tz)
|
||||
|
||||
def test_zoneinfo_object_parameter(self):
|
||||
"""Test that ZoneInfo objects can be passed directly as timezone parameter"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_now_tz = datetime(2023, 12, 25, 15, 30, 45, tzinfo=ZoneInfo('Europe/Paris'))
|
||||
mock_datetime.now.side_effect = [mock_now, mock_now_tz]
|
||||
|
||||
# Create a ZoneInfo object
|
||||
custom_tz_obj = ZoneInfo('Europe/Paris')
|
||||
ts = TimestampStrings(time_zone=custom_tz_obj)
|
||||
|
||||
# The time_zone should be the ZoneInfo object itself
|
||||
assert ts.time_zone_zi is custom_tz_obj
|
||||
assert isinstance(ts.time_zone_zi, ZoneInfo)
|
||||
|
||||
def test_zoneinfo_object_vs_string_equivalence(self):
|
||||
"""Test that ZoneInfo object and string produce equivalent results"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_now_tz = datetime(2023, 12, 25, 15, 30, 45, tzinfo=ZoneInfo('Europe/Paris'))
|
||||
mock_datetime.now.side_effect = [mock_now, mock_now_tz, mock_now, mock_now_tz]
|
||||
|
||||
# Test with string
|
||||
ts_string = TimestampStrings(time_zone='Europe/Paris')
|
||||
|
||||
# Test with ZoneInfo object
|
||||
ts_zoneinfo = TimestampStrings(time_zone=ZoneInfo('Europe/Paris'))
|
||||
|
||||
# Both should produce the same timestamp formats (though time_zone attributes will differ)
|
||||
assert ts_string.today == ts_zoneinfo.today
|
||||
assert ts_string.timestamp == ts_zoneinfo.timestamp
|
||||
assert ts_string.timestamp_file == ts_zoneinfo.timestamp_file
|
||||
|
||||
# The time_zone attributes will be different types but represent the same timezone
|
||||
assert str(ts_string.time_zone) == 'Europe/Paris'
|
||||
assert isinstance(ts_zoneinfo.time_zone_zi, ZoneInfo)
|
||||
|
||||
def test_edge_case_midnight(self):
|
||||
"""Test timestamp formatting at midnight"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 0, 0, 0)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.timestamp == '2023-12-25 00:00:00'
|
||||
assert ts.timestamp_file == '2023-12-25_000000'
|
||||
|
||||
def test_edge_case_new_year(self):
|
||||
"""Test timestamp formatting at new year"""
|
||||
with patch('corelibs.datetime_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2024, 1, 1, 0, 0, 0)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.today == '2024-01-01'
|
||||
assert ts.timestamp == '2024-01-01 00:00:00'
|
||||
|
||||
# __END__
|
||||
@@ -183,6 +183,64 @@ class TestSpacerConstants:
|
||||
assert LogParent.SPACER_LENGTH == 32
|
||||
|
||||
|
||||
# MARK: Test ConsoleFormatSettings.from_string
|
||||
class TestConsoleFormatSettingsFromString:
|
||||
"""Test cases for ConsoleFormatSettings.from_string method"""
|
||||
|
||||
def test_from_string_all(self):
|
||||
"""Test from_string with 'ALL' returns correct format"""
|
||||
result = ConsoleFormatSettings.from_string('ALL')
|
||||
assert result == ConsoleFormatSettings.ALL
|
||||
|
||||
def test_from_string_condensed(self):
|
||||
"""Test from_string with 'CONDENSED' returns correct format"""
|
||||
result = ConsoleFormatSettings.from_string('CONDENSED')
|
||||
assert result == ConsoleFormatSettings.CONDENSED
|
||||
|
||||
def test_from_string_minimal(self):
|
||||
"""Test from_string with 'MINIMAL' returns correct format"""
|
||||
result = ConsoleFormatSettings.from_string('MINIMAL')
|
||||
assert result == ConsoleFormatSettings.MINIMAL
|
||||
|
||||
def test_from_string_bare(self):
|
||||
"""Test from_string with 'BARE' returns correct format"""
|
||||
result = ConsoleFormatSettings.from_string('BARE')
|
||||
assert result == ConsoleFormatSettings.BARE
|
||||
|
||||
def test_from_string_invalid_returns_none(self):
|
||||
"""Test from_string with invalid string returns None"""
|
||||
result = ConsoleFormatSettings.from_string('INVALID')
|
||||
assert result is None
|
||||
|
||||
def test_from_string_invalid_with_default(self):
|
||||
"""Test from_string with invalid string returns provided default"""
|
||||
default = ConsoleFormatSettings.ALL
|
||||
result = ConsoleFormatSettings.from_string('INVALID', default=default)
|
||||
assert result == default
|
||||
|
||||
def test_from_string_case_sensitive(self):
|
||||
"""Test from_string is case sensitive"""
|
||||
# Lowercase should not match
|
||||
result = ConsoleFormatSettings.from_string('all')
|
||||
assert result is None
|
||||
|
||||
def test_from_string_with_none_default(self):
|
||||
"""Test from_string with explicit None default"""
|
||||
result = ConsoleFormatSettings.from_string('NONEXISTENT', default=None)
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.parametrize("setting_name,expected", [
|
||||
("ALL", ConsoleFormatSettings.ALL),
|
||||
("CONDENSED", ConsoleFormatSettings.CONDENSED),
|
||||
("MINIMAL", ConsoleFormatSettings.MINIMAL),
|
||||
("BARE", ConsoleFormatSettings.BARE),
|
||||
])
|
||||
def test_from_string_all_valid_settings(self, setting_name: str, expected: Any):
|
||||
"""Test from_string with all valid setting names"""
|
||||
result = ConsoleFormatSettings.from_string(setting_name)
|
||||
assert result == expected
|
||||
|
||||
|
||||
# MARK: Parametrized Tests
|
||||
class TestParametrized:
|
||||
"""Parametrized tests for comprehensive coverage"""
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
var_handling tests
|
||||
"""
|
||||
@@ -1,546 +0,0 @@
|
||||
"""
|
||||
var_handling.enum_base tests
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import pytest
|
||||
from corelibs.var_handling.enum_base import EnumBase
|
||||
|
||||
|
||||
class SampleBlock(EnumBase):
|
||||
"""Sample block enum for testing purposes"""
|
||||
BLOCK_A = "block_a"
|
||||
BLOCK_B = "block_b"
|
||||
HAS_NUM = 5
|
||||
HAS_FLOAT = 3.14
|
||||
LEGACY_KEY = "legacy_value"
|
||||
|
||||
|
||||
class SimpleEnum(EnumBase):
|
||||
"""Simple enum with string values"""
|
||||
OPTION_ONE = "one"
|
||||
OPTION_TWO = "two"
|
||||
OPTION_THREE = "three"
|
||||
|
||||
|
||||
class NumericEnum(EnumBase):
|
||||
"""Enum with only numeric values"""
|
||||
FIRST = 1
|
||||
SECOND = 2
|
||||
THIRD = 3
|
||||
|
||||
|
||||
class TestEnumBaseLookupKey:
|
||||
"""Test cases for lookup_key class method"""
|
||||
|
||||
def test_lookup_key_valid_uppercase(self):
|
||||
"""Test lookup_key with valid uppercase key"""
|
||||
result = SampleBlock.lookup_key("BLOCK_A")
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
assert result.name == "BLOCK_A"
|
||||
assert result.value == "block_a"
|
||||
|
||||
def test_lookup_key_valid_lowercase(self):
|
||||
"""Test lookup_key with valid lowercase key (should convert to uppercase)"""
|
||||
result = SampleBlock.lookup_key("block_a")
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
assert result.name == "BLOCK_A"
|
||||
|
||||
def test_lookup_key_valid_mixed_case(self):
|
||||
"""Test lookup_key with mixed case key"""
|
||||
result = SampleBlock.lookup_key("BlOcK_a")
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
assert result.name == "BLOCK_A"
|
||||
|
||||
def test_lookup_key_with_numeric_enum(self):
|
||||
"""Test lookup_key with numeric enum member"""
|
||||
result = SampleBlock.lookup_key("HAS_NUM")
|
||||
assert result == SampleBlock.HAS_NUM
|
||||
assert result.value == 5
|
||||
|
||||
def test_lookup_key_legacy_colon_replacement(self):
|
||||
"""Test lookup_key with legacy colon format (converts : to ___)"""
|
||||
# This assumes the enum has a key that might be accessed with legacy format
|
||||
# Should convert : to ___ and look up LEGACY___KEY
|
||||
# Since we don't have this key, we test the behavior with a valid conversion
|
||||
# Let's test with a known key that would work
|
||||
with pytest.raises(ValueError, match="Invalid key"):
|
||||
SampleBlock.lookup_key("BLOCK:A") # Should fail as BLOCK___A doesn't exist
|
||||
|
||||
def test_lookup_key_invalid_key(self):
|
||||
"""Test lookup_key with invalid key"""
|
||||
with pytest.raises(ValueError, match="Invalid key: NONEXISTENT"):
|
||||
SampleBlock.lookup_key("NONEXISTENT")
|
||||
|
||||
def test_lookup_key_empty_string(self):
|
||||
"""Test lookup_key with empty string"""
|
||||
with pytest.raises(ValueError, match="Invalid key"):
|
||||
SampleBlock.lookup_key("")
|
||||
|
||||
def test_lookup_key_with_special_characters(self):
|
||||
"""Test lookup_key with special characters that might cause AttributeError"""
|
||||
with pytest.raises(ValueError, match="Invalid key"):
|
||||
SampleBlock.lookup_key("@#$%")
|
||||
|
||||
def test_lookup_key_numeric_string(self):
|
||||
"""Test lookup_key with numeric string that isn't a key"""
|
||||
with pytest.raises(ValueError, match="Invalid key"):
|
||||
SampleBlock.lookup_key("123")
|
||||
|
||||
|
||||
class TestEnumBaseLookupValue:
|
||||
"""Test cases for lookup_value class method"""
|
||||
|
||||
def test_lookup_value_valid_string(self):
|
||||
"""Test lookup_value with valid string value"""
|
||||
result = SampleBlock.lookup_value("block_a")
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
assert result.name == "BLOCK_A"
|
||||
assert result.value == "block_a"
|
||||
|
||||
def test_lookup_value_valid_integer(self):
|
||||
"""Test lookup_value with valid integer value"""
|
||||
result = SampleBlock.lookup_value(5)
|
||||
assert result == SampleBlock.HAS_NUM
|
||||
assert result.name == "HAS_NUM"
|
||||
assert result.value == 5
|
||||
|
||||
def test_lookup_value_valid_float(self):
|
||||
"""Test lookup_value with valid float value"""
|
||||
result = SampleBlock.lookup_value(3.14)
|
||||
assert result == SampleBlock.HAS_FLOAT
|
||||
assert result.name == "HAS_FLOAT"
|
||||
assert result.value == 3.14
|
||||
|
||||
def test_lookup_value_invalid_string(self):
|
||||
"""Test lookup_value with invalid string value"""
|
||||
with pytest.raises(ValueError, match="Invalid value: nonexistent"):
|
||||
SampleBlock.lookup_value("nonexistent")
|
||||
|
||||
def test_lookup_value_invalid_integer(self):
|
||||
"""Test lookup_value with invalid integer value"""
|
||||
with pytest.raises(ValueError, match="Invalid value: 999"):
|
||||
SampleBlock.lookup_value(999)
|
||||
|
||||
def test_lookup_value_case_sensitive(self):
|
||||
"""Test that lookup_value is case-sensitive for string values"""
|
||||
with pytest.raises(ValueError, match="Invalid value"):
|
||||
SampleBlock.lookup_value("BLOCK_A") # Value is "block_a", not "BLOCK_A"
|
||||
|
||||
|
||||
class TestEnumBaseFromAny:
|
||||
"""Test cases for from_any class method"""
|
||||
|
||||
def test_from_any_with_enum_instance(self):
|
||||
"""Test from_any with an enum instance (should return as-is)"""
|
||||
enum_instance = SampleBlock.BLOCK_A
|
||||
result = SampleBlock.from_any(enum_instance)
|
||||
assert result is enum_instance
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
|
||||
def test_from_any_with_string_as_key(self):
|
||||
"""Test from_any with string that matches a key"""
|
||||
result = SampleBlock.from_any("BLOCK_A")
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
assert result.name == "BLOCK_A"
|
||||
assert result.value == "block_a"
|
||||
|
||||
def test_from_any_with_string_as_key_lowercase(self):
|
||||
"""Test from_any with lowercase string key"""
|
||||
result = SampleBlock.from_any("block_a")
|
||||
# Should first try as key (convert to uppercase and find BLOCK_A)
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
|
||||
def test_from_any_with_string_as_value(self):
|
||||
"""Test from_any with string that only matches a value"""
|
||||
# Use a value that isn't also a valid key
|
||||
result = SampleBlock.from_any("block_b")
|
||||
# Should try key first (fail), then value (succeed)
|
||||
assert result == SampleBlock.BLOCK_B
|
||||
assert result.value == "block_b"
|
||||
|
||||
def test_from_any_with_integer(self):
|
||||
"""Test from_any with integer value"""
|
||||
result = SampleBlock.from_any(5)
|
||||
assert result == SampleBlock.HAS_NUM
|
||||
assert result.value == 5
|
||||
|
||||
def test_from_any_with_float(self):
|
||||
"""Test from_any with float value"""
|
||||
result = SampleBlock.from_any(3.14)
|
||||
assert result == SampleBlock.HAS_FLOAT
|
||||
assert result.value == 3.14
|
||||
|
||||
def test_from_any_with_invalid_string(self):
|
||||
"""Test from_any with string that doesn't match key or value"""
|
||||
with pytest.raises(ValueError, match="Could not find as key or value: invalid_string"):
|
||||
SampleBlock.from_any("invalid_string")
|
||||
|
||||
def test_from_any_with_invalid_integer(self):
|
||||
"""Test from_any with integer that doesn't match any value"""
|
||||
with pytest.raises(ValueError, match="Invalid value: 999"):
|
||||
SampleBlock.from_any(999)
|
||||
|
||||
def test_from_any_string_key_priority(self):
|
||||
"""Test that from_any tries key lookup before value for strings"""
|
||||
# Create an enum where a value matches another key
|
||||
class AmbiguousEnum(EnumBase):
|
||||
KEY_A = "key_b" # Value is the name of another key
|
||||
KEY_B = "value_b"
|
||||
|
||||
# When we look up "KEY_B", it should find it as a key, not as value "key_b"
|
||||
result = AmbiguousEnum.from_any("KEY_B")
|
||||
assert result == AmbiguousEnum.KEY_B
|
||||
assert result.value == "value_b"
|
||||
|
||||
|
||||
class TestEnumBaseToValue:
|
||||
"""Test cases for to_value instance method"""
|
||||
|
||||
def test_to_value_string_value(self):
|
||||
"""Test to_value with string enum value"""
|
||||
result = SampleBlock.BLOCK_A.to_value()
|
||||
assert result == "block_a"
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_to_value_integer_value(self):
|
||||
"""Test to_value with integer enum value"""
|
||||
result = SampleBlock.HAS_NUM.to_value()
|
||||
assert result == 5
|
||||
assert isinstance(result, int)
|
||||
|
||||
def test_to_value_float_value(self):
|
||||
"""Test to_value with float enum value"""
|
||||
result = SampleBlock.HAS_FLOAT.to_value()
|
||||
assert result == 3.14
|
||||
assert isinstance(result, float)
|
||||
|
||||
def test_to_value_equals_value_attribute(self):
|
||||
"""Test that to_value returns the same as .value"""
|
||||
enum_instance = SampleBlock.BLOCK_A
|
||||
assert enum_instance.to_value() == enum_instance.value
|
||||
|
||||
|
||||
class TestEnumBaseToLowerCase:
|
||||
"""Test cases for to_lower_case instance method"""
|
||||
|
||||
def test_to_lower_case_uppercase_name(self):
|
||||
"""Test to_lower_case with uppercase enum name"""
|
||||
result = SampleBlock.BLOCK_A.to_lower_case()
|
||||
assert result == "block_a"
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_to_lower_case_mixed_name(self):
|
||||
"""Test to_lower_case with name containing underscores"""
|
||||
result = SampleBlock.HAS_NUM.to_lower_case()
|
||||
assert result == "has_num"
|
||||
|
||||
def test_to_lower_case_consistency(self):
|
||||
"""Test that to_lower_case always returns lowercase"""
|
||||
for member in SampleBlock:
|
||||
result = member.to_lower_case()
|
||||
assert result == result.lower()
|
||||
assert result == member.name.lower()
|
||||
|
||||
|
||||
class TestEnumBaseStrMethod:
|
||||
"""Test cases for __str__ magic method"""
|
||||
|
||||
def test_str_returns_name(self):
|
||||
"""Test that str() returns the enum name"""
|
||||
result = str(SampleBlock.BLOCK_A)
|
||||
assert result == "BLOCK_A"
|
||||
assert result == SampleBlock.BLOCK_A.name
|
||||
|
||||
def test_str_all_members(self):
|
||||
"""Test str() for all enum members"""
|
||||
for member in SampleBlock:
|
||||
result = str(member)
|
||||
assert result == member.name
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_str_in_formatting(self):
|
||||
"""Test that str works in string formatting"""
|
||||
formatted = f"Enum: {SampleBlock.BLOCK_A}"
|
||||
assert formatted == "Enum: BLOCK_A"
|
||||
|
||||
def test_str_vs_repr(self):
|
||||
"""Test difference between str and repr"""
|
||||
enum_instance = SampleBlock.BLOCK_A
|
||||
str_result = str(enum_instance)
|
||||
repr_result = repr(enum_instance)
|
||||
|
||||
assert str_result == "BLOCK_A"
|
||||
# repr should include class name
|
||||
assert "SampleBlock" in repr_result
|
||||
|
||||
|
||||
# Parametrized tests for comprehensive coverage
|
||||
class TestParametrized:
|
||||
"""Parametrized tests for better coverage"""
|
||||
|
||||
@pytest.mark.parametrize("key,expected_member", [
|
||||
("BLOCK_A", SampleBlock.BLOCK_A),
|
||||
("block_a", SampleBlock.BLOCK_A),
|
||||
("BLOCK_B", SampleBlock.BLOCK_B),
|
||||
("HAS_NUM", SampleBlock.HAS_NUM),
|
||||
("has_num", SampleBlock.HAS_NUM),
|
||||
("HAS_FLOAT", SampleBlock.HAS_FLOAT),
|
||||
])
|
||||
def test_lookup_key_parametrized(self, key: str, expected_member: EnumBase):
|
||||
"""Test lookup_key with various valid keys"""
|
||||
result = SampleBlock.lookup_key(key)
|
||||
assert result == expected_member
|
||||
|
||||
@pytest.mark.parametrize("value,expected_member", [
|
||||
("block_a", SampleBlock.BLOCK_A),
|
||||
("block_b", SampleBlock.BLOCK_B),
|
||||
(5, SampleBlock.HAS_NUM),
|
||||
(3.14, SampleBlock.HAS_FLOAT),
|
||||
("legacy_value", SampleBlock.LEGACY_KEY),
|
||||
])
|
||||
def test_lookup_value_parametrized(self, value: Any, expected_member: EnumBase):
|
||||
"""Test lookup_value with various valid values"""
|
||||
result = SampleBlock.lookup_value(value)
|
||||
assert result == expected_member
|
||||
|
||||
@pytest.mark.parametrize("input_any,expected_member", [
|
||||
("BLOCK_A", SampleBlock.BLOCK_A),
|
||||
("block_a", SampleBlock.BLOCK_A),
|
||||
("block_b", SampleBlock.BLOCK_B),
|
||||
(5, SampleBlock.HAS_NUM),
|
||||
(3.14, SampleBlock.HAS_FLOAT),
|
||||
(SampleBlock.BLOCK_A, SampleBlock.BLOCK_A), # Pass enum instance
|
||||
])
|
||||
def test_from_any_parametrized(self, input_any: Any, expected_member: EnumBase):
|
||||
"""Test from_any with various valid inputs"""
|
||||
result = SampleBlock.from_any(input_any)
|
||||
assert result == expected_member
|
||||
|
||||
@pytest.mark.parametrize("invalid_key", [
|
||||
"NONEXISTENT",
|
||||
"invalid",
|
||||
"123",
|
||||
"",
|
||||
"BLOCK_C",
|
||||
])
|
||||
def test_lookup_key_invalid_parametrized(self, invalid_key: str):
|
||||
"""Test lookup_key with various invalid keys"""
|
||||
with pytest.raises(ValueError, match="Invalid key"):
|
||||
SampleBlock.lookup_key(invalid_key)
|
||||
|
||||
@pytest.mark.parametrize("invalid_value", [
|
||||
"nonexistent",
|
||||
999,
|
||||
-1,
|
||||
0.0,
|
||||
"BLOCK_A", # This is a key name, not a value
|
||||
])
|
||||
def test_lookup_value_invalid_parametrized(self, invalid_value: Any):
|
||||
"""Test lookup_value with various invalid values"""
|
||||
with pytest.raises(ValueError, match="Invalid value"):
|
||||
SampleBlock.lookup_value(invalid_value)
|
||||
|
||||
|
||||
# Edge cases and special scenarios
|
||||
class TestEdgeCases:
|
||||
"""Test edge cases and special scenarios"""
|
||||
|
||||
def test_enum_with_single_member(self):
|
||||
"""Test EnumBase with only one member"""
|
||||
class SingleEnum(EnumBase):
|
||||
ONLY_ONE = "single"
|
||||
|
||||
result = SingleEnum.from_any("ONLY_ONE")
|
||||
assert result == SingleEnum.ONLY_ONE
|
||||
assert result.to_value() == "single"
|
||||
|
||||
def test_enum_iteration(self):
|
||||
"""Test iterating over enum members"""
|
||||
members = list(SampleBlock)
|
||||
assert len(members) == 5
|
||||
assert SampleBlock.BLOCK_A in members
|
||||
assert SampleBlock.BLOCK_B in members
|
||||
assert SampleBlock.HAS_NUM in members
|
||||
|
||||
def test_enum_membership(self):
|
||||
"""Test checking membership in enum"""
|
||||
assert SampleBlock.BLOCK_A in SampleBlock
|
||||
assert SampleBlock.HAS_NUM in SampleBlock
|
||||
|
||||
def test_enum_comparison(self):
|
||||
"""Test comparing enum members"""
|
||||
assert SampleBlock.BLOCK_A == SampleBlock.BLOCK_A
|
||||
assert SampleBlock.BLOCK_A != SampleBlock.BLOCK_B
|
||||
assert SampleBlock.from_any("BLOCK_A") == SampleBlock.BLOCK_A
|
||||
|
||||
def test_enum_identity(self):
|
||||
"""Test enum member identity"""
|
||||
member1 = SampleBlock.BLOCK_A
|
||||
member2 = SampleBlock.lookup_key("BLOCK_A")
|
||||
member3 = SampleBlock.from_any("BLOCK_A")
|
||||
|
||||
assert member1 is member2
|
||||
assert member1 is member3
|
||||
assert member2 is member3
|
||||
|
||||
def test_different_enum_classes(self):
|
||||
"""Test that different enum classes are distinct"""
|
||||
# Even if they have same keys/values, they're different
|
||||
class OtherEnum(EnumBase):
|
||||
BLOCK_A = "block_a"
|
||||
|
||||
result1 = SampleBlock.from_any("BLOCK_A")
|
||||
result2 = OtherEnum.from_any("BLOCK_A")
|
||||
|
||||
assert result1 != result2
|
||||
assert not isinstance(result1, type(result2))
|
||||
|
||||
def test_numeric_enum_operations(self):
|
||||
"""Test operations specific to numeric enums"""
|
||||
assert NumericEnum.FIRST.to_value() == 1
|
||||
assert NumericEnum.SECOND.to_value() == 2
|
||||
assert NumericEnum.THIRD.to_value() == 3
|
||||
|
||||
# Test from_any with integers
|
||||
assert NumericEnum.from_any(1) == NumericEnum.FIRST
|
||||
assert NumericEnum.from_any(2) == NumericEnum.SECOND
|
||||
|
||||
def test_mixed_value_types_in_same_enum(self):
|
||||
"""Test enum with mixed value types"""
|
||||
# SampleBlock already has mixed types (strings, int, float)
|
||||
assert isinstance(SampleBlock.BLOCK_A.to_value(), str)
|
||||
assert isinstance(SampleBlock.HAS_NUM.to_value(), int)
|
||||
assert isinstance(SampleBlock.HAS_FLOAT.to_value(), float)
|
||||
|
||||
def test_from_any_chained_calls(self):
|
||||
"""Test that from_any can be chained (idempotent)"""
|
||||
result1 = SampleBlock.from_any("BLOCK_A")
|
||||
result2 = SampleBlock.from_any(result1)
|
||||
result3 = SampleBlock.from_any(result2)
|
||||
|
||||
assert result1 == result2 == result3
|
||||
assert result1 is result2 is result3
|
||||
|
||||
|
||||
# Integration tests
|
||||
class TestIntegration:
|
||||
"""Integration tests combining multiple methods"""
|
||||
|
||||
def test_round_trip_key_lookup(self):
|
||||
"""Test round-trip from key to enum and back"""
|
||||
original_key = "BLOCK_A"
|
||||
enum_member = SampleBlock.lookup_key(original_key)
|
||||
result_name = str(enum_member)
|
||||
|
||||
assert result_name == original_key
|
||||
|
||||
def test_round_trip_value_lookup(self):
|
||||
"""Test round-trip from value to enum and back"""
|
||||
original_value = "block_a"
|
||||
enum_member = SampleBlock.lookup_value(original_value)
|
||||
result_value = enum_member.to_value()
|
||||
|
||||
assert result_value == original_value
|
||||
|
||||
def test_from_any_workflow(self):
|
||||
"""Test realistic workflow using from_any"""
|
||||
# Simulate receiving various types of input
|
||||
inputs = [
|
||||
"BLOCK_A", # Key as string
|
||||
"block_b", # Value as string
|
||||
5, # Numeric value
|
||||
SampleBlock.HAS_FLOAT, # Already an enum
|
||||
]
|
||||
|
||||
expected = [
|
||||
SampleBlock.BLOCK_A,
|
||||
SampleBlock.BLOCK_B,
|
||||
SampleBlock.HAS_NUM,
|
||||
SampleBlock.HAS_FLOAT,
|
||||
]
|
||||
|
||||
for input_val, expected_val in zip(inputs, expected):
|
||||
result = SampleBlock.from_any(input_val)
|
||||
assert result == expected_val
|
||||
|
||||
def test_enum_in_dictionary(self):
|
||||
"""Test using enum as dictionary key"""
|
||||
enum_dict = {
|
||||
SampleBlock.BLOCK_A: "Value A",
|
||||
SampleBlock.BLOCK_B: "Value B",
|
||||
SampleBlock.HAS_NUM: "Value Num",
|
||||
}
|
||||
|
||||
assert enum_dict[SampleBlock.BLOCK_A] == "Value A"
|
||||
block_b = SampleBlock.from_any("BLOCK_B")
|
||||
assert isinstance(block_b, SampleBlock)
|
||||
assert enum_dict[block_b] == "Value B"
|
||||
|
||||
def test_enum_in_set(self):
|
||||
"""Test using enum in a set"""
|
||||
enum_set = {SampleBlock.BLOCK_A, SampleBlock.BLOCK_B, SampleBlock.BLOCK_A}
|
||||
|
||||
assert len(enum_set) == 2 # BLOCK_A should be deduplicated
|
||||
assert SampleBlock.BLOCK_A in enum_set
|
||||
assert SampleBlock.from_any("BLOCK_B") in enum_set
|
||||
|
||||
|
||||
# Real-world usage scenarios
|
||||
class TestRealWorldScenarios:
|
||||
"""Test real-world usage scenarios from enum_test.py"""
|
||||
|
||||
def test_original_enum_test_scenario(self):
|
||||
"""Test the scenario from the original enum_test.py"""
|
||||
# BLOCK A: {SampleBlock.from_any('BLOCK_A')}
|
||||
result_a = SampleBlock.from_any('BLOCK_A')
|
||||
assert result_a == SampleBlock.BLOCK_A
|
||||
assert str(result_a) == "BLOCK_A"
|
||||
|
||||
# HAS NUM: {SampleBlock.from_any(5)}
|
||||
result_num = SampleBlock.from_any(5)
|
||||
assert result_num == SampleBlock.HAS_NUM
|
||||
assert result_num.to_value() == 5
|
||||
|
||||
# DIRECT BLOCK: {SampleBlock.BLOCK_A.name} -> {SampleBlock.BLOCK_A.value}
|
||||
assert SampleBlock.BLOCK_A.name == "BLOCK_A"
|
||||
assert SampleBlock.BLOCK_A.value == "block_a"
|
||||
|
||||
def test_config_value_parsing(self):
|
||||
"""Test parsing values from configuration (common use case)"""
|
||||
# Simulate config values that might come as strings
|
||||
config_values = ["OPTION_ONE", "option_two", "OPTION_THREE"]
|
||||
|
||||
results = [SimpleEnum.from_any(val) for val in config_values]
|
||||
|
||||
assert results[0] == SimpleEnum.OPTION_ONE
|
||||
assert results[1] == SimpleEnum.OPTION_TWO
|
||||
assert results[2] == SimpleEnum.OPTION_THREE
|
||||
|
||||
def test_api_response_mapping(self):
|
||||
"""Test mapping API response values to enum"""
|
||||
# Simulate API returning numeric codes
|
||||
api_codes = [1, 2, 3]
|
||||
|
||||
results = [NumericEnum.from_any(code) for code in api_codes]
|
||||
|
||||
assert results[0] == NumericEnum.FIRST
|
||||
assert results[1] == NumericEnum.SECOND
|
||||
assert results[2] == NumericEnum.THIRD
|
||||
|
||||
def test_validation_with_error_handling(self):
|
||||
"""Test validation with proper error handling"""
|
||||
valid_input = "BLOCK_A"
|
||||
invalid_input = "INVALID"
|
||||
|
||||
# Valid input should work
|
||||
result = SampleBlock.from_any(valid_input)
|
||||
assert result == SampleBlock.BLOCK_A
|
||||
|
||||
# Invalid input should raise ValueError
|
||||
try:
|
||||
SampleBlock.from_any(invalid_input)
|
||||
assert False, "Should have raised ValueError"
|
||||
except ValueError as e:
|
||||
assert "Could not find as key or value" in str(e)
|
||||
assert "INVALID" in str(e)
|
||||
@@ -1,241 +0,0 @@
|
||||
"""
|
||||
var helpers
|
||||
"""
|
||||
|
||||
# ADDED 2025/7/11 Replace 'your_module' with actual module name
|
||||
|
||||
from typing import Any
|
||||
import pytest
|
||||
from corelibs.var_handling.var_helpers import is_int, is_float, str_to_bool
|
||||
|
||||
|
||||
class TestIsInt:
|
||||
"""Test cases for is_int function"""
|
||||
|
||||
def test_valid_integers(self):
|
||||
"""Test with valid integer strings"""
|
||||
assert is_int("123") is True
|
||||
assert is_int("0") is True
|
||||
assert is_int("-456") is True
|
||||
assert is_int("+789") is True
|
||||
assert is_int("000") is True
|
||||
|
||||
def test_invalid_integers(self):
|
||||
"""Test with invalid integer strings"""
|
||||
assert is_int("12.34") is False
|
||||
assert is_int("abc") is False
|
||||
assert is_int("12a") is False
|
||||
assert is_int("") is False
|
||||
assert is_int(" ") is False
|
||||
assert is_int("12.0") is False
|
||||
assert is_int("1e5") is False
|
||||
|
||||
def test_numeric_types(self):
|
||||
"""Test with actual numeric types"""
|
||||
assert is_int(123) is True
|
||||
assert is_int(0) is True
|
||||
assert is_int(-456) is True
|
||||
assert is_int(12.34) is True # float can be converted to int
|
||||
assert is_int(12.0) is True
|
||||
|
||||
def test_other_types(self):
|
||||
"""Test with other data types"""
|
||||
assert is_int(None) is False
|
||||
assert is_int([]) is False
|
||||
assert is_int({}) is False
|
||||
assert is_int(True) is True # bool is subclass of int
|
||||
assert is_int(False) is True
|
||||
|
||||
|
||||
class TestIsFloat:
|
||||
"""Test cases for is_float function"""
|
||||
|
||||
def test_valid_floats(self):
|
||||
"""Test with valid float strings"""
|
||||
assert is_float("12.34") is True
|
||||
assert is_float("0.0") is True
|
||||
assert is_float("-45.67") is True
|
||||
assert is_float("+78.9") is True
|
||||
assert is_float("123") is True # integers are valid floats
|
||||
assert is_float("0") is True
|
||||
assert is_float("1e5") is True
|
||||
assert is_float("1.5e-10") is True
|
||||
assert is_float("inf") is True
|
||||
assert is_float("-inf") is True
|
||||
assert is_float("nan") is True
|
||||
|
||||
def test_invalid_floats(self):
|
||||
"""Test with invalid float strings"""
|
||||
assert is_float("abc") is False
|
||||
assert is_float("12.34.56") is False
|
||||
assert is_float("12a") is False
|
||||
assert is_float("") is False
|
||||
assert is_float(" ") is False
|
||||
assert is_float("12..34") is False
|
||||
|
||||
def test_numeric_types(self):
|
||||
"""Test with actual numeric types"""
|
||||
assert is_float(123) is True
|
||||
assert is_float(12.34) is True
|
||||
assert is_float(0) is True
|
||||
assert is_float(-45.67) is True
|
||||
|
||||
def test_other_types(self):
|
||||
"""Test with other data types"""
|
||||
assert is_float(None) is False
|
||||
assert is_float([]) is False
|
||||
assert is_float({}) is False
|
||||
assert is_float(True) is True # bool can be converted to float
|
||||
assert is_float(False) is True
|
||||
|
||||
|
||||
class TestStrToBool:
|
||||
"""Test cases for str_to_bool function"""
|
||||
|
||||
def test_valid_true_strings(self):
|
||||
"""Test with valid true strings"""
|
||||
assert str_to_bool("True") is True
|
||||
assert str_to_bool("true") is True
|
||||
|
||||
def test_valid_false_strings(self):
|
||||
"""Test with valid false strings"""
|
||||
assert str_to_bool("False") is False
|
||||
assert str_to_bool("false") is False
|
||||
|
||||
def test_invalid_strings(self):
|
||||
"""Test with invalid boolean strings"""
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("TRUE")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("FALSE")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("yes")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("no")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("1")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("0")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool(" True")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid boolean string"):
|
||||
str_to_bool("True ")
|
||||
|
||||
def test_error_message_content(self):
|
||||
"""Test that error messages contain the invalid input"""
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
str_to_bool("invalid")
|
||||
assert "Invalid boolean string: invalid" in str(exc_info.value)
|
||||
|
||||
def test_case_sensitivity(self):
|
||||
"""Test that function is case sensitive"""
|
||||
with pytest.raises(ValueError):
|
||||
str_to_bool("TRUE")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
str_to_bool("True ") # with space
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
str_to_bool(" True") # with space
|
||||
|
||||
|
||||
# Additional edge case tests
|
||||
class TestEdgeCases:
|
||||
"""Test edge cases and special scenarios"""
|
||||
|
||||
def test_is_int_with_whitespace(self):
|
||||
"""Test is_int with whitespace (should work due to int() behavior)"""
|
||||
assert is_int(" 123 ") is True
|
||||
assert is_int("\t456\n") is True
|
||||
|
||||
def test_is_float_with_whitespace(self):
|
||||
"""Test is_float with whitespace (should work due to float() behavior)"""
|
||||
assert is_float(" 12.34 ") is True
|
||||
assert is_float("\t45.67\n") is True
|
||||
|
||||
def test_large_numbers(self):
|
||||
"""Test with very large numbers"""
|
||||
large_int = "123456789012345678901234567890"
|
||||
assert is_int(large_int) is True
|
||||
assert is_float(large_int) is True
|
||||
|
||||
def test_scientific_notation(self):
|
||||
"""Test scientific notation"""
|
||||
assert is_int("1e5") is False # int() doesn't handle scientific notation
|
||||
assert is_float("1e5") is True
|
||||
assert is_float("1.5e-10") is True
|
||||
assert is_float("2E+3") is True
|
||||
|
||||
|
||||
# Parametrized tests for more comprehensive coverage
|
||||
class TestParametrized:
|
||||
"""Parametrized tests for better coverage"""
|
||||
|
||||
@pytest.mark.parametrize("value,expected", [
|
||||
("123", True),
|
||||
("0", True),
|
||||
("-456", True),
|
||||
("12.34", False),
|
||||
("abc", False),
|
||||
("", False),
|
||||
(123, True),
|
||||
(12.5, True),
|
||||
(None, False),
|
||||
])
|
||||
def test_is_int_parametrized(self, value: Any, expected: bool):
|
||||
"""Test"""
|
||||
assert is_int(value) == expected
|
||||
|
||||
@pytest.mark.parametrize("value,expected", [
|
||||
("12.34", True),
|
||||
("123", True),
|
||||
("0", True),
|
||||
("-45.67", True),
|
||||
("inf", True),
|
||||
("nan", True),
|
||||
("abc", False),
|
||||
("", False),
|
||||
(12.34, True),
|
||||
(123, True),
|
||||
(None, False),
|
||||
])
|
||||
def test_is_float_parametrized(self, value: Any, expected: bool):
|
||||
"""test"""
|
||||
assert is_float(value) == expected
|
||||
|
||||
@pytest.mark.parametrize("value,expected", [
|
||||
("True", True),
|
||||
("true", True),
|
||||
("False", False),
|
||||
("false", False),
|
||||
])
|
||||
def test_str_to_bool_valid_parametrized(self, value: Any, expected: bool):
|
||||
"""test"""
|
||||
assert str_to_bool(value) == expected
|
||||
|
||||
@pytest.mark.parametrize("invalid_value", [
|
||||
"TRUE",
|
||||
"FALSE",
|
||||
"yes",
|
||||
"no",
|
||||
"1",
|
||||
"0",
|
||||
"",
|
||||
" True",
|
||||
"True ",
|
||||
"invalid",
|
||||
])
|
||||
def test_str_to_bool_invalid_parametrized(self, invalid_value: Any):
|
||||
"""test"""
|
||||
with pytest.raises(ValueError):
|
||||
str_to_bool(invalid_value)
|
||||
Reference in New Issue
Block a user