import sys import os import unittest from unittest.mock import MagicMock # Adjust path to find backend modules. We need 'backend' to be in path so 'src' is top level. # Script is in backend/../scratch/verify_timeout.py # We want abs path to backend/ project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) backend_path = os.path.join(project_root, 'backend') sys.path.append(backend_path) # Mock dependencies to avoid database connections or complex setups # We need to mock modules BEFORE they are imported by the real code sys.modules['src.models'] = MagicMock() sys.modules['src.models.api_token'] = MagicMock() sys.modules['src.models.base'] = MagicMock() # Now import the client from the src package from src.services.garmin.client import GarminClient import garth class TestGarminTimeout(unittest.TestCase): def test_timeout_injection(self): # Initialize client (mocking credentials to avoid network) client = GarminClient("test", "test") # We want to verify that client.client.garth.sess.request (which we patched) adds 'timeout' # But we must be careful: we patched the INSTANCE method on init. # We need to mock the ORIGINAL request method that our wrapper calls, # OR just mock the return value if we don't care about the original running. # content of patch: # original_request = self.client.garth.sess.request # ... return original_request(...) # So if we mock 'original_request', how do we access it? # It's captured in the closure of 'request_with_timeout'. # Instead, we can inspect what our wrapper does. # But 'original_request' is the REAL request method of requests.Session (since we didn't mock it before Init). # We don't want to make a real network call. # So we should Mock `requests.Session.request` BEFORE initializing GarminClient? # But `GarminClient` init creates `garminconnect.Garmin` which creates `garth.Client` which creates `requests.Session`. # We can mock `requests.Session.request` globally? with unittest.mock.patch('requests.Session.request') as mock_session_request: # Re-init client so it picks up the mock as 'original_request' ?? # No, 'original_request = ...sess.request' grabs the bound method. # If we patch Session.request, new instances will have the mock. client = GarminClient("test", "test") # Now `client.client.garth.sess.request` is our wrapper. # And `original_request` (inside wrapper) should be the mock_session_request (bound). # Call our wrapper client.client.garth.sess.request("GET", "http://example.com") # Verify the mock was called with timeout kwargs = mock_session_request.call_args.kwargs print(f"Call kwargs: {kwargs}") self.assertIn('timeout', kwargs, "Timeout parameter missing from request") self.assertEqual(kwargs['timeout'], 30, "Timeout value incorrect") if __name__ == '__main__': try: unittest.main() except SystemExit as e: # Prevent unittest from exiting so we can see output if run via run_command with multiple steps pass