core/tests/test_rate_conversion.py
Shay 89321e617d feat(rate): R3.2 exact-rational time-unit conversion primitive (R3.2a)
generate/rate_comprehension/conversion.py: convert_time(value, from_unit, to_unit) -> Fraction for known time units (minute<->hour), exact rational (fractions.Fraction), NO floats. is_convertible gates it. 30 minute -> Fraction(1,2) hour; non-time/non-convertible (minute<->mile, dollar<->hour) raises ConversionError so the caller still refuses rate_unit_mismatch. Tiny: only minute<->hour in v1 (length/currency/compound deferred). 6 tests incl. never-a-float guard.
2026-06-08 05:30:57 -07:00

42 lines
1.4 KiB
Python

"""Tests for the R3.2 exact-rational time-unit conversion primitive."""
from __future__ import annotations
from fractions import Fraction
import pytest
from generate.rate_comprehension.conversion import ConversionError, convert_time, is_convertible
def test_minutes_to_hours_is_exact_rational() -> None:
assert convert_time(30, "minute", "hour") == Fraction(1, 2)
assert convert_time(90, "minute", "hour") == Fraction(3, 2)
assert convert_time(60, "minute", "hour") == Fraction(1) # exact whole
def test_hours_to_minutes() -> None:
assert convert_time(2, "hour", "minute") == Fraction(120)
def test_identity_conversion() -> None:
assert convert_time(5, "hour", "hour") == Fraction(5)
def test_non_time_units_refuse() -> None:
with pytest.raises(ConversionError):
convert_time(30, "minute", "mile")
with pytest.raises(ConversionError):
convert_time(5, "dollar", "hour") # currency deferred
def test_is_convertible() -> None:
assert is_convertible("minute", "hour") and is_convertible("hour", "minute")
assert not is_convertible("minute", "mile")
assert not is_convertible("kilometer", "mile") # length deferred
def test_result_is_never_a_float() -> None:
result = convert_time(45, "minute", "hour")
assert isinstance(result, Fraction) and not isinstance(result, float)
assert result == Fraction(3, 4)