diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 14bfa86a..021eb0e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,10 +25,16 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: + - '3.12' + - '3.11' + - '3.10' + - '3.9' + - '3.8' + - '3.7' runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/Makefile b/Makefile index 3226a031..a2a80a17 100644 --- a/Makefile +++ b/Makefile @@ -124,7 +124,8 @@ test-dist: test-sdist test-bdist-wheel test-sdist: clean venv @echo $(H1)Testing sdist build an installation$(H1END) - $(VENV_PYTHON) setup.py sdist + $(VENV_PIP) install build + $(VENV_PYTHON) -m build --sdist $(VENV_PIP) install --force-reinstall --upgrade dist/*.gz $(VENV_BIN)/http --version @echo @@ -132,8 +133,8 @@ test-sdist: clean venv test-bdist-wheel: clean venv @echo $(H1)Testing wheel build an installation$(H1END) - $(VENV_PIP) install wheel - $(VENV_PYTHON) setup.py bdist_wheel + $(VENV_PIP) install build + $(VENV_PYTHON) -m build --wheel $(VENV_PIP) install --force-reinstall --upgrade dist/*.whl $(VENV_BIN)/http --version @echo diff --git a/README.md b/README.md index 42a81e39..13a3503b 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@
-[![HTTPie for Desktop](https://img.shields.io/static/v1?label=HTTPie&message=for%20Desktop&color=4B78E6)](https://httpie.io/product) -[![](https://img.shields.io/static/v1?label=HTTPie&message=for%20Web%20%26%20Mobile&color=73DC8C)](https://httpie.io/app) -[![](https://img.shields.io/static/v1?label=HTTPie&message=for%20Terminal&color=FA9BFA)](https://httpie.io/cli) +[![HTTPie for Desktop](https://img.shields.io/static/v1?label=HTTPie&message=Desktop&color=4B78E6)](https://httpie.io/product) +[![](https://img.shields.io/static/v1?label=HTTPie&message=Web%20%26%20Mobile&color=73DC8C)](https://httpie.io/app) +[![](https://img.shields.io/static/v1?label=HTTPie&message=CLI&color=FA9BFA)](https://httpie.io/cli) [![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie) [![Chat](https://img.shields.io/discord/725351238698270761?style=flat&label=Chat%20on%20Discord&color=%23FA9BFA)](https://httpie.io/discord) @@ -23,6 +23,7 @@ [![Latest version](https://img.shields.io/pypi/v/httpie.svg?style=flat&label=Latest&color=%234B78E6&logo=&logoColor=white)](https://pypi.python.org/pypi/httpie) [![Build](https://img.shields.io/github/actions/workflow/status/httpie/cli/tests.yml?branch=master&color=%23FA9BFA&label=Build)](https://github.com/httpie/cli/actions) [![Coverage](https://img.shields.io/codecov/c/github/httpie/cli?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/cli) +[![PyPi downloads](https://img.shields.io/pepy/dt/httpie?style=flat&label=Downloads%20from%20PyPi%20only&color=4B78E6)](https://www.pepy.tech/projects/httpie)
@@ -82,7 +83,7 @@ Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https:/ http PUT pie.dev/put X-API-Token:123 name=John ``` -Build and print a request without sending it using [offline mode](https://httpie.io/docs#offline-mode): +Build and print a request without sending it using [offline mode](https://httpie.io/docs/cli/offline-mode): ```bash http --offline pie.dev/post hello=offline diff --git a/docs/README.md b/docs/README.md index 0ffa1101..7c458bc2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -133,7 +133,7 @@ Also works for other Debian-derived distributions like MX Linux, Linux Mint, dee ```bash # Install httpie $ curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg -$ sudo echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" > /etc/apt/sources.list.d/httpie.list +$ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null $ sudo apt update $ sudo apt install httpie ``` @@ -477,7 +477,7 @@ $ http pie.dev/get text==@files/text.txt ### URL shortcuts for `localhost` Additionally, curl-like shorthand for localhost is supported. -This means that, for example, `:3000` would expand to `http://localhost:3000` +This means that, for example, `:3000` would expand to `http://localhost:3000`. If the port is omitted, then port 80 is assumed. ```bash @@ -530,7 +530,7 @@ $ http-unix %2Fvar%2Frun%2Fdocker.sock/info ### `--path-as-is` -The standard behavior of HTTP clients is to normalize the path portion of URLs by squashing dot segments as a typically filesystem would: +The standard behavior of HTTP clients is to normalize the path portion of URLs by squashing dot segments as a typical filesystem would: ```bash $ http -v example.org/./../../etc/password @@ -583,7 +583,7 @@ Note that the structured data fields aren’t the only way to specify request da ### File based separators Using file contents as values for specific fields is a very common use case, which can be achieved through adding the `@` suffix to -the operators above. For example instead of using a static string as the value for some header, you can use `:@` operator +the operators above. For example, instead of using a static string as the value for some header, you can use `:@` operator to pass the desired value from a file. ```bash @@ -749,7 +749,7 @@ $ http --offline --print=B pie.dev/post \ In the example above, the `search[type]` is an instruction for creating an object called `search`, and setting the `type` field of it to the given value (`"id"`). -Also note that, just as the regular syntax, you can use the `:=` operator to directly pass raw JSON values (e.g, numbers in the case above). +Also note that, just as the regular syntax, you can use the `:=` operator to directly pass raw JSON values (e.g., numbers in the case above). ```json { @@ -1235,7 +1235,7 @@ by individual commands when sending a request instead of being joined together. ### Limiting response headers -The `--max-headers=n` options allows you to control the number of headers HTTPie reads before giving up (the default `0`, i.e., there’s no limit). +The `--max-headers=n` option allows you to control the number of headers HTTPie reads before giving up (the default `0`, i.e., there’s no limit). ```bash $ http --max-headers=100 pie.dev/get diff --git a/docs/installation/methods.yml b/docs/installation/methods.yml index 1cc09762..290b0e24 100644 --- a/docs/installation/methods.yml +++ b/docs/installation/methods.yml @@ -39,7 +39,7 @@ tools: install: - curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg # - curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list - - sudo echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" > /etc/apt/sources.list.d/httpie.list + - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null - sudo apt update - sudo apt install httpie upgrade: diff --git a/httpie/internal/update_warnings.py b/httpie/internal/update_warnings.py index c684bb80..cc698660 100644 --- a/httpie/internal/update_warnings.py +++ b/httpie/internal/update_warnings.py @@ -41,6 +41,8 @@ def _fetch_updates(env: Environment) -> str: file = env.config.version_info_file data = _read_data_error_free(file) + print("fetch update...?") + response = niquests.get(PACKAGE_INDEX_LINK, verify=False) response.raise_for_status() diff --git a/setup.cfg b/setup.cfg index 67b12ad0..d195027e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,8 +22,97 @@ filterwarnings = ignore:Passing msg=\.\. is deprecated:DeprecationWarning ignore:Unverified HTTPS request is being made to host:urllib3.exceptions.InsecureRequestWarning +[metadata] +name = httpie +version = attr: httpie.__version__ +author = Jakub Roztocil +author_email = jakub@roztocil.co +license = BSD +description = HTTPie: modern, user-friendly command-line HTTP client for the API era. +url = https://httpie.io/ +long_description = file: README.md +long_description_content_type = text/markdown +classifiers = + Development Status :: 5 - Production/Stable + Programming Language :: Python + Programming Language :: Python :: 3 :: Only + Environment :: Console + Intended Audience :: Developers + Intended Audience :: System Administrators + License :: OSI Approved :: BSD License + Topic :: Internet :: WWW/HTTP + Topic :: Software Development + Topic :: System :: Networking + Topic :: Terminals + Topic :: Text Processing + Topic :: Utilities +project_urls = + GitHub = https://github.com/httpie/cli + Twitter = https://twitter.com/httpie + Discord = https://httpie.io/discord + Documentation = https://httpie.io/docs + Online Demo = https://httpie.io/run + + +[options] +packages = find: +install_requires = + pip + charset_normalizer>=2.0.0 + defusedxml>=0.6.0 + niquests[socks]>=3 + Pygments>=2.5.2 + setuptools + importlib-metadata>=1.4.0; python_version<"3.8" + rich>=9.10.0 + colorama>=0.2.4; sys_platform=="win32" +python_requires = >=3.7 + + [flake8] # # E501 - line too long # W503 - line break before binary operator ignore = E501,W503 + +[options.packages.find] +include = + httpie + httpie.* + +[options.entry_points] +console_scripts = + http = httpie.__main__:main + https = httpie.__main__:main + httpie = httpie.manager.__main__:main + +[options.extras_require] +dev = + pytest + pytest-httpbin>=0.0.6 + responses + pytest-mock + werkzeug<2.1.0 + flake8 + flake8-comprehensions + flake8-deprecated + flake8-mutable + flake8-tuple + pyopenssl + pytest-cov + pyyaml + twine + wheel + Jinja2 +test = + pytest + pytest-httpbin>=0.0.6 + responses + pytest-mock + werkzeug<2.1.0 + +[options.data_files] +share/man/man1 = + extras/man/http.1 + extras/man/https.1 + extras/man/httpie.1 diff --git a/setup.py b/setup.py index cd18032f..60684932 100644 --- a/setup.py +++ b/setup.py @@ -1,120 +1,3 @@ -# This is purely the result of trial and error. +from setuptools import setup -import sys - -from setuptools import setup, find_packages - -import httpie - - -# Note: keep requirements here to ease distributions packaging -tests_require = [ - 'pytest<8', - 'pytest-httpbin>=0.0.6', - 'pytest-lazy-fixture>=0.0.6', - 'responses', - 'pytest-mock', - 'werkzeug<2.1.0', - 'flaky', -] -dev_require = [ - *tests_require, - 'flake8', - 'flake8-comprehensions', - 'flake8-deprecated', - 'flake8-mutable', - 'flake8-tuple', - 'pytest-cov', - 'pyyaml', - 'twine', - 'wheel', - 'Jinja2' -] -install_requires = [ - 'pip', - 'charset_normalizer>=2.0.0', - 'defusedxml>=0.6.0', - 'niquests[socks]>=3.4.0,<4', - 'Pygments>=2.5.2', - 'setuptools', - 'importlib-metadata>=1.4.0; python_version < "3.8"', - 'rich>=9.10.0', -] -install_requires_win_only = [ - 'colorama>=0.2.4', -] - -# Conditional dependencies: - -# sdist -if 'bdist_wheel' not in sys.argv: - - if 'win32' in str(sys.platform).lower(): - # Terminal colors for Windows - install_requires.extend(install_requires_win_only) - - -# bdist_wheel -extras_require = { - 'dev': dev_require, - 'test': tests_require, - # https://wheel.readthedocs.io/en/latest/#defining-conditional-dependencies - ':sys_platform == "win32"': install_requires_win_only, -} - - -def long_description(): - with open('README.md', encoding='utf-8') as f: - return f.read() - - -setup( - name='httpie', - version=httpie.__version__, - description=httpie.__doc__.strip(), - long_description=long_description(), - long_description_content_type='text/markdown', - url='https://httpie.io/', - download_url=f'https://github.com/httpie/cli/archive/{httpie.__version__}.tar.gz', - author=httpie.__author__, - author_email='jakub@roztocil.co', - license=httpie.__licence__, - packages=find_packages(include=['httpie', 'httpie.*']), - entry_points={ - 'console_scripts': [ - 'http = httpie.__main__:main', - 'https = httpie.__main__:main', - 'httpie = httpie.manager.__main__:main', - ], - }, - python_requires='>=3.7', - extras_require=extras_require, - install_requires=install_requires, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3 :: Only', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: BSD License', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development', - 'Topic :: System :: Networking', - 'Topic :: Terminals', - 'Topic :: Text Processing', - 'Topic :: Utilities' - ], - project_urls={ - 'GitHub': 'https://github.com/httpie/cli', - 'Twitter': 'https://twitter.com/httpie', - 'Discord': 'https://httpie.io/discord', - 'Documentation': 'https://httpie.io/docs', - 'Online Demo': 'https://httpie.io/run', - }, - data_files=[ - ('share/man/man1', ['extras/man/http.1']), - ('share/man/man1', ['extras/man/https.1']), - ('share/man/man1', ['extras/man/httpie.1']), - ] -) +setup() diff --git a/tests/conftest.py b/tests/conftest.py index fa8642ed..ec332faf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,14 +2,15 @@ import socket import pytest from pytest_httpbin import certs +from pytest_httpbin.serve import Server as PyTestHttpBinServer -from .utils import ( # noqa +from .utils import ( # noqa HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN, HTTPBIN_WITH_CHUNKED_SUPPORT, REMOTE_HTTPBIN_DOMAIN, mock_env ) -from .utils.plugins_cli import ( # noqa +from .utils.plugins_cli import ( # noqa broken_plugin, dummy_plugin, dummy_plugins, @@ -17,7 +18,11 @@ from .utils.plugins_cli import ( # noqa httpie_plugins_success, interface, ) -from .utils.http_server import http_server, localhost_http_server # noqa +from .utils.http_server import http_server, localhost_http_server # noqa + + +# Patch to support `url = str(server)` in addition to `url = server + '/foo'`. +PyTestHttpBinServer.__str__ = lambda self: self.url from sys import modules diff --git a/tests/test_auth.py b/tests/test_auth.py index 3f9b742c..8a11554b 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -42,7 +42,7 @@ def test_bearer_auth(httpbin_both, token): new=lambda self, prompt: 'password') def test_password_prompt(httpbin): r = http('--auth', 'user', - 'GET', httpbin.url + '/basic-auth/user/password') + 'GET', httpbin + '/basic-auth/user/password') assert HTTP_OK in r assert r.json == {'authenticated': True, 'user': 'user'} diff --git a/tests/test_binary.py b/tests/test_binary.py index 9e5747ad..2203c71d 100644 --- a/tests/test_binary.py +++ b/tests/test_binary.py @@ -15,18 +15,18 @@ class TestBinaryRequestData: stdin_isatty=False, stdout_isatty=False ) - r = http('--print=B', 'POST', httpbin.url + '/post', env=env) + r = http('--print=B', 'POST', httpbin + '/post', env=env) assert r == BIN_FILE_CONTENT def test_binary_file_path(self, httpbin): env = MockEnvironment(stdin_isatty=True, stdout_isatty=False) - r = http('--print=B', 'POST', httpbin.url + '/post', + r = http('--print=B', 'POST', httpbin + '/post', '@' + BIN_FILE_PATH_ARG, env=env) assert r == BIN_FILE_CONTENT def test_binary_file_form(self, httpbin): env = MockEnvironment(stdin_isatty=True, stdout_isatty=False) - r = http('--print=B', '--form', 'POST', httpbin.url + '/post', + r = http('--print=B', '--form', 'POST', httpbin + '/post', 'test@' + BIN_FILE_PATH_ARG, env=env) assert bytes(BIN_FILE_CONTENT) in bytes(r) diff --git a/tests/test_cli.py b/tests/test_cli.py index 1cbc46d7..b431c6f1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -154,17 +154,17 @@ class TestItemParsing: class TestQuerystring: def test_query_string_params_in_url(self, httpbin): - r = http('--print=Hhb', 'GET', httpbin.url + '/get?a=1&b=2') + r = http('--print=Hhb', 'GET', httpbin + '/get?a=1&b=2') path = '/get?a=1&b=2' - url = httpbin.url + path + url = httpbin + path assert HTTP_OK in r assert f'GET {path} HTTP/1.1' in r assert f'"url": "{url}"' in r def test_query_string_params_items(self, httpbin): - r = http('--print=Hhb', 'GET', httpbin.url + '/get', 'a==1') + r = http('--print=Hhb', 'GET', httpbin + '/get', 'a==1') path = '/get?a=1' - url = httpbin.url + path + url = httpbin + path assert HTTP_OK in r assert f'GET {path} HTTP/1.1' in r assert f'"url": "{url}"' in r @@ -172,9 +172,9 @@ class TestQuerystring: def test_query_string_params_in_url_and_items_with_duplicates(self, httpbin): r = http('--print=Hhb', 'GET', - httpbin.url + '/get?a=1&a=1', 'a==1', 'a==1') + httpbin + '/get?a=1&a=1', 'a==1', 'a==1') path = '/get?a=1&a=1&a=1&a=1' - url = httpbin.url + path + url = httpbin + path assert HTTP_OK in r assert f'GET {path} HTTP/1.1' in r assert f'"url": "{url}"' in r @@ -323,11 +323,11 @@ class TestArgumentParser: class TestNoOptions: def test_valid_no_options(self, httpbin): - r = http('--verbose', '--no-verbose', 'GET', httpbin.url + '/get') + r = http('--verbose', '--no-verbose', 'GET', httpbin + '/get') assert 'GET /get HTTP/1.1' not in r def test_invalid_no_options(self, httpbin): - r = http('--no-war', 'GET', httpbin.url + '/get', + r = http('--no-war', 'GET', httpbin + '/get', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR assert 'unrecognized arguments: --no-war' in r.stderr @@ -341,13 +341,13 @@ class TestStdin: stdin=StdinBytesIO(FILE_PATH.read_bytes()), stdin_isatty=False, ) - r = http('--ignore-stdin', '--verbose', httpbin.url + '/get', env=env) + r = http('--ignore-stdin', '--verbose', httpbin + '/get', env=env) assert HTTP_OK in r assert 'GET /get HTTP' in r, "Don't default to POST." assert FILE_CONTENT not in r, "Don't send stdin data." def test_ignore_stdin_cannot_prompt_password(self, httpbin): - r = http('--ignore-stdin', '--auth=no-password', httpbin.url + '/get', + r = http('--ignore-stdin', '--auth=no-password', httpbin + '/get', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR assert 'because --ignore-stdin' in r.stderr diff --git a/tests/test_compress.py b/tests/test_compress.py index 854a23e2..de5d8a7c 100644 --- a/tests/test_compress.py +++ b/tests/test_compress.py @@ -29,14 +29,14 @@ def assert_decompressed_equal(base64_compressed_data, expected_str): def test_cannot_combine_compress_with_chunked(httpbin): - r = http('--compress', '--chunked', httpbin.url + '/get', + r = http('--compress', '--chunked', httpbin + '/get', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR assert 'cannot combine --compress and --chunked' in r.stderr def test_cannot_combine_compress_with_multipart(httpbin): - r = http('--compress', '--multipart', httpbin.url + '/get', + r = http('--compress', '--multipart', httpbin + '/get', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR assert 'cannot combine --compress and --multipart' in r.stderr diff --git a/tests/test_config.py b/tests/test_config.py index be19d572..1d2eea07 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -17,7 +17,7 @@ def test_default_options(httpbin): env = MockEnvironment() env.config['default_options'] = ['--form'] env.config.save() - r = http(httpbin.url + '/post', 'foo=bar', env=env) + r = http(httpbin + '/post', 'foo=bar', env=env) assert r.json['form'] == { "foo": "bar" } @@ -51,7 +51,7 @@ def test_default_options_overwrite(httpbin): env = MockEnvironment() env.config['default_options'] = ['--form'] env.config.save() - r = http('--json', httpbin.url + '/post', 'foo=bar', env=env) + r = http('--json', httpbin + '/post', 'foo=bar', env=env) assert r.json['json'] == { "foo": "bar" } diff --git a/tests/test_cookie_on_redirects.py b/tests/test_cookie_on_redirects.py index 23d8324f..2b0ab73b 100644 --- a/tests/test_cookie_on_redirects.py +++ b/tests/test_cookie_on_redirects.py @@ -2,54 +2,47 @@ import pytest from .utils import http -def _stringify(fixture): - return fixture + '' - - -@pytest.mark.parametrize('instance', [ - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), +@pytest.mark.parametrize('target_httpbin', [ + 'httpbin', + 'remote_httpbin', ]) -def test_explicit_user_set_cookie(httpbin, instance): - # User set cookies ARE NOT persisted within redirects - # when there is no session, even on the same domain. - +def test_explicit_user_set_cookie(httpbin, target_httpbin, request): + """User set cookies ARE NOT persisted within redirects when there is no session, even on the same domain.""" + target_httpbin = request.getfixturevalue(target_httpbin) r = http( '--follow', httpbin + '/redirect-to', - f'url=={_stringify(instance)}/cookies', + f'url=={target_httpbin}/cookies', 'Cookie:a=b' ) assert r.json == {'cookies': {}} -@pytest.mark.parametrize('instance', [ - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), +@pytest.mark.parametrize('target_httpbin', [ + 'httpbin', + 'remote_httpbin', ]) -def test_explicit_user_set_cookie_in_session(tmp_path, httpbin, instance): - # User set cookies ARE persisted within redirects - # when there is A session, even on the same domain. - +def test_explicit_user_set_cookie_in_session(tmp_path, httpbin, target_httpbin, request): + """User set cookies ARE persisted within redirects when there is A session, even on the same domain.""" + target_httpbin = request.getfixturevalue(target_httpbin) r = http( '--follow', '--session', str(tmp_path / 'session.json'), httpbin + '/redirect-to', - f'url=={_stringify(instance)}/cookies', + f'url=={target_httpbin}/cookies', 'Cookie:a=b' ) assert r.json == {'cookies': {'a': 'b'}} -@pytest.mark.parametrize('instance', [ - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), +@pytest.mark.parametrize('target_httpbin', [ + 'httpbin', + 'remote_httpbin', ]) -def test_saved_user_set_cookie_in_session(tmp_path, httpbin, instance): - # User set cookies ARE persisted within redirects - # when there is A session, even on the same domain. - +def test_saved_user_set_cookie_in_session(tmp_path, httpbin, target_httpbin, request): + """User set cookies ARE persisted within redirects when there is A session, even on the same domain.""" + target_httpbin = request.getfixturevalue(target_httpbin) http( '--follow', '--session', @@ -62,32 +55,33 @@ def test_saved_user_set_cookie_in_session(tmp_path, httpbin, instance): '--session', str(tmp_path / 'session.json'), httpbin + '/redirect-to', - f'url=={_stringify(instance)}/cookies', + f'url=={target_httpbin}/cookies', ) assert r.json == {'cookies': {'a': 'b'}} -@pytest.mark.parametrize('instance', [ - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), +@pytest.mark.parametrize('target_httpbin', [ + 'httpbin', + 'remote_httpbin', ]) @pytest.mark.parametrize('session', [True, False]) -def test_explicit_user_set_headers(httpbin, tmp_path, instance, session): - # User set headers ARE persisted within redirects - # even on different domains domain with or without - # an active session. +def test_explicit_user_set_headers(httpbin, tmp_path, target_httpbin, session, request): + """ + User set headers ARE persisted within redirects even on different domains domain with or without an active session. + + """ + target_httpbin = request.getfixturevalue(target_httpbin) session_args = [] if session: session_args.extend([ '--session', str(tmp_path / 'session.json') ]) - r = http( '--follow', *session_args, httpbin + '/redirect-to', - f'url=={_stringify(instance)}/get', + f'url=={target_httpbin}/get', 'X-Custom-Header:value' ) assert 'X-Custom-Header' in r.json['headers'] @@ -95,16 +89,13 @@ def test_explicit_user_set_headers(httpbin, tmp_path, instance, session): @pytest.mark.parametrize('session', [True, False]) def test_server_set_cookie_on_redirect_same_domain(tmp_path, httpbin, session): - # Server set cookies ARE persisted on the same domain - # when they are forwarded. - + """Server set cookies ARE persisted on the same domain when they are forwarded.""" session_args = [] if session: session_args.extend([ '--session', str(tmp_path / 'session.json') ]) - r = http( '--follow', *session_args, @@ -136,8 +127,7 @@ def test_server_set_cookie_on_redirect_different_domain(tmp_path, http_server, h def test_saved_session_cookies_on_same_domain(tmp_path, httpbin): - # Saved session cookies ARE persisted when making a new - # request to the same domain. + """Saved session cookies ARE persisted when making a new request to the same domain.""" http( '--session', str(tmp_path / 'session.json'), @@ -152,8 +142,7 @@ def test_saved_session_cookies_on_same_domain(tmp_path, httpbin): def test_saved_session_cookies_on_different_domain(tmp_path, httpbin, remote_httpbin): - # Saved session cookies ARE persisted when making a new - # request to a different domain. + """Saved session cookies ARE persisted when making a new request to a different domain.""" http( '--session', str(tmp_path / 'session.json'), @@ -167,45 +156,49 @@ def test_saved_session_cookies_on_different_domain(tmp_path, httpbin, remote_htt assert r.json == {'cookies': {}} -@pytest.mark.parametrize('initial_domain, first_request_domain, second_request_domain, expect_cookies', [ +@pytest.mark.parametrize(['initial_domain', 'first_request_domain', 'second_request_domain', 'expect_cookies'], [ ( # Cookies are set by Domain A # Initial domain is Domain A # Redirected domain is Domain A - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('httpbin'), + 'httpbin', + 'httpbin', + 'httpbin', True, ), ( # Cookies are set by Domain A # Initial domain is Domain B # Redirected domain is Domain B - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), - pytest.lazy_fixture('remote_httpbin'), + 'httpbin', + 'remote_httpbin', + 'remote_httpbin', False, ), ( # Cookies are set by Domain A # Initial domain is Domain A # Redirected domain is Domain B - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), + 'httpbin', + 'httpbin', + 'remote_httpbin', False, ), ( # Cookies are set by Domain A # Initial domain is Domain B # Redirected domain is Domain A - pytest.lazy_fixture('httpbin'), - pytest.lazy_fixture('remote_httpbin'), - pytest.lazy_fixture('httpbin'), + 'httpbin', + 'remote_httpbin', + 'httpbin', True, ), ]) -def test_saved_session_cookies_on_redirect(tmp_path, initial_domain, first_request_domain, second_request_domain, expect_cookies): +def test_saved_session_cookies_on_redirect( + tmp_path, initial_domain, first_request_domain, second_request_domain, expect_cookies, request): + initial_domain = request.getfixturevalue(initial_domain) + first_request_domain = request.getfixturevalue(first_request_domain) + second_request_domain = request.getfixturevalue(second_request_domain) http( '--session', str(tmp_path / 'session.json'), @@ -216,7 +209,7 @@ def test_saved_session_cookies_on_redirect(tmp_path, initial_domain, first_reque str(tmp_path / 'session.json'), '--follow', first_request_domain + '/redirect-to', - f'url=={_stringify(second_request_domain)}/cookies' + f'url=={second_request_domain}/cookies' ) if expect_cookies: expected_data = {'cookies': {'a': 'b'}} diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 267be2ae..dc912116 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -16,7 +16,7 @@ def test_default_headers_case_insensitive(httpbin): r = http( '--debug', '--print=H', - httpbin.url + '/post', + httpbin + '/post', 'CONTENT-TYPE:application/json-patch+json', 'a=b', ) @@ -27,26 +27,26 @@ def test_default_headers_case_insensitive(httpbin): # noinspection PyPep8Naming class TestImplicitHTTPMethod: def test_implicit_GET(self, httpbin): - r = http(httpbin.url + '/get') + r = http(httpbin + '/get') assert HTTP_OK in r def test_implicit_GET_with_headers(self, httpbin): - r = http(httpbin.url + '/headers', 'Foo:bar') + r = http(httpbin + '/headers', 'Foo:bar') assert HTTP_OK in r assert r.json['headers']['Foo'] == 'bar' def test_implicit_POST_json(self, httpbin): - r = http(httpbin.url + '/post', 'hello=world') + r = http(httpbin + '/post', 'hello=world') assert HTTP_OK in r assert r.json['json'] == {'hello': 'world'} def test_implicit_POST_form(self, httpbin): - r = http('--form', httpbin.url + '/post', 'foo=bar') + r = http('--form', httpbin + '/post', 'foo=bar') assert HTTP_OK in r assert r.json['form'] == {'foo': 'bar'} def test_implicit_POST_raw(self, httpbin): - r = http('--raw', 'foo bar', httpbin.url + '/post') + r = http('--raw', 'foo bar', httpbin + '/post') assert HTTP_OK in r assert r.json['data'] == 'foo bar' @@ -55,7 +55,7 @@ class TestImplicitHTTPMethod: stdin_isatty=False, stdin=BytesIO(FILE_PATH.read_bytes()) ) - r = http('--form', httpbin.url + '/post', env=env) + r = http('--form', httpbin + '/post', env=env) assert HTTP_OK in r @@ -69,33 +69,33 @@ class TestAutoContentTypeAndAcceptHeaders: def test_GET_no_data_no_auto_headers(self, httpbin): # https://github.com/httpie/cli/issues/62 - r = http('GET', httpbin.url + '/headers') + r = http('GET', httpbin + '/headers') assert HTTP_OK in r assert r.json['headers']['Accept'] == '*/*' assert 'Content-Type' not in r.json['headers'] def test_POST_no_data_no_auto_headers(self, httpbin): # JSON headers shouldn't be automatically set for POST with no data. - r = http('POST', httpbin.url + '/post') + r = http('POST', httpbin + '/post') assert HTTP_OK in r assert '"Accept": "*/*"' in r assert '"Content-Type": "application/json' not in r def test_POST_with_data_auto_JSON_headers(self, httpbin): - r = http('POST', httpbin.url + '/post', 'a=b') + r = http('POST', httpbin + '/post', 'a=b') assert HTTP_OK in r assert r.json['headers']['Accept'] == JSON_ACCEPT assert r.json['headers']['Content-Type'] == 'application/json' def test_GET_with_data_auto_JSON_headers(self, httpbin): # JSON headers should automatically be set also for GET with data. - r = http('POST', httpbin.url + '/post', 'a=b') + r = http('POST', httpbin + '/post', 'a=b') assert HTTP_OK in r assert r.json['headers']['Accept'] == JSON_ACCEPT assert r.json['headers']['Content-Type'] == 'application/json' def test_POST_explicit_JSON_JSON_ACCEPT(self, httpbin): - r = http('--json', 'POST', httpbin.url + '/post') + r = http('--json', 'POST', httpbin + '/post') assert HTTP_OK in r assert r.json['headers']['Accept'] == JSON_ACCEPT # Make sure Content-Type gets set even with no data. @@ -103,7 +103,7 @@ class TestAutoContentTypeAndAcceptHeaders: assert 'application/json' in r.json['headers']['Content-Type'] def test_GET_explicit_JSON_explicit_headers(self, httpbin): - r = http('--json', 'GET', httpbin.url + '/headers', + r = http('--json', 'GET', httpbin + '/headers', 'Accept:application/xml', 'Content-Type:application/xml') assert HTTP_OK in r @@ -111,22 +111,22 @@ class TestAutoContentTypeAndAcceptHeaders: assert '"Content-Type": "application/xml"' in r def test_POST_form_auto_Content_Type(self, httpbin): - r = http('--form', 'POST', httpbin.url + '/post') + r = http('--form', 'POST', httpbin + '/post') assert HTTP_OK in r assert '"Content-Type": "application/x-www-form-urlencoded' in r def test_POST_form_Content_Type_override(self, httpbin): - r = http('--form', 'POST', httpbin.url + '/post', + r = http('--form', 'POST', httpbin + '/post', 'Content-Type:application/xml') assert HTTP_OK in r assert '"Content-Type": "application/xml"' in r def test_print_only_body_when_stdout_redirected_by_default(self, httpbin): env = MockEnvironment(stdin_isatty=True, stdout_isatty=False) - r = http('GET', httpbin.url + '/get', env=env) + r = http('GET', httpbin + '/get', env=env) assert 'HTTP/' not in r def test_print_overridable_when_stdout_redirected(self, httpbin): env = MockEnvironment(stdin_isatty=True, stdout_isatty=False) - r = http('--print=h', 'GET', httpbin.url + '/get', env=env) + r = http('--print=h', 'GET', httpbin + '/get', env=env) assert HTTP_OK in r diff --git a/tests/test_downloads.py b/tests/test_downloads.py index 180e702d..6bd8dcc6 100644 --- a/tests/test_downloads.py +++ b/tests/test_downloads.py @@ -255,7 +255,7 @@ class TestDownloads: os.chdir(tmp_dirname) try: assert os.listdir('.') == [] - http('--download', httpbin.url + '/redirect/1') + http('--download', httpbin + '/redirect/1') assert os.listdir('.') == [expected_filename] finally: os.chdir(orig_cwd) diff --git a/tests/test_encoding.py b/tests/test_encoding.py index b16de3c8..5afcc23f 100644 --- a/tests/test_encoding.py +++ b/tests/test_encoding.py @@ -31,90 +31,90 @@ def test_charset_text_pairs(): def test_unicode_headers(httpbin): # httpbin doesn't interpret UFT-8 headers - r = http(httpbin.url + '/headers', f'Test:{UNICODE}') + r = http(httpbin + '/headers', f'Test:{UNICODE}') assert HTTP_OK in r def test_unicode_headers_verbose(httpbin): # httpbin doesn't interpret UTF-8 headers - r = http('--verbose', httpbin.url + '/headers', f'Test:{UNICODE}') + r = http('--verbose', httpbin + '/headers', f'Test:{UNICODE}') assert HTTP_OK in r assert UNICODE in r def test_unicode_raw(httpbin): - r = http('--raw', f'test {UNICODE}', 'POST', httpbin.url + '/post') + r = http('--raw', f'test {UNICODE}', 'POST', httpbin + '/post') assert HTTP_OK in r assert r.json['data'] == f'test {UNICODE}' def test_unicode_raw_verbose(httpbin): r = http('--verbose', '--raw', f'test {UNICODE}', - 'POST', httpbin.url + '/post') + 'POST', httpbin + '/post') assert HTTP_OK in r assert UNICODE in r def test_unicode_form_item(httpbin): - r = http('--form', 'POST', httpbin.url + '/post', f'test={UNICODE}') + r = http('--form', 'POST', httpbin + '/post', f'test={UNICODE}') assert HTTP_OK in r assert r.json['form'] == {'test': UNICODE} def test_unicode_form_item_verbose(httpbin): r = http('--verbose', '--form', - 'POST', httpbin.url + '/post', f'test={UNICODE}') + 'POST', httpbin + '/post', f'test={UNICODE}') assert HTTP_OK in r assert UNICODE in r def test_unicode_json_item(httpbin): - r = http('--json', 'POST', httpbin.url + '/post', f'test={UNICODE}') + r = http('--json', 'POST', httpbin + '/post', f'test={UNICODE}') assert HTTP_OK in r assert r.json['json'] == {'test': UNICODE} def test_unicode_json_item_verbose(httpbin): r = http('--verbose', '--json', - 'POST', httpbin.url + '/post', f'test={UNICODE}') + 'POST', httpbin + '/post', f'test={UNICODE}') assert HTTP_OK in r assert UNICODE in r def test_unicode_raw_json_item(httpbin): - r = http('--json', 'POST', httpbin.url + '/post', + r = http('--json', 'POST', httpbin + '/post', f'test:={{ "{UNICODE}" : [ "{UNICODE}" ] }}') assert HTTP_OK in r assert r.json['json'] == {'test': {UNICODE: [UNICODE]}} def test_unicode_raw_json_item_verbose(httpbin): - r = http('--json', 'POST', httpbin.url + '/post', + r = http('--json', 'POST', httpbin + '/post', f'test:={{ "{UNICODE}" : [ "{UNICODE}" ] }}') assert HTTP_OK in r assert r.json['json'] == {'test': {UNICODE: [UNICODE]}} def test_unicode_url_query_arg_item(httpbin): - r = http(httpbin.url + '/get', f'test=={UNICODE}') + r = http(httpbin + '/get', f'test=={UNICODE}') assert HTTP_OK in r assert r.json['args'] == {'test': UNICODE}, r def test_unicode_url_query_arg_item_verbose(httpbin): - r = http('--verbose', httpbin.url + '/get', f'test=={UNICODE}') + r = http('--verbose', httpbin + '/get', f'test=={UNICODE}') assert HTTP_OK in r assert UNICODE in r def test_unicode_url(httpbin): - r = http(f'{httpbin.url}/get?test={UNICODE}') + r = http(f'{httpbin}/get?test={UNICODE}') assert HTTP_OK in r assert r.json['args'] == {'test': UNICODE} def test_unicode_url_verbose(httpbin): - r = http('--verbose', f'{httpbin.url}/get?test={UNICODE}') + r = http('--verbose', f'{httpbin}/get?test={UNICODE}') assert HTTP_OK in r assert r.json['args'] == {'test': UNICODE} @@ -123,7 +123,7 @@ def test_unicode_basic_auth(httpbin): # it doesn't really authenticate us because httpbin # doesn't interpret the UTF-8-encoded auth http('--verbose', '--auth', f'test:{UNICODE}', - f'{httpbin.url}/basic-auth/test/{UNICODE}') + f'{httpbin}/basic-auth/test/{UNICODE}') def test_unicode_digest_auth(httpbin): @@ -131,7 +131,7 @@ def test_unicode_digest_auth(httpbin): # doesn't interpret the UTF-8-encoded auth http('--auth-type=digest', '--auth', f'test:{UNICODE}', - f'{httpbin.url}/digest-auth/auth/test/{UNICODE}') + f'{httpbin}/digest-auth/auth/test/{UNICODE}') @pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) diff --git a/tests/test_exit_status.py b/tests/test_exit_status.py index 97f071da..ec884914 100644 --- a/tests/test_exit_status.py +++ b/tests/test_exit_status.py @@ -7,25 +7,25 @@ from .utils import MockEnvironment, http, HTTP_OK def test_keyboard_interrupt_during_arg_parsing_exit_status(httpbin): with mock.patch('httpie.cli.definition.parser.parse_args', side_effect=KeyboardInterrupt()): - r = http('GET', httpbin.url + '/get', tolerate_error_exit_status=True) + r = http('GET', httpbin + '/get', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR_CTRL_C def test_keyboard_interrupt_in_program_exit_status(httpbin): with mock.patch('httpie.core.program', side_effect=KeyboardInterrupt()): - r = http('GET', httpbin.url + '/get', tolerate_error_exit_status=True) + r = http('GET', httpbin + '/get', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR_CTRL_C def test_ok_response_exits_0(httpbin): - r = http('GET', httpbin.url + '/get') + r = http('GET', httpbin + '/get') assert HTTP_OK in r assert r.exit_status == ExitStatus.SUCCESS def test_error_response_exits_0_without_check_status(httpbin): - r = http('GET', httpbin.url + '/status/500') + r = http('GET', httpbin + '/status/500') assert '500 Internal Server Error' in r assert r.exit_status == ExitStatus.SUCCESS assert not r.stderr @@ -33,7 +33,7 @@ def test_error_response_exits_0_without_check_status(httpbin): def test_timeout_exit_status(httpbin): - r = http('--timeout=0.01', 'GET', httpbin.url + '/delay/0.5', + r = http('--timeout=0.01', 'GET', httpbin + '/delay/0.5', tolerate_error_exit_status=True) assert r.exit_status == ExitStatus.ERROR_TIMEOUT @@ -42,7 +42,7 @@ def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected( httpbin): env = MockEnvironment(stdout_isatty=False) r = http('--check-status', '--headers', - 'GET', httpbin.url + '/status/301', + 'GET', httpbin + '/status/301', env=env, tolerate_error_exit_status=True) assert '301 Moved Permanently' in r assert r.exit_status == ExitStatus.ERROR_HTTP_3XX @@ -51,7 +51,7 @@ def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected( def test_3xx_check_status_redirects_allowed_exits_0(httpbin): r = http('--check-status', '--follow', - 'GET', httpbin.url + '/status/301', + 'GET', httpbin + '/status/301', tolerate_error_exit_status=True) # The redirect will be followed so 200 is expected. assert HTTP_OK in r @@ -59,7 +59,7 @@ def test_3xx_check_status_redirects_allowed_exits_0(httpbin): def test_4xx_check_status_exits_4(httpbin): - r = http('--check-status', 'GET', httpbin.url + '/status/401', + r = http('--check-status', 'GET', httpbin + '/status/401', tolerate_error_exit_status=True) assert '401 Unauthorized' in r assert r.exit_status == ExitStatus.ERROR_HTTP_4XX @@ -68,7 +68,7 @@ def test_4xx_check_status_exits_4(httpbin): def test_5xx_check_status_exits_5(httpbin): - r = http('--check-status', 'GET', httpbin.url + '/status/500', + r = http('--check-status', 'GET', httpbin + '/status/500', tolerate_error_exit_status=True) assert '500 Internal Server Error' in r assert r.exit_status == ExitStatus.ERROR_HTTP_5XX diff --git a/tests/test_output.py b/tests/test_output.py index ac07bba7..1e01de4a 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -54,7 +54,7 @@ class TestQuietFlag: stdout_isatty=True, devnull=io.BytesIO() ) - r = http(*quiet_flags, 'GET', httpbin.url + '/get', env=env) + r = http(*quiet_flags, 'GET', httpbin + '/get', env=env) assert env.stdout is env.devnull assert env.stderr is env.devnull assert HTTP_OK in r.devnull @@ -138,7 +138,7 @@ class TestQuietFlag: ) r = http( *quiet_flags, '--auth', 'user', 'GET', - httpbin.url + '/basic-auth/user/password', + httpbin + '/basic-auth/user/password', env=env ) assert env.stdout is env.devnull @@ -151,7 +151,7 @@ class TestQuietFlag: @pytest.mark.parametrize('output_options', ['-h', '-b', '-v', '-p=hH']) def test_quiet_with_explicit_output_options(self, httpbin, quiet_flags, output_options): env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) - r = http(*quiet_flags, output_options, httpbin.url + '/get', env=env) + r = http(*quiet_flags, output_options, httpbin + '/get', env=env) assert env.stdout is env.devnull assert env.stderr is env.devnull assert r == '' @@ -192,26 +192,26 @@ class TestQuietFlag: class TestVerboseFlag: def test_verbose(self, httpbin): r = http('--verbose', - 'GET', httpbin.url + '/get', 'test-header:__test__') + 'GET', httpbin + '/get', 'test-header:__test__') assert HTTP_OK in r assert r.count('__test__') == 2 def test_verbose_raw(self, httpbin): r = http('--verbose', '--raw', 'foo bar', - 'POST', httpbin.url + '/post') + 'POST', httpbin + '/post') assert HTTP_OK in r assert 'foo bar' in r def test_verbose_form(self, httpbin): # https://github.com/httpie/cli/issues/53 - r = http('--verbose', '--form', 'POST', httpbin.url + '/post', + r = http('--verbose', '--form', 'POST', httpbin + '/post', 'A=B', 'C=D') assert HTTP_OK in r assert 'A=B&C=D' in r def test_verbose_json(self, httpbin): r = http('--verbose', - 'POST', httpbin.url + '/post', 'foo=bar', 'baz=bar') + 'POST', httpbin + '/post', 'foo=bar', 'baz=bar') assert HTTP_OK in r assert '"baz": "bar"' in r @@ -300,20 +300,20 @@ class TestPrettyOptions: def test_pretty_enabled_by_default(self, httpbin): env = MockEnvironment(colors=256) - r = http('GET', httpbin.url + '/get', env=env) + r = http('GET', httpbin + '/get', env=env) assert COLOR in r def test_pretty_enabled_by_default_unless_stdout_redirected(self, httpbin): - r = http('GET', httpbin.url + '/get') + r = http('GET', httpbin + '/get') assert COLOR not in r def test_force_pretty(self, httpbin): env = MockEnvironment(stdout_isatty=False, colors=256) - r = http('--pretty=all', 'GET', httpbin.url + '/get', env=env) + r = http('--pretty=all', 'GET', httpbin + '/get', env=env) assert COLOR in r def test_force_ugly(self, httpbin): - r = http('--pretty=none', 'GET', httpbin.url + '/get') + r = http('--pretty=none', 'GET', httpbin + '/get') assert COLOR not in r def test_subtype_based_pygments_lexer_match(self, httpbin): @@ -322,14 +322,14 @@ class TestPrettyOptions: """ env = MockEnvironment(colors=256) - r = http('--print=B', '--pretty=all', httpbin.url + '/post', + r = http('--print=B', '--pretty=all', httpbin + '/post', 'Content-Type:text/foo+json', 'a=b', env=env) assert COLOR in r def test_colors_option(self, httpbin): env = MockEnvironment(colors=256) r = http('--print=B', '--pretty=colors', - 'GET', httpbin.url + '/get', 'a=b', + 'GET', httpbin + '/get', 'a=b', env=env) # Tests that the JSON data isn't formatted. assert not r.strip().count('\n') @@ -338,7 +338,7 @@ class TestPrettyOptions: def test_format_option(self, httpbin): env = MockEnvironment(colors=256) r = http('--print=B', '--pretty=format', - 'GET', httpbin.url + '/get', 'a=b', + 'GET', httpbin + '/get', 'a=b', env=env) # Tests that the JSON data is formatted. assert r.strip().count('\n') == 2 @@ -365,25 +365,25 @@ class TestLineEndings: return body def test_CRLF_headers_only(self, httpbin): - r = http('--headers', 'GET', httpbin.url + '/get') + r = http('--headers', 'GET', httpbin + '/get') body = self._validate_crlf(r) assert not body, f'Garbage after headers: {r!r}' def test_CRLF_ugly_response(self, httpbin): - r = http('--pretty=none', 'GET', httpbin.url + '/get') + r = http('--pretty=none', 'GET', httpbin + '/get') self._validate_crlf(r) def test_CRLF_formatted_response(self, httpbin): - r = http('--pretty=format', 'GET', httpbin.url + '/get') + r = http('--pretty=format', 'GET', httpbin + '/get') assert r.exit_status == ExitStatus.SUCCESS self._validate_crlf(r) def test_CRLF_ugly_request(self, httpbin): - r = http('--pretty=none', '--print=HB', 'GET', httpbin.url + '/get') + r = http('--pretty=none', '--print=HB', 'GET', httpbin + '/get') self._validate_crlf(r) def test_CRLF_formatted_request(self, httpbin): - r = http('--pretty=format', '--print=HB', 'GET', httpbin.url + '/get') + r = http('--pretty=format', '--print=HB', 'GET', httpbin + '/get') self._validate_crlf(r) diff --git a/tests/test_redirects.py b/tests/test_redirects.py index 692bb8ef..44baf04c 100644 --- a/tests/test_redirects.py +++ b/tests/test_redirects.py @@ -13,7 +13,7 @@ REDIRECTS_WITH_METHOD_BODY_PRESERVED = [307, 308] def test_follow_all_redirects_shown(httpbin): - r = http('--follow', '--all', httpbin.url + '/redirect/2') + r = http('--follow', '--all', httpbin + '/redirect/2') assert r.count('HTTP/1.1') == 3 assert r.count('HTTP/1.1 302 Found', 2) assert HTTP_OK in r @@ -21,14 +21,14 @@ def test_follow_all_redirects_shown(httpbin): @pytest.mark.parametrize('follow_flag', ['--follow', '-F']) def test_follow_without_all_redirects_hidden(httpbin, follow_flag): - r = http(follow_flag, httpbin.url + '/redirect/2') + r = http(follow_flag, httpbin + '/redirect/2') assert r.count('HTTP/1.1') == 1 assert HTTP_OK in r @pytest.mark.xfail(True, reason="https://github.com/httpie/cli/issues/1082") def test_follow_output_options_used_for_redirects(httpbin): - r = http('--follow', '--print=H', httpbin.url + '/redirect/2') + r = http('--follow', '--print=H', httpbin + '/redirect/2') assert r.count('GET /') == 1 assert HTTP_OK not in r @@ -38,7 +38,7 @@ def test_follow_all_output_options_used_for_redirects(httpbin): '--follow', '--all', '--print=H', - httpbin.url + '/redirect/2') + httpbin + '/redirect/2') assert r.count('GET /') == 3 assert HTTP_OK not in r @@ -50,7 +50,7 @@ def test_follow_all_output_options_used_for_redirects(httpbin): # '--all', # '--print=h', # '--history-print=H', -# httpbin.url + '/redirect/2') +# httpbin + '/redirect/2') # assert r.count('GET /') == 2 # assert 'HTTP/1.1 302 FOUND' not in r # assert HTTP_OK in r @@ -61,7 +61,7 @@ def test_max_redirects(httpbin): r = http( '--max-redirects=1', '--follow', - httpbin.url + '/redirect/3', + httpbin + '/redirect/3', tolerate_error_exit_status=True, ) assert r.exit_status == ExitStatus.ERROR_TOO_MANY_REDIRECTS @@ -72,11 +72,11 @@ def test_max_redirects(httpbin): def test_follow_redirect_with_repost(httpbin, status_code): r = http( '--follow', - httpbin.url + '/redirect-to', + httpbin + '/redirect-to', 'A:A', 'A:B', 'B:B', - f'url=={httpbin.url}/post', + f'url=={httpbin}/post', f'status_code=={status_code}', '@' + FILE_PATH_ARG, ) @@ -92,11 +92,11 @@ def test_verbose_follow_redirect_with_repost(httpbin, status_code): r = http( '--follow', '--verbose', - httpbin.url + '/redirect-to', + httpbin + '/redirect-to', 'A:A', 'A:B', 'B:B', - f'url=={httpbin.url}/post', + f'url=={httpbin}/post', f'status_code=={status_code}', '@' + FILE_PATH_ARG, ) diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 7d7f3e66..a5cd00a0 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -12,7 +12,7 @@ def test_Host_header_overwrite(httpbin): """ host = 'pie.dev' - url = httpbin.url + '/get' + url = httpbin + '/get' r = http('--print=hH', url, f'host:{host}') assert HTTP_OK in r assert r.lower().count('host:') == 1 @@ -34,7 +34,7 @@ def test_verbose_redirected_stdout_separator(httpbin): """ r = http( '-v', - httpbin.url + '/post', + httpbin + '/post', 'a=b', env=MockEnvironment(stdout_isatty=False), ) diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 6bece489..aa524348 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -82,7 +82,7 @@ class TestSessionFlow(SessionTestBase): '--session=test', '--auth=username:password', 'GET', - httpbin.url + '/cookies/set?hello=world', + httpbin + '/cookies/set?hello=world', 'Hello:World', env=self.env() ) @@ -92,7 +92,7 @@ class TestSessionFlow(SessionTestBase): self.start_session(httpbin) # Verify that the session created in setup_method() has been used. r2 = http('--session=test', - 'GET', httpbin.url + '/get', env=self.env()) + 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r2 assert r2.json['headers']['Hello'] == 'World' assert r2.json['headers']['Cookie'] == 'hello=world' @@ -101,19 +101,19 @@ class TestSessionFlow(SessionTestBase): def test_session_update(self, httpbin): self.start_session(httpbin) # Get a response to a request from the original session. - r2 = http('--session=test', 'GET', httpbin.url + '/get', + r2 = http('--session=test', 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r2 # Make a request modifying the session data. r3 = http('--follow', '--session=test', '--auth=username:password2', - 'GET', httpbin.url + '/cookies/set?hello=world2', + 'GET', httpbin + '/cookies/set?hello=world2', 'Hello:World2', env=self.env()) assert HTTP_OK in r3 # Get a response to a request from the updated session. - r4 = http('--session=test', 'GET', httpbin.url + '/get', + r4 = http('--session=test', 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r4 assert r4.json['headers']['Hello'] == 'World2' @@ -124,7 +124,7 @@ class TestSessionFlow(SessionTestBase): def test_session_read_only(self, httpbin): self.start_session(httpbin) # Get a response from the original session. - r2 = http('--session=test', 'GET', httpbin.url + '/get', + r2 = http('--session=test', 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r2 @@ -132,12 +132,12 @@ class TestSessionFlow(SessionTestBase): # with --session-read-only. r3 = http('--follow', '--session-read-only=test', '--auth=username:password2', 'GET', - httpbin.url + '/cookies/set?hello=world2', 'Hello:World2', + httpbin + '/cookies/set?hello=world2', 'Hello:World2', env=self.env()) assert HTTP_OK in r3 # Get a response from the updated session. - r4 = http('--session=test', 'GET', httpbin.url + '/get', + r4 = http('--session=test', 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r4 @@ -151,17 +151,17 @@ class TestSessionFlow(SessionTestBase): def test_session_overwrite_header(self, httpbin): self.start_session(httpbin) - r2 = http('--session=test', 'GET', httpbin.url + '/get', + r2 = http('--session=test', 'GET', httpbin + '/get', 'Hello:World2', env=self.env()) assert HTTP_OK in r2 assert r2.json['headers']['Hello'] == 'World2' - r3 = http('--session=test', 'GET', httpbin.url + '/get', + r3 = http('--session=test', 'GET', httpbin + '/get', 'Hello:World2', 'Hello:World3', env=self.env()) assert HTTP_OK in r3 assert r3.json['headers']['Hello'] == 'World2,World3' - r3 = http('--session=test', 'GET', httpbin.url + '/get', + r3 = http('--session=test', 'GET', httpbin + '/get', 'Hello:', 'Hello:World3', env=self.env()) assert HTTP_OK in r3 assert 'Hello' not in r3.json['headers']['Hello'] @@ -172,12 +172,12 @@ class TestSession(SessionTestBase): def test_session_ignored_header_prefixes(self, httpbin): self.start_session(httpbin) - r1 = http('--session=test', 'GET', httpbin.url + '/get', + r1 = http('--session=test', 'GET', httpbin + '/get', 'Content-Type: text/plain', 'If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT', env=self.env()) assert HTTP_OK in r1 - r2 = http('--session=test', 'GET', httpbin.url + '/get', + r2 = http('--session=test', 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r2 assert 'Content-Type' not in r2.json['headers'] @@ -185,18 +185,18 @@ class TestSession(SessionTestBase): def test_session_with_upload(self, httpbin): self.start_session(httpbin) - r = http('--session=test', '--form', '--verbose', 'POST', httpbin.url + '/post', + r = http('--session=test', '--form', '--verbose', 'POST', httpbin + '/post', f'test-file@{FILE_PATH_ARG}', 'foo=bar', env=self.env()) assert HTTP_OK in r def test_session_by_path(self, httpbin): self.start_session(httpbin) session_path = self.config_dir / 'session-by-path.json' - r1 = http('--session', str(session_path), 'GET', httpbin.url + '/get', + r1 = http('--session', str(session_path), 'GET', httpbin + '/get', 'Foo:Bar', env=self.env()) assert HTTP_OK in r1 - r2 = http('--session', str(session_path), 'GET', httpbin.url + '/get', + r2 = http('--session', str(session_path), 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r2 assert r2.json['headers']['Foo'] == 'Bar' @@ -214,7 +214,7 @@ class TestSession(SessionTestBase): } session_path = self.config_dir / 'session-data.json' session_path.write_text(json.dumps(session_data)) - r = http('--session', str(session_path), 'GET', httpbin.url + '/get', + r = http('--session', str(session_path), 'GET', httpbin + '/get', env=self.env()) assert HTTP_OK in r assert 'Zzz' in r @@ -223,12 +223,12 @@ class TestSession(SessionTestBase): self.start_session(httpbin) r1 = http('--session=test', f'--auth=test:{UNICODE}', - 'GET', httpbin.url + '/get', f'Test:{UNICODE}', + 'GET', httpbin + '/get', f'Test:{UNICODE}', env=self.env()) assert HTTP_OK in r1 r2 = http('--session=test', '--verbose', 'GET', - httpbin.url + '/get', env=self.env()) + httpbin + '/get', env=self.env()) assert HTTP_OK in r2 # FIXME: Authorization *sometimes* is not present @@ -241,12 +241,12 @@ class TestSession(SessionTestBase): self.start_session(httpbin) # https://github.com/httpie/cli/issues/180 r1 = http('--session=test', - httpbin.url + '/headers', 'User-Agent:custom', + httpbin + '/headers', 'User-Agent:custom', env=self.env()) assert HTTP_OK in r1 assert r1.json['headers']['User-Agent'] == 'custom' - r2 = http('--session=test', httpbin.url + '/headers', env=self.env()) + r2 = http('--session=test', httpbin + '/headers', env=self.env()) assert HTTP_OK in r2 assert r2.json['headers']['User-Agent'] == 'custom' @@ -257,7 +257,7 @@ class TestSession(SessionTestBase): os.chdir(tmp_path) try: http('--session=test', '--download', - httpbin.url + '/get', env=self.env()) + httpbin + '/get', env=self.env()) finally: os.chdir(cwd) @@ -386,7 +386,7 @@ class TestExpiredCookies(CookieTestBase): r = http( '--session', str(self.session_path), '--print=H', - httpbin.url + '/cookies/delete?cookie2', + httpbin + '/cookies/delete?cookie2', ) assert 'Cookie: cookie1=foo; cookie2=foo' in r @@ -484,7 +484,7 @@ class TestCookieStorage(CookieTestBase): r = http( '--session', str(self.session_path), '--print=H', - httpbin.url, + httpbin + '/get', 'Cookie:' + specified_cookie_header, ) parsed_request_headers = { # noqa @@ -537,7 +537,7 @@ class TestCookieStorage(CookieTestBase): """ http( '--session', str(self.session_path), - httpbin.url + set_cookie, + httpbin + set_cookie, 'Cookie:' + cli_cookie, ) updated_session = json.loads(self.session_path.read_text(encoding=UTF8)) @@ -821,16 +821,17 @@ def test_session_multiple_headers_with_same_name(basic_session, httpbin): 'server, expected_cookies', [ ( - pytest.lazy_fixture('localhost_http_server'), + 'localhost_http_server', {'secure_cookie': 'foo', 'insecure_cookie': 'bar'} ), ( - pytest.lazy_fixture('remote_httpbin'), + 'remote_httpbin', {'insecure_cookie': 'bar'} ) ] ) -def test_secure_cookies_on_localhost(mock_env, tmp_path, server, expected_cookies): +def test_secure_cookies_on_localhost(mock_env, tmp_path, server, expected_cookies, request): + server = request.getfixturevalue(server) session_path = tmp_path / 'session.json' http( '--session', str(session_path), diff --git a/tests/test_stream.py b/tests/test_stream.py index c3b50a75..9dd335f1 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -46,7 +46,7 @@ def test_pretty_redirected_stream(httpbin): stdout_isatty=False, ) r = http('--verbose', '--pretty=all', '--stream', 'GET', - httpbin.url + '/get', env=env) + httpbin + '/get', env=env) assert BINARY_SUPPRESSED_NOTICE.decode() in r @@ -57,7 +57,7 @@ def test_pretty_stream_ensure_full_stream_is_retrieved(httpbin): stdout_isatty=False, ) r = http('--pretty=format', '--stream', 'GET', - httpbin.url + '/stream/3', env=env) + httpbin + '/stream/3', env=env) assert r.count('/stream/3') == 3 @@ -98,7 +98,7 @@ def test_encoded_stream(httpbin): stdin_isatty=False, ) r = http('--pretty=none', '--stream', '--verbose', 'GET', - httpbin.url + '/get', env=env) + httpbin + '/get', env=env) assert BINARY_SUPPRESSED_NOTICE.decode() in r @@ -111,7 +111,7 @@ def test_redirected_stream(httpbin): stdin=StdinBytesIO(BIN_FILE_PATH.read_bytes()), ) r = http('--pretty=none', '--stream', '--verbose', 'GET', - httpbin.url + '/get', env=env) + httpbin + '/get', env=env) assert BIN_FILE_CONTENT in r diff --git a/tests/test_update_warnings.py b/tests/test_update_warnings.py index 36e25963..60a1120c 100644 --- a/tests/test_update_warnings.py +++ b/tests/test_update_warnings.py @@ -132,10 +132,10 @@ def test_check_updates_first_invocation( @pytest.mark.parametrize( - 'should_issue_warning, build_channel', + ['should_issue_warning', 'build_channel'], [ - (False, pytest.lazy_fixture('lower_build_channel')), - (True, pytest.lazy_fixture('higher_build_channel')), + (False, 'lower_build_channel'), + (True, 'higher_build_channel'), ], ) def test_check_updates_first_time_after_data_fetch( @@ -145,7 +145,9 @@ def test_check_updates_first_time_after_data_fetch( static_fetch_data, should_issue_warning, build_channel, + request, ): + request.getfixturevalue(build_channel) http('fetch_updates', '--daemon', env=with_warnings) r = http(httpbin + '/get', env=with_warnings) @@ -176,14 +178,15 @@ def test_cli_check_updates( @pytest.mark.parametrize( - "build_channel", [ - pytest.lazy_fixture("lower_build_channel"), - pytest.lazy_fixture("unknown_build_channel") + 'build_channel', [ + 'lower_build_channel', + 'unknown_build_channel', ] ) def test_cli_check_updates_not_shown( - static_fetch_data, build_channel + static_fetch_data, build_channel, request ): + request.getfixturevalue(build_channel) r = httpie('cli', 'check-updates') assert r.exit_status == ExitStatus.SUCCESS assert not check_update_warnings(r) diff --git a/tests/test_uploads.py b/tests/test_uploads.py index 055b6d2a..7cf98636 100644 --- a/tests/test_uploads.py +++ b/tests/test_uploads.py @@ -199,10 +199,10 @@ class TestMultipartFormDataFileUpload: def test_non_existent_file_raises_parse_error(self, httpbin): with pytest.raises(ParseError): http('--form', - 'POST', httpbin.url + '/post', 'foo@/__does_not_exist__') + 'POST', httpbin + '/post', 'foo@/__does_not_exist__') def test_upload_ok(self, httpbin): - r = http('--form', '--verbose', 'POST', httpbin.url + '/post', + r = http('--form', '--verbose', 'POST', httpbin + '/post', f'test-file@{FILE_PATH_ARG}', 'foo=bar') assert HTTP_OK in r assert 'Content-Disposition: form-data; name="foo"' in r @@ -213,7 +213,7 @@ class TestMultipartFormDataFileUpload: assert 'Content-Type: text/plain' in r def test_upload_multiple_fields_with_the_same_name(self, httpbin): - r = http('--form', '--verbose', 'POST', httpbin.url + '/post', + r = http('--form', '--verbose', 'POST', httpbin + '/post', f'test-file@{FILE_PATH_ARG}', f'test-file@{FILE_PATH_ARG}') assert HTTP_OK in r @@ -228,7 +228,7 @@ class TestMultipartFormDataFileUpload: r = http( '--form', '--verbose', - httpbin.url + '/post', + httpbin + '/post', f'test-file@{FILE_PATH_ARG};type=image/vnd.microsoft.icon' ) assert HTTP_OK in r @@ -242,7 +242,7 @@ class TestMultipartFormDataFileUpload: r = http( '--form', '--verbose', - httpbin.url + '/post', + httpbin + '/post', 'AAAA=AAA', 'BBB=BBB', ) @@ -253,7 +253,7 @@ class TestMultipartFormDataFileUpload: r = http( '--verbose', '--multipart', - httpbin.url + '/post', + httpbin + '/post', 'AAAA=AAA', 'BBB=BBB', ) @@ -268,7 +268,7 @@ class TestMultipartFormDataFileUpload: '--check-status', '--multipart', f'--boundary={boundary}', - httpbin.url + '/post', + httpbin + '/post', 'AAAA=AAA', 'BBB=BBB', ) @@ -282,7 +282,7 @@ class TestMultipartFormDataFileUpload: '--check-status', '--multipart', f'--boundary={boundary}', - httpbin.url + '/post', + httpbin + '/post', 'Content-Type: multipart/magic', 'AAAA=AAA', 'BBB=BBB', @@ -299,7 +299,7 @@ class TestMultipartFormDataFileUpload: '--check-status', '--multipart', f'--boundary={boundary_in_body}', - httpbin.url + '/post', + httpbin + '/post', f'Content-Type: multipart/magic; boundary={boundary_in_header}', 'AAAA=AAA', 'BBB=BBB', @@ -349,7 +349,7 @@ class TestRequestBodyFromFilePath: def test_request_body_from_file_by_path(self, httpbin): r = http( '--verbose', - 'POST', httpbin.url + '/post', + 'POST', httpbin + '/post', '@' + FILE_PATH_ARG, ) assert HTTP_OK in r @@ -370,7 +370,7 @@ class TestRequestBodyFromFilePath: def test_request_body_from_file_by_path_with_explicit_content_type( self, httpbin): r = http('--verbose', - 'POST', httpbin.url + '/post', '@' + FILE_PATH_ARG, + 'POST', httpbin + '/post', '@' + FILE_PATH_ARG, 'Content-Type:text/plain; charset=UTF-8') assert HTTP_OK in r assert FILE_CONTENT in r @@ -379,7 +379,7 @@ class TestRequestBodyFromFilePath: def test_request_body_from_file_by_path_no_field_name_allowed( self, httpbin): env = MockEnvironment(stdin_isatty=True) - r = http('POST', httpbin.url + '/post', 'field-name@' + FILE_PATH_ARG, + r = http('POST', httpbin + '/post', 'field-name@' + FILE_PATH_ARG, env=env, tolerate_error_exit_status=True) assert 'perhaps you meant --form?' in r.stderr @@ -388,7 +388,7 @@ class TestRequestBodyFromFilePath: env = MockEnvironment(stdin_isatty=False) r = http( 'POST', - httpbin.url + '/post', + httpbin + '/post', '@' + FILE_PATH_ARG, 'foo=bar', env=env, tolerate_error_exit_status=True, @@ -400,7 +400,7 @@ class TestRequestBodyFromFilePath: env = MockEnvironment(stdin_isatty=True) r = http( '--verbose', - 'POST', httpbin.url + '/post', + 'POST', httpbin + '/post', '@' + FILE_PATH_ARG, '@' + FILE_PATH_ARG, env=env, diff --git a/tests/test_windows.py b/tests/test_windows.py index b30c7a50..80d392ae 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -12,7 +12,7 @@ class TestWindowsOnly: reason='this test for some reason kills the process') def test_windows_colorized_output(self, httpbin): # Spits out the colorized output. - http(httpbin.url + '/get', env=Environment()) + http(httpbin + '/get', env=Environment()) class TestFakeWindows: @@ -20,6 +20,6 @@ class TestFakeWindows: env = MockEnvironment(is_windows=True) output_file = tmp_path / 'test_output_file_pretty_not_allowed_on_windows' r = http('--output', str(output_file), - '--pretty=all', 'GET', httpbin.url + '/get', + '--pretty=all', 'GET', httpbin + '/get', env=env, tolerate_error_exit_status=True) assert 'Only terminal output can be colorized on Windows' in r.stderr diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 63d7668a..43839dbb 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -367,7 +367,7 @@ def http( $ http --auth=user:password GET pie.dev/basic-auth/user/password >>> httpbin = getfixture('httpbin') - >>> r = http('-a', 'user:pw', httpbin.url + '/basic-auth/user/pw') + >>> r = http('-a', 'user:pw', httpbin + '/basic-auth/user/pw') >>> type(r) == StrCLIResponse True >>> r.exit_status is ExitStatus.SUCCESS