diff --git a/googleapiclient/http.py b/googleapiclient/http.py index 187f6f5dac8..d7528776d50 100644 --- a/googleapiclient/http.py +++ b/googleapiclient/http.py @@ -1825,6 +1825,33 @@ def request( content = content.encode("utf-8") return httplib2.Response(resp), content + def close(self): + """Closes httplib2 connections. + + This is a no-op for HttpMockSequence since it doesn't hold + real network connections, but is required for compatibility + with the context manager protocol used by discovery.build(). + + Example: + http = HttpMockSequence([({'status': '200'}, '{}')]) + + # Using context manager (recommended) + with build('drive', 'v3', http=http) as service: + service.files().list().execute() + + # Or manual close + service = build('drive', 'v3', http=http) + # ... use service ... + service.close() + + See Also: + - HttpMock.close() for the similar implementation + - https://github.com/googleapis/google-api-python-client/issues/2359 + """ + # No-op: HttpMockSequence doesn't hold real connections + pass + + def set_user_agent(http, user_agent): """Set the user-agent on every request. diff --git a/tests/test_http.py b/tests/test_http.py index 42110adfab1..b0502e34892 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1732,3 +1732,56 @@ def test_build_http_default_308_is_excluded_as_redirect(self): if __name__ == "__main__": logging.getLogger().setLevel(logging.ERROR) unittest.main() + + +class TestHttpMockSequenceClose(unittest.TestCase): + """Tests for HttpMockSequence.close() method (Issue #2359).""" + + def test_http_mock_sequence_has_close_method(self): + """Verify HttpMockSequence has close() method.""" + http = HttpMockSequence([({'status': '200'}, b'OK')]) + + # Should have close() method + self.assertTrue(hasattr(http, 'close')) + self.assertTrue(callable(http.close)) + + def test_http_mock_sequence_close_does_not_raise(self): + """Verify close() can be called without raising.""" + http = HttpMockSequence([({'status': '200'}, b'OK')]) + + # Should not raise + http.close() + + def test_http_mock_sequence_close_is_no_op(self): + """Verify close() is safe to call multiple times.""" + http = HttpMockSequence([({'status': '200'}, b'OK')]) + + # Multiple calls should be safe + http.close() + http.close() + http.close() + + # Mock should still be usable after close + resp, content = http.request('http://example.com') + self.assertEqual(resp.status, 200) + self.assertEqual(content, b'OK') + + def test_http_mock_sequence_close_returns_none(self): + """Verify close() returns None like HttpMock.""" + http = HttpMockSequence([({'status': '200'}, b'OK')]) + + result = http.close() + self.assertIsNone(result) + + def test_http_mock_sequence_consistency_with_http_mock(self): + """Verify HttpMockSequence.close() behaves like HttpMock.close().""" + http_mock = HttpMock(headers={'status': '200'}) + http_sequence = HttpMockSequence([({'status': '200'}, b'OK')]) + + # Both should have close() + self.assertTrue(hasattr(http_mock, 'close')) + self.assertTrue(hasattr(http_sequence, 'close')) + + # Both should return None + self.assertIsNone(http_mock.close()) + self.assertIsNone(http_sequence.close())