diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50cff98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/httmock-1.2.6.tar.gz +/httmock-1.3.0.tar.gz +/httmock-1.4.0.tar.gz diff --git a/.packit.yaml b/.packit.yaml new file mode 100644 index 0000000..1d68e68 --- /dev/null +++ b/.packit.yaml @@ -0,0 +1,25 @@ +--- +# See the documentation for more information: +# https://packit.dev/docs/configuration/ + +upstream_project_url: https://github.com/patrys/httmock +copy_upstream_release_description: false +upstream_tag_include: '^\d+\.\d+\.\d+$' + +jobs: + - job: pull_from_upstream + trigger: release + dist_git_branches: + - fedora-rawhide + - epel10 + - job: koji_build + trigger: commit + allowed_committers: ['packit','all_admins'] + dist_git_branches: + - fedora-rawhide + - epel10 + - job: bodhi_update + trigger: commit + allowed_builders: ['packit','all_users'] + dist_git_branches: + - fedora-rawhide diff --git a/64.diff b/64.diff new file mode 100644 index 0000000..7755b76 --- /dev/null +++ b/64.diff @@ -0,0 +1,19 @@ +diff --git a/tox.ini b/tox.ini +new file mode 100644 +index 0000000..e781a62 +--- /dev/null ++++ b/tox.ini +@@ -0,0 +1,13 @@ ++# Tox (https://tox.readthedocs.io) is a tool for running tests ++# in multiple virtualenvs. This configuration file will run the ++# test suite on all supported Python versions. To use it, ++# "python -m pip install tox" and then run "tox" from this directory. ++ ++[tox] ++envlist = py{27, 34, 35, 36, 37, 38, 39, 310, 311, py2, py3} ++ ++[testenv] ++deps = ++ pytest ++ requests ++commands = {envpython} -b -m pytest -W always tests.py {posargs} diff --git a/README.md b/README.md deleted file mode 100644 index 255b310..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# python-httmock - -mocking library for requests \ No newline at end of file diff --git a/changelog b/changelog new file mode 100644 index 0000000..29ef604 --- /dev/null +++ b/changelog @@ -0,0 +1,92 @@ +* Sat Jan 18 2025 Fedora Release Engineering - 1.4.0-16 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild + +* Fri Jul 19 2024 Fedora Release Engineering - 1.4.0-15 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild + +* Fri Jun 07 2024 Python Maint - 1.4.0-14 +- Rebuilt for Python 3.13 + +* Fri Jan 26 2024 Fedora Release Engineering - 1.4.0-13 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Mon Jan 22 2024 Fedora Release Engineering - 1.4.0-12 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jul 21 2023 Fedora Release Engineering - 1.4.0-11 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Wed Jun 14 2023 Python Maint - 1.4.0-10 +- Rebuilt for Python 3.12 + +* Fri May 05 2023 Steve Traylen 1.4.0-9 +- Complete migration to pyproject macros +- Switch SPDX license field +- Use tests.py matching released version of module (rhbz#2175195) + +* Fri Jan 20 2023 Fedora Release Engineering - 1.4.0-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Fri Jul 22 2022 Fedora Release Engineering - 1.4.0-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Mon Jun 13 2022 Python Maint - 1.4.0-6 +- Rebuilt for Python 3.11 + +* Fri Jan 28 2022 Steve Traylen 1.4.0-5 +- Migrate tests to tox rhbz#2019409 + +* Fri Jan 21 2022 Fedora Release Engineering - 1.4.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Fri Jul 23 2021 Fedora Release Engineering - 1.4.0-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Fri Jun 04 2021 Python Maint - 1.4.0-2 +- Rebuilt for Python 3.10 + +* Mon Mar 8 2021 Steve Traylen - 1.4.0-1 +- Update to 1.4.0 + +* Wed Jan 27 2021 Fedora Release Engineering - 1.3.0-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Wed Jul 29 2020 Fedora Release Engineering - 1.3.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Tue May 26 2020 Miro Hrončok - 1.3.0-3 +- Rebuilt for Python 3.9 + +* Thu Jan 30 2020 Fedora Release Engineering - 1.3.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Wed Nov 13 2019 Steve Traylen - 1.3.0-1 +- Update to 1.3.0 + +* Thu Oct 03 2019 Miro Hrončok - 1.2.6-9 +- Rebuilt for Python 3.8.0rc1 (#1748018) + +* Mon Aug 19 2019 Miro Hrončok - 1.2.6-8 +- Rebuilt for Python 3.8 + +* Fri Jul 26 2019 Fedora Release Engineering - 1.2.6-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Sat Feb 02 2019 Fedora Release Engineering - 1.2.6-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Fri Jan 04 2019 Igor Gnatenko - 1.2.6-5 +- Enable python dependency generator + +* Fri Jan 04 2019 Miro Hrončok - 1.2.6-4 +- Subpackage python2-httmock has been removed + See https://fedoraproject.org/wiki/Changes/Mass_Python_2_Package_Removal + +* Sat Jul 14 2018 Fedora Release Engineering - 1.2.6-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Tue Jun 19 2018 Miro Hrončok - 1.2.6-2 +- Rebuilt for Python 3.7 + +* Fri May 11 2018 Steve Traylen - 1.2.6-1 +- Initial package. diff --git a/python-httmock.spec b/python-httmock.spec new file mode 100644 index 0000000..596edac --- /dev/null +++ b/python-httmock.spec @@ -0,0 +1,60 @@ +Name: python-httmock +Version: 1.4.0 +Release: %autorelease +Summary: A mocking library for requests +License: Apache-2.0 +URL: https://github.com/patrys/httmock + +# Switch to github at next release to avoid the extra Source1 +Source0: https://files.pythonhosted.org/packages/source/h/httmock/httmock-%{version}.tar.gz +Source1: https://raw.githubusercontent.com/patrys/httmock/%{version}/tests.py + +# Add a tox file. +# https://bugzilla.redhat.com/show_bug.cgi?id=2019409 +Patch0: https://patch-diff.githubusercontent.com/raw/patrys/httmock/pull/64.diff +BuildArch: noarch + +%global _description %{expand: +A mocking library for requests for Python. +You can use it to mock third-party APIs and test libraries +that use requests internally} + + +%description %_description + +%package -n python3-httmock +Summary: %{summary} +BuildRequires: python3-devel + +%description -n python3-httmock %_description + + +%prep +%autosetup -p1 -n httmock-%{version} +cp %{SOURCE1} . + +%generate_buildrequires +%pyproject_buildrequires -t + + +%build +%pyproject_wheel + + +%install +%pyproject_install + +%pyproject_save_files httmock + + +%check +%{tox} + + +%files -n python3-httmock -f %{pyproject_files} +%license LICENSE +%doc README.md + + +%changelog +%autochangelog diff --git a/sources b/sources new file mode 100644 index 0000000..3d1b8d1 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +SHA512 (httmock-1.4.0.tar.gz) = d8674c0bd7c667d02e100f35157f717189ddcfb14d26993a87e8823aec255d191ab6ac53da0cb4035eb37caab36f8f7c6705bf10f0d8d4de62a5aa09cddacfc4 diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..9d4cabb --- /dev/null +++ b/tests.py @@ -0,0 +1,395 @@ +# -*- coding: utf-8 -*- +import requests +import unittest + +from httmock import (all_requests, response, urlmatch, with_httmock, HTTMock, + remember_called, text_type, binary_type) + + +@urlmatch(scheme='swallow') +def unmatched_scheme(url, request): + raise AssertionError('This is outrageous') + + +@urlmatch(path=r'^never$') +def unmatched_path(url, request): + raise AssertionError('This is outrageous') + + +@urlmatch(method='post') +def unmatched_method(url, request): + raise AssertionError('This is outrageous') + + +@urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$') +def google_mock(url, request): + return 'Hello from Google' + + +@urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$') +@remember_called +def google_mock_count(url, request): + return 'Hello from Google' + + +@urlmatch(scheme='http', netloc=r'(.*\.)?facebook\.com$') +def facebook_mock(url, request): + return 'Hello from Facebook' + + +@urlmatch(scheme='http', netloc=r'(.*\.)?facebook\.com$') +@remember_called +def facebook_mock_count(url, request): + return 'Hello from Facebook' + +@urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$', method='POST') +@remember_called +def google_mock_store_requests(url, request): + return 'Posting at Google' + + +@all_requests +def charset_utf8(url, request): + return { + 'content': u'Motörhead'.encode('utf-8'), + 'status_code': 200, + 'headers': { + 'Content-Type': 'text/plain; charset=utf-8' + } + } + + +def any_mock(url, request): + return 'Hello from %s' % (url.netloc,) + + +def dict_any_mock(url, request): + return { + 'content': 'Hello from %s' % (url.netloc,), + 'status_code': 200, + 'http_vsn': 10, + } + + +def example_400_response(url, response): + r = requests.Response() + r.status_code = 400 + r._content = b'Bad request.' + return r + + +class MockTest(unittest.TestCase): + + def test_return_type(self): + with HTTMock(any_mock): + r = requests.get('http://domain.com/') + self.assertTrue(isinstance(r, requests.Response)) + self.assertTrue(isinstance(r.content, binary_type)) + self.assertTrue(isinstance(r.text, text_type)) + + def test_scheme_fallback(self): + with HTTMock(unmatched_scheme, any_mock): + r = requests.get('http://example.com/') + self.assertEqual(r.content, b'Hello from example.com') + + def test_path_fallback(self): + with HTTMock(unmatched_path, any_mock): + r = requests.get('http://example.com/') + self.assertEqual(r.content, b'Hello from example.com') + + def test_method_fallback(self): + with HTTMock(unmatched_method, any_mock): + r = requests.get('http://example.com/') + self.assertEqual(r.content, b'Hello from example.com') + + def test_netloc_fallback(self): + with HTTMock(google_mock, facebook_mock): + r = requests.get('http://google.com/') + self.assertEqual(r.content, b'Hello from Google') + with HTTMock(google_mock, facebook_mock): + r = requests.get('http://facebook.com/') + self.assertEqual(r.content, b'Hello from Facebook') + + def test_400_response(self): + with HTTMock(example_400_response): + r = requests.get('http://example.com/') + self.assertEqual(r.status_code, 400) + self.assertEqual(r.content, b'Bad request.') + + def test_real_request_fallback(self): + with HTTMock(any_mock): + with HTTMock(google_mock, facebook_mock): + r = requests.get('http://example.com/') + self.assertEqual(r.status_code, 200) + self.assertEqual(r.content, b'Hello from example.com') + + def test_invalid_intercept_response_raises_value_error(self): + @all_requests + def response_content(url, request): + return -1 + with HTTMock(response_content): + self.assertRaises(TypeError, requests.get, 'http://example.com/') + + def test_encoding_from_contenttype(self): + with HTTMock(charset_utf8): + r = requests.get('http://example.com/') + self.assertEqual(r.encoding, 'utf-8') + self.assertEqual(r.text, u'Motörhead') + self.assertEqual(r.content, r.text.encode('utf-8')) + + def test_has_raw_version(self): + with HTTMock(any_mock): + r = requests.get('http://example.com') + self.assertEqual(r.raw.version, 11) + with HTTMock(dict_any_mock): + r = requests.get('http://example.com') + self.assertEqual(r.raw.version, 10) + +class DecoratorTest(unittest.TestCase): + + @with_httmock(any_mock) + def test_decorator(self): + r = requests.get('http://example.com/') + self.assertEqual(r.content, b'Hello from example.com') + + @with_httmock(any_mock) + def test_iter_lines(self): + r = requests.get('http://example.com/') + self.assertEqual(list(r.iter_lines()), + [b'Hello from example.com']) + + +class AllRequestsDecoratorTest(unittest.TestCase): + + def test_all_requests_response(self): + @all_requests + def response_content(url, request): + return {'status_code': 200, 'content': 'Oh hai'} + with HTTMock(response_content): + r = requests.get('https://example.com/') + self.assertEqual(r.status_code, 200) + self.assertEqual(r.content, b'Oh hai') + + def test_all_str_response(self): + @all_requests + def response_content(url, request): + return 'Hello' + with HTTMock(response_content): + r = requests.get('https://example.com/') + self.assertEqual(r.content, b'Hello') + + +class AllRequestsMethodDecoratorTest(unittest.TestCase): + @all_requests + def response_content(self, url, request): + return {'status_code': 200, 'content': 'Oh hai'} + + def test_all_requests_response(self): + with HTTMock(self.response_content): + r = requests.get('https://example.com/') + self.assertEqual(r.status_code, 200) + self.assertEqual(r.content, b'Oh hai') + + @all_requests + def string_response_content(self, url, request): + return 'Hello' + + def test_all_str_response(self): + with HTTMock(self.string_response_content): + r = requests.get('https://example.com/') + self.assertEqual(r.content, b'Hello') + + +class UrlMatchMethodDecoratorTest(unittest.TestCase): + @urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$') + def google_mock(self, url, request): + return 'Hello from Google' + + @urlmatch(scheme='http', netloc=r'(.*\.)?facebook\.com$') + def facebook_mock(self, url, request): + return 'Hello from Facebook' + + @urlmatch(query=r'.*page=test') + def query_page_mock(self, url, request): + return 'Hello from test page' + + def test_netloc_fallback(self): + with HTTMock(self.google_mock, facebook_mock): + r = requests.get('http://google.com/') + self.assertEqual(r.content, b'Hello from Google') + with HTTMock(self.google_mock, facebook_mock): + r = requests.get('http://facebook.com/') + self.assertEqual(r.content, b'Hello from Facebook') + + def test_query(self): + with HTTMock(self.query_page_mock, self.google_mock): + r = requests.get('http://google.com/?page=test') + r2 = requests.get('http://google.com/') + self.assertEqual(r.content, b'Hello from test page') + self.assertEqual(r2.content, b'Hello from Google') + + +class ResponseTest(unittest.TestCase): + + content = {'name': 'foo', 'ipv4addr': '127.0.0.1'} + content_list = list(content.keys()) + + def test_response_auto_json(self): + r = response(0, self.content) + self.assertTrue(isinstance(r.content, binary_type)) + self.assertTrue(isinstance(r.text, text_type)) + self.assertEqual(r.json(), self.content) + r = response(0, self.content_list) + self.assertEqual(r.json(), self.content_list) + + def test_response_status_code(self): + r = response(200) + self.assertEqual(r.status_code, 200) + + def test_response_headers(self): + r = response(200, None, {'Content-Type': 'application/json'}) + self.assertEqual(r.headers['content-type'], 'application/json') + + def test_response_raw_version(self): + r = response(200, None, {'Content-Type': 'application/json'}, + http_vsn=10) + self.assertEqual(r.raw.version, 10) + + def test_response_cookies(self): + @all_requests + def response_content(url, request): + return response(200, 'Foo', {'Set-Cookie': 'foo=bar;'}, + request=request) + with HTTMock(response_content): + r = requests.get('https://example.com/') + self.assertEqual(len(r.cookies), 1) + self.assertTrue('foo' in r.cookies) + self.assertEqual(r.cookies['foo'], 'bar') + + def test_response_session_cookies(self): + @all_requests + def response_content(url, request): + return response(200, 'Foo', {'Set-Cookie': 'foo=bar;'}, + request=request) + session = requests.Session() + with HTTMock(response_content): + r = session.get('https://foo_bar') + self.assertEqual(len(r.cookies), 1) + self.assertTrue('foo' in r.cookies) + self.assertEqual(r.cookies['foo'], 'bar') + self.assertEqual(len(session.cookies), 1) + self.assertTrue('foo' in session.cookies) + self.assertEqual(session.cookies['foo'], 'bar') + + def test_session_persistent_cookies(self): + session = requests.Session() + with HTTMock(lambda u, r: response(200, 'Foo', {'Set-Cookie': 'foo=bar;'}, request=r)): + session.get('https://foo_bar') + with HTTMock(lambda u, r: response(200, 'Baz', {'Set-Cookie': 'baz=qux;'}, request=r)): + session.get('https://baz_qux') + self.assertEqual(len(session.cookies), 2) + self.assertTrue('foo' in session.cookies) + self.assertEqual(session.cookies['foo'], 'bar') + self.assertTrue('baz' in session.cookies) + self.assertEqual(session.cookies['baz'], 'qux') + + def test_python_version_encoding_differences(self): + # Previous behavior would result in this test failing in Python3 due + # to how requests checks for utf-8 JSON content in requests.utils with: + # + # TypeError: Can't convert 'bytes' object to str implicitly + @all_requests + def get_mock(url, request): + return {'content': self.content, + 'headers': {'content-type': 'application/json'}, + 'status_code': 200, + 'elapsed': 5} + + with HTTMock(get_mock): + response = requests.get('http://example.com/') + self.assertEqual(self.content, response.json()) + + def test_mock_redirect(self): + @urlmatch(netloc='example.com') + def get_mock(url, request): + return {'status_code': 302, + 'headers': {'Location': 'http://google.com/'}} + + with HTTMock(get_mock, google_mock): + response = requests.get('http://example.com/') + self.assertEqual(len(response.history), 1) + self.assertEqual(response.content, b'Hello from Google') + + +class StreamTest(unittest.TestCase): + @with_httmock(any_mock) + def test_stream_request(self): + r = requests.get('http://domain.com/', stream=True) + self.assertEqual(r.raw.read(), b'Hello from domain.com') + + @with_httmock(dict_any_mock) + def test_stream_request_with_dict_mock(self): + r = requests.get('http://domain.com/', stream=True) + self.assertEqual(r.raw.read(), b'Hello from domain.com') + + @with_httmock(any_mock) + def test_non_stream_request(self): + r = requests.get('http://domain.com/') + self.assertEqual(r.raw.read(), b'') + + +class RememberCalledTest(unittest.TestCase): + + @staticmethod + def several_calls(count, method, *args, **kwargs): + results = [] + for _ in range(count): + results.append(method(*args, **kwargs)) + return results + + def test_several_calls(self): + with HTTMock(google_mock_count, facebook_mock_count): + results = self.several_calls( + 3, requests.get, 'http://facebook.com/') + + self.assertTrue(facebook_mock_count.call['called']) + self.assertEqual(facebook_mock_count.call['count'], 3) + + self.assertFalse(google_mock_count.call['called']) + self.assertEqual(google_mock_count.call['count'], 0) + + for r in results: + self.assertEqual(r.content, b'Hello from Facebook') + + # Negative case: cleanup call data + with HTTMock(facebook_mock_count): + results = self.several_calls( + 1, requests.get, 'http://facebook.com/') + + self.assertEqual(facebook_mock_count.call['count'], 1) + + @with_httmock(google_mock_count, facebook_mock_count) + def test_several_call_decorated(self): + results = self.several_calls(3, requests.get, 'http://facebook.com/') + + self.assertTrue(facebook_mock_count.call['called']) + self.assertEqual(facebook_mock_count.call['count'], 3) + + self.assertFalse(google_mock_count.call['called']) + self.assertEqual(google_mock_count.call['count'], 0) + + for r in results: + self.assertEqual(r.content, b'Hello from Facebook') + + self.several_calls(1, requests.get, 'http://facebook.com/') + self.assertEqual(facebook_mock_count.call['count'], 4) + + def test_store_several_requests(self): + with HTTMock(google_mock_store_requests): + payload = {"query": "foo"} + requests.post('http://google.com', data=payload) + + self.assertTrue(google_mock_store_requests.call['called']) + self.assertEqual(google_mock_store_requests.call['count'], 1) + request = google_mock_store_requests.call['requests'][0] + self.assertEqual(request.body, 'query=foo')