from pathlib import Path import sys import time import unittest from unittest import mock ROOT = Path(__file__).resolve().parents[1] APP_DIR = ROOT / 'app' sys.path.insert(0, str(APP_DIR)) import scanner class ApiDeadlinePlumbingTests(unittest.TestCase): def test_deadline_is_consumed_internally_not_forwarded_to_requests(self): response = mock.Mock(status_code=200) with mock.patch.object(scanner.requests, 'request', return_value=response) as request: result = scanner.api_request( 'GET', 'https://example.invalid', timeout=10, deadline=time.monotonic() + 1, ) self.assertIs(result, response) self.assertNotIn('deadline', request.call_args.kwargs) self.assertGreater(request.call_args.kwargs['timeout'], 0) self.assertLessEqual(request.call_args.kwargs['timeout'], 1) def test_expired_deadline_never_reaches_requests(self): with mock.patch.object(scanner.requests, 'request') as request: with self.assertRaisesRegex(scanner.ApiRequestError, 'deadline expired'): scanner.api_request('GET', 'https://example.invalid', deadline=time.monotonic() - 1) request.assert_not_called() def test_deadline_bounds_unset_timeouts(self): for timeout in (None, (None, 30), (2, None), (None, None)): with self.subTest(timeout=timeout): response = mock.Mock(status_code=200) with mock.patch.object(scanner.scan_config, 'api_proxy_enabled', False), \ mock.patch.object(scanner.requests, 'request', return_value=response) as request: self.assertIs(scanner.api_request( 'GET', 'https://example.invalid', timeout=timeout, deadline=time.monotonic() + 1, ), response) effective = request.call_args.kwargs['timeout'] for value in effective if isinstance(effective, tuple) else (effective,): self.assertGreater(value, 0) self.assertLessEqual(value, 1) def test_response_after_deadline_is_closed_not_returned_or_retried(self): for stream in (False, True): for status in (200, 503): with self.subTest(stream=stream, status=status): clock = [0.0] response = mock.Mock(status_code=status) def late_response(*args, **kwargs): clock[0] = 11.0 return response with mock.patch.object(scanner.time, 'monotonic', side_effect=lambda: clock[0]), \ mock.patch.object(scanner.scan_config, 'api_proxy_enabled', False), \ mock.patch.object(scanner.requests, 'request', side_effect=late_response) as request, \ mock.patch.object(scanner, '_wait_or_raise_scan_slot_fatal') as wait: with self.assertRaisesRegex(scanner.ApiRequestError, 'deadline expired after response'): scanner.api_request( 'GET', 'https://example.invalid', timeout=30, deadline=10, max_retries=3, stream=stream, ) request.assert_called_once() response.close.assert_called_once() wait.assert_not_called() if __name__ == '__main__': unittest.main()