@@ -122,11 +122,61 @@ def test_head_failure_falls_back_to_streaming_get_and_confirms_404() -> None:
122122 assert session .calls [1 ][2 ]["stream" ] is True
123123
124124
125+ def test_head_redirect_without_location_falls_back_to_get () -> None :
126+ head = FakeResponse (301 )
127+ response = FakeResponse (200 )
128+ session = FakeSession (head , response )
129+ checker = LinkChecker (
130+ guard = guard_for (), session_factory = lambda : session , workers = 1 , min_interval = 0
131+ )
132+
133+ result = checker .check_one (link ())
134+
135+ assert result .status == "working"
136+ assert result .method == "GET"
137+ assert head .closed is True
138+ assert response .closed is True
139+
140+
141+ def test_get_redirect_without_location_is_fatal () -> None :
142+ head = FakeResponse (301 )
143+ response = FakeResponse (301 )
144+ session = FakeSession (head , response )
145+ checker = LinkChecker (
146+ guard = guard_for (), session_factory = lambda : session , workers = 1 , min_interval = 0
147+ )
148+
149+ result = checker .check_one (link ())
150+
151+ assert result .status == "error"
152+ assert "no Location" in (result .error or "" )
153+ assert head .closed is True
154+ assert response .closed is True
155+
156+
157+ def test_unsupported_redirect_status_is_fatal () -> None :
158+ response = FakeResponse (304 )
159+ session = FakeSession (response )
160+ checker = LinkChecker (
161+ guard = guard_for (), session_factory = lambda : session , workers = 1 , min_interval = 0
162+ )
163+
164+ result = checker .check_one (link ())
165+
166+ assert result .status == "error"
167+ assert "unsupported redirect status 304" in (result .error or "" )
168+ assert response .closed is True
169+
170+
125171@pytest .mark .parametrize ("status_code" , [403 , 408 , 425 , 429 , 500 , 503 ])
126172def test_transient_and_access_denied_statuses_need_review (status_code ) -> None :
127173 session = FakeSession (FakeResponse (status_code ), FakeResponse (status_code ))
128174 checker = LinkChecker (
129- guard = guard_for (), session_factory = lambda : session , workers = 1 , min_interval = 0
175+ guard = guard_for (),
176+ session_factory = lambda : session ,
177+ workers = 1 ,
178+ retries = 0 ,
179+ min_interval = 0 ,
130180 )
131181 assert checker .check_one (link ()).status == "review"
132182
@@ -347,10 +397,43 @@ def test_retry_configuration_ignores_unbounded_retry_after() -> None:
347397 retry = session .get_adapter ("https://" ).max_retries
348398 assert retry .respect_retry_after_header is False
349399 assert retry .backoff_max == 5.0
400+ assert retry .status == 0
401+ assert not retry .status_forcelist
350402 finally :
351403 session .close ()
352404
353405
406+ def test_status_retry_is_manual_and_closes_each_response () -> None :
407+ first = FakeResponse (503 )
408+ second = FakeResponse (200 )
409+
410+ class RetrySession (FakeSession ):
411+ def __init__ (self ):
412+ super ().__init__ (first )
413+ self .responses = iter ([first , second ])
414+
415+ def head (self , url , ** kwargs ):
416+ self .calls .append (("HEAD" , url , kwargs ))
417+ return next (self .responses )
418+
419+ session = RetrySession ()
420+ checker = LinkChecker (
421+ guard = guard_for (),
422+ session_factory = lambda : session ,
423+ workers = 1 ,
424+ retries = 1 ,
425+ backoff_factor = 0 ,
426+ min_interval = 0 ,
427+ )
428+
429+ result = checker .check_one (link ())
430+
431+ assert result .status == "working"
432+ assert len (session .calls ) == 2
433+ assert first .closed is True
434+ assert second .closed is True
435+
436+
354437def test_responses_close_when_result_processing_fails (monkeypatch ) -> None :
355438 head = FakeResponse (404 )
356439 response = FakeResponse (200 )
0 commit comments