From cf4b80b24a2014a76a5df842bb841d9c6a74376c Mon Sep 17 00:00:00 2001 From: Deepanshu Date: Sat, 5 Sep 2026 07:36:18 -0400 Subject: [PATCH] test: cover every TagRateLimit* validator and normalizer Codecov flagged 57% patch coverage on the new config types: only 3 of TagRateLimitEntry's validators had tests, and TagRateLimitScope, TagRateLimitGroup, TagRateLimits, and ModelInfo.tag_rate_limits had none at all. Added a test for each remaining validator/normalizer (NaN limit, non-positive period/ttl/cache-size, empty allowlists, sort+dedup normalization, frozen scope) and for constructing the group/aggregate types. Spot-checked kill power on the NaN check, the sort+dedup normalizer, and the frozen guard by reverting each one and confirming its test fails. --- tests/test_litellm/types/test_router.py | 100 +++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/tests/test_litellm/types/test_router.py b/tests/test_litellm/types/test_router.py index 3427fbd2f89..cec79ebf9d1 100644 --- a/tests/test_litellm/types/test_router.py +++ b/tests/test_litellm/types/test_router.py @@ -10,6 +10,9 @@ from litellm.types.router import ( LiteLLM_Params, ModelInfo, TagRateLimitEntry, + TagRateLimitGroup, + TagRateLimits, + TagRateLimitScope, ) from litellm.types.utils import CustomPricingLiteLLMParams, MirroredPricingParams @@ -92,7 +95,7 @@ def test_pricing_strings_are_coerced_to_float(): def test_invalid_pricing_is_rejected(): - with pytest.raises(ValueError, match='validation error for ModelInfo'): + with pytest.raises(ValueError, match="validation error for ModelInfo"): ModelInfo(id="x", input_cost_per_token="free") @@ -191,3 +194,98 @@ def test_key_ttl_seconds_shorter_than_period_rejected_without_internal_enforceme key_ttl_seconds=60, ) _assert_message_has_no_internal_jargon(excinfo) + + +def test_limit_nan_rejected(): + """NaN compares False against every ordering operator, so it would otherwise slip + past a `limit <= 0` style guard and silently defeat whichever check gates it.""" + with pytest.raises(ValueError, match="limit must not be NaN"): + TagRateLimitEntry(name="daily", limit=float("nan"), period_seconds=60) + + +def test_period_seconds_non_positive_rejected(): + with pytest.raises(ValueError, match="period_seconds must be a positive integer"): + TagRateLimitEntry(name="daily", limit=10, period_seconds=0) + + +def test_key_ttl_seconds_non_positive_rejected(): + with pytest.raises(ValueError, match="key_ttl_seconds must be a positive integer when set"): + TagRateLimitEntry(name="daily", limit=10, period_seconds=60, key_ttl_seconds=0) + + +def test_max_in_memory_cache_size_non_positive_rejected(): + with pytest.raises(ValueError, match="max_in_memory_cache_size must be a positive integer"): + TagRateLimitEntry(name="daily", limit=10, period_seconds=60, max_in_memory_cache_size=0) + + +def test_apply_to_key_alias_empty_list_rejected(): + with pytest.raises(ValueError, match="apply_to_key_alias must be a non-empty list"): + TagRateLimitEntry(name="daily", limit=10, period_seconds=60, apply_to_key_alias=()) + + +def test_apply_to_key_alias_sorted_and_deduped(): + entry = TagRateLimitEntry(name="daily", limit=10, period_seconds=60, apply_to_key_alias=("b", "a", "a")) + assert entry.apply_to_key_alias == ("a", "b") + + +def test_apply_to_models_empty_list_rejected(): + with pytest.raises(ValueError, match="apply_to_models must be a non-empty list"): + TagRateLimitEntry(name="daily", limit=10, period_seconds=60, apply_to_models=()) + + +def test_apply_to_models_sorted_and_deduped(): + entry = TagRateLimitEntry(name="daily", limit=10, period_seconds=60, apply_to_models=("gpt-4o", "claude", "claude")) + assert entry.apply_to_models == ("claude", "gpt-4o") + + +def test_scope_values_empty_list_rejected(): + with pytest.raises(ValueError, match="values must be a non-empty list"): + TagRateLimitScope(tag_id="company_id", values=()) + + +def test_scope_values_sorted_and_deduped(): + scope = TagRateLimitScope(tag_id="company_id", values=("1032", "1001", "1001")) + assert scope.values == ("1001", "1032") + + +def test_scope_is_frozen(): + scope = TagRateLimitScope(tag_id="company_id", values=("1032",)) + with pytest.raises(ValidationError): + scope.tag_id = "other_tag" + + +def test_entry_accepts_enabled_for_and_disabled_for_scopes(): + entry = TagRateLimitEntry( + name="daily", + limit=10, + period_seconds=60, + enabled_for=TagRateLimitScope(tag_id="company_id", values=("1032",)), + disabled_for=TagRateLimitScope(tag_id="company_id", values=("9999",)), + ) + assert entry.enabled_for.values == ("1032",) + assert entry.disabled_for.values == ("9999",) + + +def test_group_defaults_to_no_limits(): + assert TagRateLimitGroup().limits == () + + +def test_rate_limits_group_holds_multiple_entries(): + entries = ( + TagRateLimitEntry(name="daily", limit=10, period_seconds=60), + TagRateLimitEntry(name="weekly", limit=100, period_seconds=604800), + ) + group = TagRateLimitGroup(limits=entries) + assert group.limits == entries + + +def test_model_info_tag_rate_limits_defaults_to_none(): + assert ModelInfo(id="x").tag_rate_limits is None + + +def test_model_info_accepts_tag_rate_limits(): + limits = TagRateLimits( + token_limits=TagRateLimitGroup(limits=(TagRateLimitEntry(name="daily", limit=10, period_seconds=60),)) + ) + info = ModelInfo(id="x", tag_rate_limits=limits) + assert info.tag_rate_limits.token_limits.limits[0].name == "daily"