From d0951cbf613b0b3966c7fecc0acb707a71d85ecc Mon Sep 17 00:00:00 2001 From: Ashwin Upadhyay Date: Sun, 17 May 2026 01:50:55 +0530 Subject: [PATCH] test(proxy): split nested-access-groups test file under size gate 577-LOC test file tripped the +500 added-LOC size gate. classify + validate coverage moves to a sibling file; the original keeps the DFS and edge-case suite. each file is now under 500 LOC. --- .../proxy/auth/test_nested_access_groups.py | 152 +---------------- .../test_nested_access_groups_validate.py | 160 ++++++++++++++++++ 2 files changed, 162 insertions(+), 150 deletions(-) create mode 100644 tests/test_litellm/proxy/auth/test_nested_access_groups_validate.py diff --git a/tests/test_litellm/proxy/auth/test_nested_access_groups.py b/tests/test_litellm/proxy/auth/test_nested_access_groups.py index 0f4b3f63ff6..0f5fd575e45 100644 --- a/tests/test_litellm/proxy/auth/test_nested_access_groups.py +++ b/tests/test_litellm/proxy/auth/test_nested_access_groups.py @@ -4,8 +4,8 @@ Unit tests for nested access group resolution (#28032). Covers: - resolve_nested_groups (DFS + cycle detection) - _get_models_from_access_groups with the new group_memberships kwarg -- _classify_member_names precedence -- validate_models_exist with known_access_groups + +Classify + validate coverage lives in test_nested_access_groups_validate.py. """ import os @@ -19,10 +19,6 @@ from litellm.proxy.auth.model_checks import ( _get_models_from_access_groups, resolve_nested_groups, ) -from litellm.proxy.management_endpoints.model_access_group_management_endpoints import ( - _classify_member_names, - validate_models_exist, -) # --------------------------------------------------------------------------- @@ -159,87 +155,6 @@ def test_include_model_access_groups_keeps_parent_name(): assert "dall-e-3" in result -# --------------------------------------------------------------------------- -# _classify_member_names -# --------------------------------------------------------------------------- - - -def test_classify_router_model_takes_precedence_over_group(): - """A name registered as both a router model and a group is classified as a model.""" - real, child, unknown = _classify_member_names( - names=["shared-name"], - router_model_names={"shared-name"}, - known_access_groups={"shared-name"}, - ) - assert real == ["shared-name"] - assert child == [] - assert unknown == [] - - -def test_classify_splits_mixed_input(): - """Mixed input is split across the three buckets in order.""" - real, child, unknown = _classify_member_names( - names=["gpt-4", "image-group", "mystery"], - router_model_names={"gpt-4"}, - known_access_groups={"image-group"}, - ) - assert real == ["gpt-4"] - assert child == ["image-group"] - assert unknown == ["mystery"] - - -# --------------------------------------------------------------------------- -# validate_models_exist -# --------------------------------------------------------------------------- - - -def test_validate_models_exist_accepts_known_group_names(): - """Group names are valid members when passed via known_access_groups.""" - - class FakeRouter: - def get_model_names(self): - return ["gpt-4"] - - all_valid, missing = validate_models_exist( - model_names=["gpt-4", "image-group"], - llm_router=FakeRouter(), - known_access_groups={"image-group"}, - ) - assert all_valid is True - assert missing == [] - - -def test_validate_models_exist_reports_unknown_names(): - """Names that match neither a router model nor a known group are reported.""" - - class FakeRouter: - def get_model_names(self): - return ["gpt-4"] - - all_valid, missing = validate_models_exist( - model_names=["gpt-4", "image-group", "ghost"], - llm_router=FakeRouter(), - known_access_groups={"image-group"}, - ) - assert all_valid is False - assert missing == ["ghost"] - - -def test_validate_models_exist_backwards_compatible_without_groups(): - """Without known_access_groups, behavior is identical to today's pure-model check.""" - - class FakeRouter: - def get_model_names(self): - return ["gpt-4"] - - all_valid, missing = validate_models_exist( - model_names=["gpt-4", "anything-else"], - llm_router=FakeRouter(), - ) - assert all_valid is False - assert missing == ["anything-else"] - - # --------------------------------------------------------------------------- # Smoke: chained scenarios # --------------------------------------------------------------------------- @@ -503,69 +418,6 @@ def test_membership_with_empty_child_list_does_not_explode(): assert result == ["gpt-4"] -def test_classify_empty_input_returns_three_empty_lists(): - """Edge case: empty names input must not error.""" - real, child, unknown = _classify_member_names( - names=[], - router_model_names={"gpt-4"}, - known_access_groups={"g"}, - ) - assert real == [] and child == [] and unknown == [] - - -def test_classify_preserves_input_order(): - """Output preserves caller-provided order within each bucket - matters for error messages.""" - real, child, unknown = _classify_member_names( - names=["c-unknown", "a-model", "b-group", "d-model"], - router_model_names={"a-model", "d-model"}, - known_access_groups={"b-group"}, - ) - assert real == ["a-model", "d-model"] - assert child == ["b-group"] - assert unknown == ["c-unknown"] - - -def test_validate_models_exist_reports_missing_in_input_order(): - """Missing names are reported in the order they appeared, for human-readable errors.""" - - class FakeRouter: - def get_model_names(self): - return ["a"] - - all_valid, missing = validate_models_exist( - model_names=["z-missing", "a", "y-missing"], - llm_router=FakeRouter(), - known_access_groups=set(), - ) - assert all_valid is False - assert missing == ["z-missing", "y-missing"] - - -def test_validate_models_exist_with_null_router_still_accepts_known_groups(): - """DB-only deployment: llm_router is None but known_access_groups is still authoritative - for nested-group composition - only names not in known_groups are reported missing. - """ - all_valid, missing = validate_models_exist( - model_names=["image", "reasoning"], - llm_router=None, - known_access_groups={"image", "reasoning"}, - ) - assert all_valid is True - assert missing == [] - - -def test_validate_models_exist_with_null_router_rejects_unknown_real_models(): - """Without a router we can't validate real model names, so anything not in - known_access_groups is fail-closed reported as missing.""" - all_valid, missing = validate_models_exist( - model_names=["gpt-4", "image"], - llm_router=None, - known_access_groups={"image"}, - ) - assert all_valid is False - assert missing == ["gpt-4"] - - def test_resolve_with_empty_models_and_empty_memberships_returns_empty(): """Defensive: both maps empty + unknown seed = empty result, no crash.""" result = resolve_nested_groups( diff --git a/tests/test_litellm/proxy/auth/test_nested_access_groups_validate.py b/tests/test_litellm/proxy/auth/test_nested_access_groups_validate.py new file mode 100644 index 00000000000..bfc32a695c8 --- /dev/null +++ b/tests/test_litellm/proxy/auth/test_nested_access_groups_validate.py @@ -0,0 +1,160 @@ +""" +Validate + classify coverage for nested access groups (#28032). + +Split out of test_nested_access_groups.py to keep file size manageable. The +sister file covers resolve_nested_groups / _get_models_from_access_groups. +""" + +import os +import sys + +sys.path.insert(0, os.path.abspath("../../..")) + +from litellm.proxy.management_endpoints.model_access_group_management_endpoints import ( + _classify_member_names, + validate_models_exist, +) + + +# --------------------------------------------------------------------------- +# _classify_member_names +# --------------------------------------------------------------------------- + + +def test_classify_router_model_takes_precedence_over_group(): + """A name registered as both a router model and a group is classified as a model.""" + real, child, unknown = _classify_member_names( + names=["shared-name"], + router_model_names={"shared-name"}, + known_access_groups={"shared-name"}, + ) + assert real == ["shared-name"] + assert child == [] + assert unknown == [] + + +def test_classify_splits_mixed_input(): + """Mixed input is split across the three buckets in order.""" + real, child, unknown = _classify_member_names( + names=["gpt-4", "image-group", "mystery"], + router_model_names={"gpt-4"}, + known_access_groups={"image-group"}, + ) + assert real == ["gpt-4"] + assert child == ["image-group"] + assert unknown == ["mystery"] + + +def test_classify_empty_input_returns_three_empty_lists(): + """Edge case: empty names input must not error.""" + real, child, unknown = _classify_member_names( + names=[], + router_model_names={"gpt-4"}, + known_access_groups={"g"}, + ) + assert real == [] and child == [] and unknown == [] + + +def test_classify_preserves_input_order(): + """Output preserves caller-provided order within each bucket - matters for error messages.""" + real, child, unknown = _classify_member_names( + names=["c-unknown", "a-model", "b-group", "d-model"], + router_model_names={"a-model", "d-model"}, + known_access_groups={"b-group"}, + ) + assert real == ["a-model", "d-model"] + assert child == ["b-group"] + assert unknown == ["c-unknown"] + + +# --------------------------------------------------------------------------- +# validate_models_exist +# --------------------------------------------------------------------------- + + +def test_validate_models_exist_accepts_known_group_names(): + """Group names are valid members when passed via known_access_groups.""" + + class FakeRouter: + def get_model_names(self): + return ["gpt-4"] + + all_valid, missing = validate_models_exist( + model_names=["gpt-4", "image-group"], + llm_router=FakeRouter(), + known_access_groups={"image-group"}, + ) + assert all_valid is True + assert missing == [] + + +def test_validate_models_exist_reports_unknown_names(): + """Names that match neither a router model nor a known group are reported.""" + + class FakeRouter: + def get_model_names(self): + return ["gpt-4"] + + all_valid, missing = validate_models_exist( + model_names=["gpt-4", "image-group", "ghost"], + llm_router=FakeRouter(), + known_access_groups={"image-group"}, + ) + assert all_valid is False + assert missing == ["ghost"] + + +def test_validate_models_exist_backwards_compatible_without_groups(): + """Without known_access_groups, behavior is identical to today's pure-model check.""" + + class FakeRouter: + def get_model_names(self): + return ["gpt-4"] + + all_valid, missing = validate_models_exist( + model_names=["gpt-4", "anything-else"], + llm_router=FakeRouter(), + ) + assert all_valid is False + assert missing == ["anything-else"] + + +def test_validate_models_exist_reports_missing_in_input_order(): + """Missing names are reported in the order they appeared, for human-readable errors.""" + + class FakeRouter: + def get_model_names(self): + return ["a"] + + all_valid, missing = validate_models_exist( + model_names=["z-missing", "a", "y-missing"], + llm_router=FakeRouter(), + known_access_groups=set(), + ) + assert all_valid is False + assert missing == ["z-missing", "y-missing"] + + +def test_validate_models_exist_with_null_router_still_accepts_known_groups(): + """DB-only deployment: llm_router is None but known_access_groups is still authoritative + for nested-group composition - only names not in known_groups are reported missing. + """ + all_valid, missing = validate_models_exist( + model_names=["image", "reasoning"], + llm_router=None, + known_access_groups={"image", "reasoning"}, + ) + assert all_valid is True + assert missing == [] + + +def test_validate_models_exist_with_null_router_rejects_unknown_real_models(): + """Without a router we can't validate real model names, so anything not in + known_access_groups is fail-closed reported as missing.""" + all_valid, missing = validate_models_exist( + model_names=["gpt-4", "image"], + llm_router=None, + known_access_groups={"image"}, + ) + assert all_valid is False + assert missing == ["gpt-4"]