litellm/tests/unit/test_assert_workflow_dir_hygiene.py
yuneng-jiang f6882246d4
test: move tests/test_litellm root and small trees into tests/unit (#43186)
* ci: run the unit_selection.sh shard files on every event instead of only fork pull requests

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci: rename fork-flag to unit-flag now that it applies on every event

* test: move tests/test_litellm root and small trees into tests/unit

Pure renames, no content changes. Follow-up commits in this PR fix
references, merge the three files that already existed in tests/unit,
keep live-provider tests in tests/test_litellm and wire CI.

* test: carry tests/test_litellm conftest isolation into tests/unit

Callback lists, routing fallbacks, cached HTTP clients, logger state, AWS,
proxy-URL and keychain env, and session-end client cleanup now reset for
unit tests too. The environment isolation owns its MonkeyPatch so a test's
own monkeypatch is undone before the model-cost teardown runs.

* test: merge, split and prune the moved root and small-tree tests

Merge batches/test_batch_utils.py and the chat_completions and messages
dispatch tests into the files that already existed in tests/unit. Keep
the live Gemini interactions tests, the async image-fetch format test and
the OpenAI embedding scorer test in tests/test_litellm since they need
real network or keys. Put test_router.py under tests/unit/test_router so
the existing package no longer shadows it. Delete eight tests the audit
found superseded by stronger ones kept in this move.

* ci: run the moved root and small-tree tests under their legacy flags

Add the misc and responses-caching-types flags to unit_selection.sh and
CircleCI, extend enterprise-routing and mcp-integration, and point the
legacy GHA shards, Makefile, redis-compat workflow, merge smoke manifest
and change classifier at the new paths.

* test: make the new tests/unit directories packages

tests/unit/test_package_layout.py requires every directory to carry an
__init__.py, and without one the moved and retained
test_litellm_responses_bridge.py modules collide on import.

* test: scope the unit socket block to tests/unit in shared sessions

The GHA shards collect the legacy test-path and the unit selection in one
pytest session. The unit conftest's loopback-only block leaked into legacy
modules that reach the network at import. The legacy conftest now lifts the
restriction at collect and setup time, and the unit conftest re-applies it
when collecting its own modules.

* test: give the shard-script tests their own GITHUB_OUTPUT

They only passed where the runner set it. The CircleCI unit job's env
allowlist drops it, so the script's redirect failed there.

* test: point the router and module-deletion checks at tests/unit

router_code_coverage and code_qa_check_tests only searched tests/test_litellm,
so the moved router tests no longer counted. The two silent-experiment tests
the audit deleted were the only direct callers of those methods; they are
replaced with tests that assert the forwarded shadow request and the
recursion guard.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-25 11:30:43 -07:00

115 lines
4.2 KiB
Python

"""Tests for .github/scripts/assert_workflow_dir_hygiene.py."""
import importlib.util
import sys
from pathlib import Path
from typing import Final
import pytest
_REPO_ROOT: Final = Path(__file__).resolve().parents[2]
_MODULE_PATH: Final = _REPO_ROOT / ".github" / "scripts" / "assert_workflow_dir_hygiene.py"
_spec: Final = importlib.util.spec_from_file_location("assert_workflow_dir_hygiene", _MODULE_PATH)
hygiene: Final = importlib.util.module_from_spec(_spec)
sys.modules[_spec.name] = hygiene # @dataclass(slots=True) rebuilds via sys.modules
_spec.loader.exec_module(hygiene)
def _codes(path_name, triggers):
return [f.code for f in hygiene._naming_findings(Path(path_name), frozenset(triggers))]
def test_a_call_only_workflow_without_the_prefix_is_flagged():
assert _codes("deploy.yml", {"workflow_call"}) == ["WF002"]
def test_a_call_only_workflow_with_the_prefix_is_clean():
assert _codes("_deploy.yml", {"workflow_call"}) == []
def test_a_dual_mode_workflow_keeps_its_plain_name():
# workflow_call plus a human trigger is deliberate: the `_` prefix would hide a
# workflow someone is meant to be able to dispatch.
assert _codes("create-release-branch.yml", {"workflow_call", "workflow_dispatch"}) == []
def test_a_prefixed_workflow_nobody_can_call_is_flagged():
assert _codes("_helper.yml", {"push"}) == ["WF003"]
def test_a_plain_workflow_with_ordinary_triggers_is_clean():
assert _codes("test-unit.yml", {"pull_request", "push"}) == []
@pytest.mark.parametrize(
"raw, expected",
[
({"on": "push"}, {"push"}),
({"on": ["push", "pull_request"]}, {"push", "pull_request"}),
({"on": {"workflow_call": None}}, {"workflow_call"}),
({True: {"pull_request": None}}, {"pull_request"}),
({"jobs": {}}, set()),
("not a mapping", set()),
],
)
def test_triggers_reads_every_shape_the_on_key_takes(raw, expected):
# YAML 1.1 turns a bare `on:` key into the boolean True, which is why the loaded
# document has to be read both ways.
assert hygiene._triggers(raw) == frozenset(expected)
def test_the_repo_as_it_stands_holds_only_workflows_in_the_workflow_dir():
assert [f.subject for f in hygiene._strays(hygiene.WORKFLOW_DIR)] == []
def test_the_repo_as_it_stands_names_every_reusable_workflow_with_the_prefix():
assert [f.subject for f in hygiene._misnamed(hygiene.WORKFLOW_DIR)] == []
def test_the_repo_as_it_stands_spells_every_workflow_yml():
assert [f.subject for f in hygiene._misspelled(hygiene.WORKFLOW_DIR)] == []
_WORKFLOW: Final = "name: ci\non: [push]\njobs:\n a:\n runs-on: ubuntu-latest\n steps: [{run: 'true'}]\n"
def _populate(directory, files):
for name, body in files.items():
target = directory / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(body, encoding="utf-8")
return directory
def _findings(directory):
return [
(f.subject, f.code)
for f in hygiene._strays(directory) + hygiene._misspelled(directory) + hygiene._misnamed(directory)
]
def test_a_script_at_the_top_level_is_a_stray(tmp_path):
directory = _populate(tmp_path, {"ci.yml": _WORKFLOW, "render.py": "print(1)\n"})
assert _findings(directory) == [("render.py", "WF001")]
def test_a_script_inside_a_subdirectory_is_left_alone(tmp_path):
directory = _populate(tmp_path, {"ci.yml": _WORKFLOW, "helpers/render.py": "print(1)\n"})
assert _findings(directory) == []
def test_a_yaml_workflow_is_a_naming_finding_not_a_stray(tmp_path):
directory = _populate(tmp_path, {"test-model-map.yaml": _WORKFLOW})
assert _findings(directory) == [("test-model-map.yaml", "WF004")]
def test_the_yaml_message_names_the_rename_and_not_the_scripts_directory(tmp_path):
directory = _populate(tmp_path, {"test-model-map.yaml": _WORKFLOW})
detail = hygiene._misspelled(directory)[0].detail
assert "test-model-map.yml" in detail
assert hygiene.SCRIPT_HOME not in detail
def test_a_yaml_workflow_is_still_held_to_the_prefix_rules(tmp_path):
directory = _populate(tmp_path, {"deploy.yaml": "on: {workflow_call: null}\njobs: {}\n"})
assert _findings(directory) == [("deploy.yaml", "WF004"), ("deploy.yaml", "WF002")]