66"""
77
88import datetime
9+ import json
910import logging
1011import pathlib
1112import re
8485
8586@pytest .fixture (autouse = True )
8687def clear_resolver_cache () -> typing .Generator [None , None , None ]:
87- """Clear the class-level resolver cache before each test.
88+ """Clear the class-level resolver cache and cooldown report before each test.
8889
8990 BaseProvider.resolver_cache is a ClassVar that persists across test
9091 instances. Without clearing it, candidates fetched in one test are reused
@@ -93,7 +94,9 @@ def clear_resolver_cache() -> typing.Generator[None, None, None]:
9394 """
9495 resolver .BaseProvider .clear_cache ()
9596 resolver .BaseProvider ._cooldown_unsupported_warned .clear ()
97+ resolver .cooldown_report .clear ()
9698 yield
99+ resolver .cooldown_report .clear ()
97100
98101
99102def test_cooldown_filters_recent_version (
@@ -965,3 +968,121 @@ def test_resolve_package_cooldown_toplevel_compound_specifier_not_exempt(
965968 ctx , Requirement ("test-pkg==1.0,>0.9" ), req_type = RequirementType .TOP_LEVEL
966969 )
967970 assert result is _COOLDOWN
971+
972+
973+ # ---------------------------------------------------------------------------
974+ # CooldownReport tests
975+ # ---------------------------------------------------------------------------
976+
977+
978+ def test_cooldown_report_records_blocked_version () -> None :
979+ """Blocked versions are recorded in the module-level cooldown report."""
980+ with requests_mock .Mocker () as r :
981+ r .get (
982+ "https://pypi.org/simple/test-pkg/" ,
983+ json = _cooldown_json_response ,
984+ headers = {"Content-Type" : _PYPI_SIMPLE_JSON_CONTENT_TYPE },
985+ )
986+ provider = resolver .PyPIProvider (include_sdists = True , cooldown = _COOLDOWN )
987+ rslvr = resolvelib .Resolver (provider , resolvelib .BaseReporter ())
988+ rslvr .resolve ([Requirement ("test-pkg" )])
989+
990+ entries = resolver .cooldown_report .entries ()
991+ assert len (entries ) == 1
992+ entry = entries [0 ]
993+ assert entry .package == "test-pkg"
994+ assert entry .version == "2.0.0"
995+ assert entry .upload_time == "2026-03-24T00:00:00+00:00"
996+ assert entry .cooldown_min_age_days == 7
997+
998+
999+ def test_cooldown_report_empty_when_disabled () -> None :
1000+ """No entries recorded when cooldown is not configured."""
1001+ with requests_mock .Mocker () as r :
1002+ r .get (
1003+ "https://pypi.org/simple/test-pkg/" ,
1004+ json = _cooldown_json_response ,
1005+ headers = {"Content-Type" : _PYPI_SIMPLE_JSON_CONTENT_TYPE },
1006+ )
1007+ provider = resolver .PyPIProvider (include_sdists = True , cooldown = None )
1008+ rslvr = resolvelib .Resolver (provider , resolvelib .BaseReporter ())
1009+ rslvr .resolve ([Requirement ("test-pkg" )])
1010+
1011+ assert resolver .cooldown_report .entries () == []
1012+
1013+
1014+ def test_cooldown_report_write_to_json (tmp_path : pathlib .Path ) -> None :
1015+ """write_to() produces valid JSON with the expected structure."""
1016+ with requests_mock .Mocker () as r :
1017+ r .get (
1018+ "https://pypi.org/simple/test-pkg/" ,
1019+ json = _cooldown_json_response ,
1020+ headers = {"Content-Type" : _PYPI_SIMPLE_JSON_CONTENT_TYPE },
1021+ )
1022+ provider = resolver .PyPIProvider (include_sdists = True , cooldown = _COOLDOWN )
1023+ rslvr = resolvelib .Resolver (provider , resolvelib .BaseReporter ())
1024+ rslvr .resolve ([Requirement ("test-pkg" )])
1025+
1026+ output = tmp_path / "cooldown-skipped-versions.json"
1027+ resolver .cooldown_report .write_to (output )
1028+
1029+ assert output .exists ()
1030+ report = json .loads (output .read_text ())
1031+ assert report ["total_blocked" ] == 1
1032+ assert "test-pkg" in report ["packages" ]
1033+ assert report ["packages" ]["test-pkg" ][0 ]["version" ] == "2.0.0"
1034+ assert "generated_at" in report
1035+
1036+
1037+ def test_cooldown_report_empty_file_when_no_blocked (tmp_path : pathlib .Path ) -> None :
1038+ """write_to() creates a file with zero blocked entries when report is empty."""
1039+ output = tmp_path / "cooldown-skipped-versions.json"
1040+ resolver .cooldown_report .write_to (output )
1041+ assert output .exists ()
1042+ report = json .loads (output .read_text ())
1043+ assert report ["total_blocked" ] == 0
1044+ assert report ["packages" ] == {}
1045+
1046+
1047+ def test_cooldown_report_deduplicates () -> None :
1048+ """Repeated find_matches calls for the same package record only once."""
1049+ with requests_mock .Mocker () as r :
1050+ r .get (
1051+ "https://pypi.org/simple/test-pkg/" ,
1052+ json = _cooldown_json_response ,
1053+ headers = {"Content-Type" : _PYPI_SIMPLE_JSON_CONTENT_TYPE },
1054+ )
1055+ provider = resolver .PyPIProvider (
1056+ include_sdists = True , cooldown = _COOLDOWN , use_resolver_cache = False
1057+ )
1058+ req = Requirement ("test-pkg" )
1059+ # Simulate resolvelib backtracking — call find_matches twice
1060+ provider .find_matches (
1061+ identifier = "test-pkg" ,
1062+ requirements = {"test-pkg" : [req ]},
1063+ incompatibilities = {"test-pkg" : []},
1064+ )
1065+ provider .find_matches (
1066+ identifier = "test-pkg" ,
1067+ requirements = {"test-pkg" : [req ]},
1068+ incompatibilities = {"test-pkg" : []},
1069+ )
1070+
1071+ entries = resolver .cooldown_report .entries ()
1072+ assert len (entries ) == 1
1073+
1074+
1075+ def test_cooldown_report_clear () -> None :
1076+ """clear() removes all recorded entries."""
1077+ resolver .cooldown_report .record (
1078+ resolver .CooldownBlockedEntry (
1079+ package = "pkg" ,
1080+ version = "1.0.0" ,
1081+ upload_time = None ,
1082+ cooldown_min_age_days = 7 ,
1083+ provider = "test" ,
1084+ )
1085+ )
1086+ assert len (resolver .cooldown_report .entries ()) == 1
1087+ resolver .cooldown_report .clear ()
1088+ assert resolver .cooldown_report .entries () == []
0 commit comments