You've already forked httpie-cli
mirror of
https://github.com/httpie/cli.git
synced 2025-08-10 22:42:05 +02:00
Implement new style cookies
This commit is contained in:
@@ -4,7 +4,11 @@ import socket
|
||||
import pytest
|
||||
from pytest_httpbin import certs
|
||||
|
||||
from .utils import HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN, HTTPBIN_WITH_CHUNKED_SUPPORT
|
||||
from .utils import ( # noqa
|
||||
HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN,
|
||||
HTTPBIN_WITH_CHUNKED_SUPPORT,
|
||||
mock_env
|
||||
)
|
||||
from .utils.plugins_cli import ( # noqa
|
||||
broken_plugin,
|
||||
dummy_plugin,
|
||||
|
24
tests/fixtures/__init__.py
vendored
24
tests/fixtures/__init__.py
vendored
@@ -1,6 +1,9 @@
|
||||
"""Test data"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
import httpie
|
||||
from httpie.encoding import UTF8
|
||||
from httpie.output.formatters.xml import pretty_xml, parse_xml
|
||||
|
||||
@@ -19,10 +22,20 @@ FILE_PATH = FIXTURES_ROOT / 'test.txt'
|
||||
JSON_FILE_PATH = FIXTURES_ROOT / 'test.json'
|
||||
JSON_WITH_DUPE_KEYS_FILE_PATH = FIXTURES_ROOT / 'test_with_dupe_keys.json'
|
||||
BIN_FILE_PATH = FIXTURES_ROOT / 'test.bin'
|
||||
|
||||
XML_FILES_PATH = FIXTURES_ROOT / 'xmldata'
|
||||
XML_FILES_VALID = list((XML_FILES_PATH / 'valid').glob('*_raw.xml'))
|
||||
XML_FILES_INVALID = list((XML_FILES_PATH / 'invalid').glob('*.xml'))
|
||||
|
||||
SESSION_FILES_PATH = FIXTURES_ROOT / 'session_data'
|
||||
SESSION_FILES_OLD = sorted((SESSION_FILES_PATH / 'old').glob('*.json'))
|
||||
SESSION_FILES_NEW = sorted((SESSION_FILES_PATH / 'new').glob('*.json'))
|
||||
|
||||
SESSION_VARIABLES = {
|
||||
'__version__': httpie.__version__,
|
||||
'__host__': 'null',
|
||||
}
|
||||
|
||||
FILE_PATH_ARG = patharg(FILE_PATH)
|
||||
BIN_FILE_PATH_ARG = patharg(BIN_FILE_PATH)
|
||||
JSON_FILE_PATH_ARG = patharg(JSON_FILE_PATH)
|
||||
@@ -40,3 +53,14 @@ BIN_FILE_CONTENT = BIN_FILE_PATH.read_bytes()
|
||||
UNICODE = FILE_CONTENT
|
||||
XML_DATA_RAW = '<?xml version="1.0" encoding="utf-8"?><root><e>text</e></root>'
|
||||
XML_DATA_FORMATTED = pretty_xml(parse_xml(XML_DATA_RAW))
|
||||
|
||||
|
||||
def read_session_file(session_file: Path, *, extra_variables: Optional[Dict[str, str]] = None) -> Any:
|
||||
with open(session_file) as stream:
|
||||
data = stream.read()
|
||||
|
||||
session_vars = {**SESSION_VARIABLES, **(extra_variables or {})}
|
||||
for variable, value in session_vars.items():
|
||||
data = data.replace(variable, value)
|
||||
|
||||
return json.loads(data)
|
||||
|
31
tests/fixtures/session_data/new/cookies_dict.json
vendored
Normal file
31
tests/fixtures/session_data/new/cookies_dict.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "__version__"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": [
|
||||
{
|
||||
"domain": __host__,
|
||||
"expires": null,
|
||||
"name": "baz",
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "quux"
|
||||
},
|
||||
{
|
||||
"domain": __host__,
|
||||
"expires": null,
|
||||
"name": "foo",
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "bar"
|
||||
}
|
||||
],
|
||||
"headers": {}
|
||||
}
|
31
tests/fixtures/session_data/new/cookies_dict_dev_version.json
vendored
Normal file
31
tests/fixtures/session_data/new/cookies_dict_dev_version.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "__version__"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": [
|
||||
{
|
||||
"domain": __host__,
|
||||
"expires": null,
|
||||
"name": "baz",
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "quux"
|
||||
},
|
||||
{
|
||||
"domain": __host__,
|
||||
"expires": null,
|
||||
"name": "foo",
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "bar"
|
||||
}
|
||||
],
|
||||
"headers": {}
|
||||
}
|
33
tests/fixtures/session_data/new/cookies_dict_with_extras.json
vendored
Normal file
33
tests/fixtures/session_data/new/cookies_dict_with_extras.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "__version__"
|
||||
},
|
||||
"auth": {
|
||||
"raw_auth": "foo:bar",
|
||||
"type": "basic"
|
||||
},
|
||||
"cookies": [
|
||||
{
|
||||
"domain": __host__,
|
||||
"expires": null,
|
||||
"name": "baz",
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "quux"
|
||||
},
|
||||
{
|
||||
"domain": __host__,
|
||||
"expires": null,
|
||||
"name": "foo",
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "bar"
|
||||
}
|
||||
],
|
||||
"headers": {
|
||||
"X-Data": "value",
|
||||
"X-Foo": "bar"
|
||||
}
|
||||
}
|
14
tests/fixtures/session_data/new/empty_cookies_dict.json
vendored
Normal file
14
tests/fixtures/session_data/new/empty_cookies_dict.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "__version__"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": [],
|
||||
"headers": {}
|
||||
}
|
14
tests/fixtures/session_data/new/empty_cookies_list.json
vendored
Normal file
14
tests/fixtures/session_data/new/empty_cookies_list.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "__version__"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": [],
|
||||
"headers": {}
|
||||
}
|
27
tests/fixtures/session_data/old/cookies_dict.json
vendored
Normal file
27
tests/fixtures/session_data/old/cookies_dict.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "3.0.2"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": {
|
||||
"baz": {
|
||||
"expires": null,
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "quux"
|
||||
},
|
||||
"foo": {
|
||||
"expires": null,
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "bar"
|
||||
}
|
||||
},
|
||||
"headers": {}
|
||||
}
|
27
tests/fixtures/session_data/old/cookies_dict_dev_version.json
vendored
Normal file
27
tests/fixtures/session_data/old/cookies_dict_dev_version.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "2.7.0.dev0"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": {
|
||||
"baz": {
|
||||
"expires": null,
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "quux"
|
||||
},
|
||||
"foo": {
|
||||
"expires": null,
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "bar"
|
||||
}
|
||||
},
|
||||
"headers": {}
|
||||
}
|
29
tests/fixtures/session_data/old/cookies_dict_with_extras.json
vendored
Normal file
29
tests/fixtures/session_data/old/cookies_dict_with_extras.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "3.0.2"
|
||||
},
|
||||
"auth": {
|
||||
"raw_auth": "foo:bar",
|
||||
"type": "basic"
|
||||
},
|
||||
"cookies": {
|
||||
"baz": {
|
||||
"expires": null,
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "quux"
|
||||
},
|
||||
"foo": {
|
||||
"expires": null,
|
||||
"path": "/",
|
||||
"secure": false,
|
||||
"value": "bar"
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
"X-Data": "value",
|
||||
"X-Foo": "bar"
|
||||
}
|
||||
}
|
14
tests/fixtures/session_data/old/empty_cookies_dict.json
vendored
Normal file
14
tests/fixtures/session_data/old/empty_cookies_dict.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "3.0.2"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": {},
|
||||
"headers": {}
|
||||
}
|
14
tests/fixtures/session_data/old/empty_cookies_list.json
vendored
Normal file
14
tests/fixtures/session_data/old/empty_cookies_list.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"__meta__": {
|
||||
"about": "HTTPie session file",
|
||||
"help": "https://httpie.io/docs#sessions",
|
||||
"httpie": "3.0.2"
|
||||
},
|
||||
"auth": {
|
||||
"password": null,
|
||||
"type": null,
|
||||
"username": null
|
||||
},
|
||||
"cookies": [],
|
||||
"headers": {}
|
||||
}
|
262
tests/test_cookie_on_redirects.py
Normal file
262
tests/test_cookie_on_redirects.py
Normal file
@@ -0,0 +1,262 @@
|
||||
import pytest
|
||||
from .utils import http
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remote_httpbin(httpbin_with_chunked_support):
|
||||
return httpbin_with_chunked_support
|
||||
|
||||
|
||||
def _stringify(fixture):
|
||||
return fixture + ''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('instance', [
|
||||
pytest.lazy_fixture('httpbin'),
|
||||
pytest.lazy_fixture('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.
|
||||
|
||||
r = http(
|
||||
'--follow',
|
||||
httpbin + '/redirect-to',
|
||||
f'url=={_stringify(instance)}/cookies',
|
||||
'Cookie:a=b'
|
||||
)
|
||||
assert r.json == {'cookies': {}}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('instance', [
|
||||
pytest.lazy_fixture('httpbin'),
|
||||
pytest.lazy_fixture('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.
|
||||
|
||||
r = http(
|
||||
'--follow',
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/redirect-to',
|
||||
f'url=={_stringify(instance)}/cookies',
|
||||
'Cookie:a=b'
|
||||
)
|
||||
assert r.json == {'cookies': {'a': 'b'}}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('instance', [
|
||||
pytest.lazy_fixture('httpbin'),
|
||||
pytest.lazy_fixture('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.
|
||||
|
||||
http(
|
||||
'--follow',
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/get',
|
||||
'Cookie:a=b'
|
||||
)
|
||||
r = http(
|
||||
'--follow',
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/redirect-to',
|
||||
f'url=={_stringify(instance)}/cookies',
|
||||
)
|
||||
assert r.json == {'cookies': {'a': 'b'}}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('instance', [
|
||||
pytest.lazy_fixture('httpbin'),
|
||||
pytest.lazy_fixture('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.
|
||||
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',
|
||||
'X-Custom-Header:value'
|
||||
)
|
||||
assert 'X-Custom-Header' in r.json['headers']
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
session_args = []
|
||||
if session:
|
||||
session_args.extend([
|
||||
'--session',
|
||||
str(tmp_path / 'session.json')
|
||||
])
|
||||
|
||||
r = http(
|
||||
'--follow',
|
||||
*session_args,
|
||||
httpbin + '/cookies/set/a/b',
|
||||
)
|
||||
assert r.json['cookies'] == {'a': 'b'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('session', [True, False])
|
||||
def test_server_set_cookie_on_redirect_different_domain(tmp_path, http_server, httpbin, session):
|
||||
# Server set cookies ARE persisted on different domains
|
||||
# when they are forwarded.
|
||||
|
||||
session_args = []
|
||||
if session:
|
||||
session_args.extend([
|
||||
'--session',
|
||||
str(tmp_path / 'session.json')
|
||||
])
|
||||
|
||||
r = http(
|
||||
'--follow',
|
||||
*session_args,
|
||||
http_server + '/cookies/set-and-redirect',
|
||||
f"X-Redirect-To:{httpbin + '/cookies'}",
|
||||
'X-Cookies:a=b'
|
||||
)
|
||||
assert r.json['cookies'] == {'a': 'b'}
|
||||
|
||||
|
||||
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.
|
||||
http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/cookies/set/a/b'
|
||||
)
|
||||
r = http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/cookies'
|
||||
)
|
||||
assert r.json == {'cookies': {'a': 'b'}}
|
||||
|
||||
|
||||
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.
|
||||
http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/cookies/set/a/b'
|
||||
)
|
||||
r = http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
remote_httpbin + '/cookies'
|
||||
)
|
||||
assert r.json == {'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'),
|
||||
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'),
|
||||
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'),
|
||||
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'),
|
||||
True,
|
||||
),
|
||||
])
|
||||
def test_saved_session_cookies_on_redirect(tmp_path, initial_domain, first_request_domain, second_request_domain, expect_cookies):
|
||||
http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
initial_domain + '/cookies/set/a/b'
|
||||
)
|
||||
r = http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
'--follow',
|
||||
first_request_domain + '/redirect-to',
|
||||
f'url=={_stringify(second_request_domain)}/cookies'
|
||||
)
|
||||
if expect_cookies:
|
||||
expected_data = {'cookies': {'a': 'b'}}
|
||||
else:
|
||||
expected_data = {'cookies': {}}
|
||||
assert r.json == expected_data
|
||||
|
||||
|
||||
def test_saved_session_cookie_pool(tmp_path, httpbin, remote_httpbin):
|
||||
http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/cookies/set/a/b'
|
||||
)
|
||||
http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
remote_httpbin + '/cookies/set/a/c'
|
||||
)
|
||||
http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
remote_httpbin + '/cookies/set/b/d'
|
||||
)
|
||||
|
||||
response = http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
httpbin + '/cookies'
|
||||
)
|
||||
assert response.json['cookies'] == {'a': 'b'}
|
||||
|
||||
response = http(
|
||||
'--session',
|
||||
str(tmp_path / 'session.json'),
|
||||
remote_httpbin + '/cookies'
|
||||
)
|
||||
assert response.json['cookies'] == {'a': 'c', 'b': 'd'}
|
125
tests/test_httpie_cli.py
Normal file
125
tests/test_httpie_cli.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import pytest
|
||||
import shutil
|
||||
import json
|
||||
from httpie.sessions import SESSIONS_DIR_NAME
|
||||
from httpie.status import ExitStatus
|
||||
from tests.utils import DUMMY_HOST, httpie
|
||||
from tests.fixtures import SESSION_FILES_PATH, SESSION_FILES_NEW, SESSION_FILES_OLD, read_session_file
|
||||
|
||||
|
||||
OLD_SESSION_FILES_PATH = SESSION_FILES_PATH / 'old'
|
||||
|
||||
|
||||
@pytest.mark.requires_installation
|
||||
def test_plugins_cli_error_message_without_args():
|
||||
# No arguments
|
||||
result = httpie(no_debug=True)
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'usage: ' in result.stderr
|
||||
assert 'specify one of these' in result.stderr
|
||||
assert 'please use the http/https commands:' in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'example',
|
||||
[
|
||||
'pie.dev/get',
|
||||
'DELETE localhost:8000/delete',
|
||||
'POST pie.dev/post header:value a=b header_2:value x:=1',
|
||||
],
|
||||
)
|
||||
@pytest.mark.requires_installation
|
||||
def test_plugins_cli_error_messages_with_example(example):
|
||||
result = httpie(*example.split(), no_debug=True)
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'usage: ' in result.stderr
|
||||
assert f'http {example}' in result.stderr
|
||||
assert f'https {example}' in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'example',
|
||||
[
|
||||
'cli',
|
||||
'plugins',
|
||||
'cli foo',
|
||||
'plugins unknown',
|
||||
'plugins unknown.com A:B c=d',
|
||||
'unknown.com UNPARSABLE????SYNTAX',
|
||||
],
|
||||
)
|
||||
@pytest.mark.requires_installation
|
||||
def test_plugins_cli_error_messages_invalid_example(example):
|
||||
result = httpie(*example.split(), no_debug=True)
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'usage: ' in result.stderr
|
||||
assert f'http {example}' not in result.stderr
|
||||
assert f'https {example}' not in result.stderr
|
||||
|
||||
|
||||
HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS = [
|
||||
(
|
||||
# Default settings
|
||||
[],
|
||||
{'__host__': json.dumps(None)},
|
||||
),
|
||||
(
|
||||
# When --bind-cookies is applied, the __host__ becomes DUMMY_URL.
|
||||
['--bind-cookies'],
|
||||
{'__host__': json.dumps(DUMMY_HOST)},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'old_session_file, new_session_file', zip(SESSION_FILES_OLD, SESSION_FILES_NEW)
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
'extra_args, extra_variables',
|
||||
HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS,
|
||||
)
|
||||
def test_httpie_sessions_upgrade(tmp_path, old_session_file, new_session_file, extra_args, extra_variables):
|
||||
session_path = tmp_path / 'session.json'
|
||||
shutil.copyfile(old_session_file, session_path)
|
||||
|
||||
result = httpie(
|
||||
'cli', 'sessions', 'upgrade', *extra_args, DUMMY_HOST, str(session_path)
|
||||
)
|
||||
assert result.exit_status == ExitStatus.SUCCESS
|
||||
assert read_session_file(session_path) == read_session_file(
|
||||
new_session_file, extra_variables=extra_variables
|
||||
)
|
||||
|
||||
|
||||
def test_httpie_sessions_upgrade_on_non_existent_file(tmp_path):
|
||||
session_path = tmp_path / 'session.json'
|
||||
result = httpie('cli', 'sessions', 'upgrade', DUMMY_HOST, str(session_path))
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'does not exist' in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'extra_args, extra_variables',
|
||||
HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS,
|
||||
)
|
||||
def test_httpie_sessions_upgrade_all(tmp_path, mock_env, extra_args, extra_variables):
|
||||
mock_env._create_temp_config_dir = False
|
||||
mock_env.config_dir = tmp_path / "config"
|
||||
|
||||
session_dir = mock_env.config_dir / SESSIONS_DIR_NAME / DUMMY_HOST
|
||||
session_dir.mkdir(parents=True)
|
||||
for original_session_file in SESSION_FILES_OLD:
|
||||
shutil.copy(original_session_file, session_dir)
|
||||
|
||||
result = httpie(
|
||||
'cli', 'sessions', 'upgrade-all', *extra_args, env=mock_env
|
||||
)
|
||||
assert result.exit_status == ExitStatus.SUCCESS
|
||||
|
||||
for refactored_session_file, expected_session_file in zip(
|
||||
sorted(session_dir.glob("*.json")),
|
||||
SESSION_FILES_NEW
|
||||
):
|
||||
assert read_session_file(refactored_session_file) == read_session_file(
|
||||
expected_session_file, extra_variables=extra_variables
|
||||
)
|
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from httpie.status import ExitStatus
|
||||
from tests.utils import httpie
|
||||
from tests.utils.plugins_cli import parse_listing
|
||||
|
||||
|
||||
@@ -149,45 +148,3 @@ def test_broken_plugins(httpie_plugins, httpie_plugins_success, dummy_plugin, br
|
||||
# No warning now, since it is uninstalled.
|
||||
data = parse_listing(httpie_plugins_success('list'))
|
||||
assert len(data) == 1
|
||||
|
||||
|
||||
@pytest.mark.requires_installation
|
||||
def test_plugins_cli_error_message_without_args():
|
||||
# No arguments
|
||||
result = httpie(no_debug=True)
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'usage: ' in result.stderr
|
||||
assert 'specify one of these' in result.stderr
|
||||
assert 'please use the http/https commands:' in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'example', [
|
||||
'pie.dev/get',
|
||||
'DELETE localhost:8000/delete',
|
||||
'POST pie.dev/post header:value a=b header_2:value x:=1'
|
||||
]
|
||||
)
|
||||
@pytest.mark.requires_installation
|
||||
def test_plugins_cli_error_messages_with_example(example):
|
||||
result = httpie(*example.split(), no_debug=True)
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'usage: ' in result.stderr
|
||||
assert f'http {example}' in result.stderr
|
||||
assert f'https {example}' in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'example', [
|
||||
'plugins unknown',
|
||||
'plugins unknown.com A:B c=d',
|
||||
'unknown.com UNPARSABLE????SYNTAX',
|
||||
]
|
||||
)
|
||||
@pytest.mark.requires_installation
|
||||
def test_plugins_cli_error_messages_invalid_example(example):
|
||||
result = httpie(*example.split(), no_debug=True)
|
||||
assert result.exit_status == ExitStatus.ERROR
|
||||
assert 'usage: ' in result.stderr
|
||||
assert f'http {example}' not in result.stderr
|
||||
assert f'https {example}' not in result.stderr
|
||||
|
@@ -1,12 +1,16 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
from pathlib import Path
|
||||
from typing import Iterator
|
||||
|
||||
import pytest
|
||||
|
||||
from .fixtures import FILE_PATH_ARG, UNICODE
|
||||
from httpie.context import Environment
|
||||
from httpie.encoding import UTF8
|
||||
from httpie.plugins import AuthPlugin
|
||||
from httpie.plugins.builtin import HTTPBasicAuth
|
||||
@@ -14,7 +18,7 @@ from httpie.plugins.registry import plugin_manager
|
||||
from httpie.sessions import Session
|
||||
from httpie.utils import get_expired_cookies
|
||||
from .test_auth_plugins import basic_auth
|
||||
from .utils import HTTP_OK, MockEnvironment, http, mk_config_dir
|
||||
from .utils import DUMMY_HOST, HTTP_OK, MockEnvironment, http, mk_config_dir
|
||||
from base64 import b64encode
|
||||
|
||||
|
||||
@@ -203,9 +207,9 @@ class TestSession(SessionTestBase):
|
||||
"""
|
||||
self.start_session(httpbin)
|
||||
session_data = {
|
||||
"headers": {
|
||||
"cookie": "...",
|
||||
"zzz": "..."
|
||||
'headers': {
|
||||
'cookie': '...',
|
||||
'zzz': '...'
|
||||
}
|
||||
}
|
||||
session_path = self.config_dir / 'session-data.json'
|
||||
@@ -307,7 +311,7 @@ class TestSession(SessionTestBase):
|
||||
auth_type = 'test-prompted'
|
||||
|
||||
def get_auth(self, username=None, password=None):
|
||||
basic_auth_header = "Basic " + b64encode(self.raw_auth.encode()).strip().decode('latin1')
|
||||
basic_auth_header = 'Basic ' + b64encode(self.raw_auth.encode()).strip().decode('latin1')
|
||||
return basic_auth(basic_auth_header)
|
||||
|
||||
plugin_manager.register(Plugin)
|
||||
@@ -359,7 +363,7 @@ class TestSession(SessionTestBase):
|
||||
)
|
||||
updated_session = json.loads(self.session_path.read_text(encoding=UTF8))
|
||||
assert updated_session['auth']['type'] == 'test-saved'
|
||||
assert updated_session['auth']['raw_auth'] == "user:password"
|
||||
assert updated_session['auth']['raw_auth'] == 'user:password'
|
||||
plugin_manager.unregister(Plugin)
|
||||
|
||||
|
||||
@@ -368,12 +372,12 @@ class TestExpiredCookies(CookieTestBase):
|
||||
@pytest.mark.parametrize(
|
||||
'initial_cookie, expired_cookie',
|
||||
[
|
||||
({'id': {'value': 123}}, 'id'),
|
||||
({'id': {'value': 123}}, 'token')
|
||||
({'id': {'value': 123}}, {'name': 'id'}),
|
||||
({'id': {'value': 123}}, {'name': 'token'})
|
||||
]
|
||||
)
|
||||
def test_removes_expired_cookies_from_session_obj(self, initial_cookie, expired_cookie, httpbin):
|
||||
session = Session(self.config_dir)
|
||||
def test_removes_expired_cookies_from_session_obj(self, initial_cookie, expired_cookie, httpbin, mock_env):
|
||||
session = Session(self.config_dir, env=mock_env, session_id=None, bound_host=None)
|
||||
session['cookies'] = initial_cookie
|
||||
session.remove_cookies([expired_cookie])
|
||||
assert expired_cookie not in session.cookies
|
||||
@@ -524,3 +528,165 @@ class TestCookieStorage(CookieTestBase):
|
||||
updated_session = json.loads(self.session_path.read_text(encoding=UTF8))
|
||||
|
||||
assert updated_session['cookies']['cookie1']['value'] == expected
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def basic_session(httpbin, tmp_path):
|
||||
session_path = tmp_path / 'session.json'
|
||||
http(
|
||||
'--session', str(session_path),
|
||||
httpbin + '/get'
|
||||
)
|
||||
return session_path
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_session(path: Path, env: Environment, read_only: bool = False) -> Iterator[Session]:
|
||||
session = Session(path, env, session_id='test', bound_host=DUMMY_HOST)
|
||||
session.load()
|
||||
yield session
|
||||
if not read_only:
|
||||
session.save()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_raw_session(path: Path, read_only: bool = False) -> None:
|
||||
with open(path) as stream:
|
||||
raw_session = json.load(stream)
|
||||
|
||||
yield raw_session
|
||||
|
||||
if not read_only:
|
||||
with open(path, 'w') as stream:
|
||||
json.dump(raw_session, stream)
|
||||
|
||||
|
||||
def read_stderr(env: Environment) -> bytes:
|
||||
env.stderr.seek(0)
|
||||
stderr_data = env.stderr.read()
|
||||
if isinstance(stderr_data, str):
|
||||
return stderr_data.encode()
|
||||
else:
|
||||
return stderr_data
|
||||
|
||||
|
||||
def test_old_session_version_saved_as_is(basic_session, mock_env):
|
||||
with open_session(basic_session, mock_env) as session:
|
||||
session['__meta__'] = {'httpie': '0.0.1'}
|
||||
|
||||
with open_session(basic_session, mock_env, read_only=True) as session:
|
||||
assert session['__meta__']['httpie'] == '0.0.1'
|
||||
|
||||
|
||||
def test_old_session_cookie_layout_warning(basic_session, mock_env):
|
||||
with open_session(basic_session, mock_env) as session:
|
||||
# Use the old layout & set a cookie
|
||||
session['cookies'] = {}
|
||||
session.cookies.set('foo', 'bar')
|
||||
|
||||
assert read_stderr(mock_env) == b''
|
||||
|
||||
with open_session(basic_session, mock_env, read_only=True) as session:
|
||||
assert b'Outdated layout detected' in read_stderr(mock_env)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cookies, expect_warning', [
|
||||
# Old-style cookie format
|
||||
(
|
||||
# Without 'domain' set
|
||||
{'foo': {'value': 'bar'}},
|
||||
True
|
||||
),
|
||||
(
|
||||
# With 'domain' set to empty string
|
||||
{'foo': {'value': 'bar', 'domain': ''}},
|
||||
True
|
||||
),
|
||||
(
|
||||
# With 'domain' set to null
|
||||
{'foo': {'value': 'bar', 'domain': None}},
|
||||
False,
|
||||
),
|
||||
(
|
||||
# With 'domain' set to a URL
|
||||
{'foo': {'value': 'bar', 'domain': DUMMY_HOST}},
|
||||
False,
|
||||
),
|
||||
# New style cookie format
|
||||
(
|
||||
# Without 'domain' set
|
||||
[{'name': 'foo', 'value': 'bar'}],
|
||||
False
|
||||
),
|
||||
(
|
||||
# With 'domain' set to empty string
|
||||
[{'name': 'foo', 'value': 'bar', 'domain': ''}],
|
||||
False
|
||||
),
|
||||
(
|
||||
# With 'domain' set to null
|
||||
[{'name': 'foo', 'value': 'bar', 'domain': None}],
|
||||
False,
|
||||
),
|
||||
(
|
||||
# With 'domain' set to a URL
|
||||
[{'name': 'foo', 'value': 'bar', 'domain': DUMMY_HOST}],
|
||||
False,
|
||||
),
|
||||
])
|
||||
def test_cookie_security_warnings_on_raw_cookies(basic_session, mock_env, cookies, expect_warning):
|
||||
with open_raw_session(basic_session) as raw_session:
|
||||
raw_session['cookies'] = cookies
|
||||
|
||||
with open_session(basic_session, mock_env, read_only=True):
|
||||
warning = b'Outdated layout detected'
|
||||
stderr = read_stderr(mock_env)
|
||||
|
||||
if expect_warning:
|
||||
assert warning in stderr
|
||||
else:
|
||||
assert warning not in stderr
|
||||
|
||||
|
||||
def test_old_session_cookie_layout_loading(basic_session, httpbin, mock_env):
|
||||
with open_session(basic_session, mock_env) as session:
|
||||
# Use the old layout & set a cookie
|
||||
session['cookies'] = {}
|
||||
session.cookies.set('foo', 'bar')
|
||||
|
||||
response = http(
|
||||
'--session', str(basic_session),
|
||||
httpbin + '/cookies'
|
||||
)
|
||||
assert response.json['cookies'] == {'foo': 'bar'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('layout_type', [
|
||||
dict, list
|
||||
])
|
||||
def test_session_cookie_layout_preservance(basic_session, mock_env, layout_type):
|
||||
with open_session(basic_session, mock_env) as session:
|
||||
session['cookies'] = layout_type()
|
||||
session.cookies.set('foo', 'bar')
|
||||
session.save()
|
||||
|
||||
with open_session(basic_session, mock_env, read_only=True) as session:
|
||||
assert isinstance(session['cookies'], layout_type)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('layout_type', [
|
||||
dict, list
|
||||
])
|
||||
def test_session_cookie_layout_preservance_on_new_cookies(basic_session, httpbin, mock_env, layout_type):
|
||||
with open_session(basic_session, mock_env) as session:
|
||||
session['cookies'] = layout_type()
|
||||
session.cookies.set('foo', 'bar')
|
||||
session.save()
|
||||
|
||||
http(
|
||||
'--session', str(basic_session),
|
||||
httpbin + '/cookies/set/baz/quux'
|
||||
)
|
||||
|
||||
with open_session(basic_session, mock_env, read_only=True) as session:
|
||||
assert isinstance(session['cookies'], layout_type)
|
||||
|
@@ -6,6 +6,8 @@ import time
|
||||
import json
|
||||
import tempfile
|
||||
import warnings
|
||||
import pytest
|
||||
from contextlib import suppress
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union, List, Iterable
|
||||
@@ -16,6 +18,7 @@ import httpie.manager.__main__ as manager
|
||||
from httpie.status import ExitStatus
|
||||
from httpie.config import Config
|
||||
from httpie.context import Environment
|
||||
from httpie.utils import url_as_host
|
||||
|
||||
|
||||
# pytest-httpbin currently does not support chunked requests:
|
||||
@@ -39,6 +42,7 @@ HTTP_OK_COLOR = (
|
||||
)
|
||||
|
||||
DUMMY_URL = 'http://this-should.never-resolve' # Note: URL never fetched
|
||||
DUMMY_HOST = url_as_host(DUMMY_URL)
|
||||
|
||||
|
||||
def strip_colors(colorized_msg: str) -> str:
|
||||
@@ -187,6 +191,13 @@ class ExitStatusError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_env() -> MockEnvironment:
|
||||
env = MockEnvironment(stdout_mode='')
|
||||
yield env
|
||||
env.cleanup()
|
||||
|
||||
|
||||
def normalize_args(args: Iterable[Any]) -> List[str]:
|
||||
return [str(arg) for arg in args]
|
||||
|
||||
@@ -201,7 +212,7 @@ def httpie(
|
||||
status.
|
||||
"""
|
||||
|
||||
env = kwargs.setdefault('env', MockEnvironment())
|
||||
env = kwargs.setdefault('env', MockEnvironment(stdout_mode=''))
|
||||
cli_args = ['httpie']
|
||||
if not kwargs.pop('no_debug', False):
|
||||
cli_args.append('--debug')
|
||||
@@ -214,7 +225,16 @@ def httpie(
|
||||
env.stdout.seek(0)
|
||||
env.stderr.seek(0)
|
||||
try:
|
||||
response = StrCLIResponse(env.stdout.read())
|
||||
output = env.stdout.read()
|
||||
if isinstance(output, bytes):
|
||||
with suppress(UnicodeDecodeError):
|
||||
output = output.decode()
|
||||
|
||||
if isinstance(output, bytes):
|
||||
response = BytesCLIResponse(output)
|
||||
else:
|
||||
response = StrCLIResponse(output)
|
||||
|
||||
response.stderr = env.stderr.read()
|
||||
response.exit_status = exit_status
|
||||
response.args = cli_args
|
||||
|
@@ -85,6 +85,19 @@ def status_custom_msg(handler):
|
||||
handler.end_headers()
|
||||
|
||||
|
||||
@TestHandler.handler('GET', '/cookies/set-and-redirect')
|
||||
def set_cookie_and_redirect(handler):
|
||||
handler.send_response(302)
|
||||
|
||||
redirect_to = handler.headers.get('X-Redirect-To', '/headers')
|
||||
handler.send_header('Location', redirect_to)
|
||||
|
||||
raw_cookies = handler.headers.get('X-Cookies', 'a=b')
|
||||
for cookie in raw_cookies.split(', '):
|
||||
handler.send_header('Set-Cookie', cookie)
|
||||
handler.end_headers()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def http_server():
|
||||
"""A custom HTTP server implementation for our tests, that is
|
||||
|
Reference in New Issue
Block a user