httpie-cli/tests/test_cli.py

376 lines
14 KiB
Python
Raw Normal View History

2014-04-24 14:07:31 +02:00
"""CLI argument parsing related tests."""
import argparse
import pytest
from requests.exceptions import InvalidSchema
2019-08-31 15:17:10 +02:00
import httpie.cli.argparser
from httpie.cli import constants
from httpie.cli.definition import parser
from httpie.cli.argtypes import KeyValueArg, KeyValueArgType
from httpie.cli.requestitems import RequestItems
from httpie.status import ExitStatus
from httpie.utils import load_json_preserve_order_and_dupe_keys
from .fixtures import (
FILE_CONTENT, FILE_PATH, FILE_PATH_ARG, JSON_FILE_CONTENT,
JSON_FILE_PATH_ARG,
)
from .utils import HTTP_OK, MockEnvironment, StdinBytesIO, http
2014-04-24 14:07:31 +02:00
class TestItemParsing:
2019-08-31 15:17:10 +02:00
key_value_arg = KeyValueArgType(*constants.SEPARATOR_GROUP_ALL_ITEMS)
2014-04-24 14:07:31 +02:00
def test_invalid_items(self):
items = ['no-separator']
for item in items:
2019-08-31 15:17:10 +02:00
pytest.raises(argparse.ArgumentTypeError, self.key_value_arg, item)
2014-04-24 14:07:31 +02:00
def test_escape_separator(self):
2019-08-31 15:17:10 +02:00
items = RequestItems.from_args([
2014-04-24 14:07:31 +02:00
# headers
2019-08-31 15:17:10 +02:00
self.key_value_arg(r'foo\:bar:baz'),
self.key_value_arg(r'jack\@jill:hill'),
2014-04-24 14:07:31 +02:00
# data
2019-08-31 15:17:10 +02:00
self.key_value_arg(r'baz\=bar=foo'),
2014-04-24 14:07:31 +02:00
# files
self.key_value_arg(fr'bar\@baz@{FILE_PATH_ARG}'),
2014-04-24 14:07:31 +02:00
])
# `HTTPHeadersDict` => `dict`
headers = dict(items.headers)
assert headers == {
2014-04-24 14:07:31 +02:00
'foo:bar': 'baz',
'jack@jill': 'hill',
}
2019-08-31 15:17:10 +02:00
assert items.data == {
'baz=bar': 'foo'
}
assert 'bar@baz' in items.files
2014-04-24 14:07:31 +02:00
@pytest.mark.parametrize('string, key, sep, value', [
('path=c:\\windows', 'path', '=', 'c:\\windows'),
('path=c:\\windows\\', 'path', '=', 'c:\\windows\\'),
('path\\==c:\\windows', 'path=', '=', 'c:\\windows'),
])
def test_backslash_before_non_special_character_does_not_escape(
2019-08-31 15:17:10 +02:00
self, string, key, sep, value
):
expected = KeyValueArg(orig=string, key=key, sep=sep, value=value)
actual = self.key_value_arg(string)
assert actual == expected
2014-04-24 14:07:31 +02:00
def test_escape_longsep(self):
2019-08-31 15:17:10 +02:00
items = RequestItems.from_args([
self.key_value_arg(r'bob\:==foo'),
2014-04-24 14:07:31 +02:00
])
2019-08-31 15:17:10 +02:00
assert items.params == {
'bob:': 'foo'
}
2014-04-24 14:07:31 +02:00
def test_valid_items(self):
2019-08-31 15:17:10 +02:00
items = RequestItems.from_args([
self.key_value_arg('string=value'),
self.key_value_arg('Header:value'),
self.key_value_arg('Unset-Header:'),
self.key_value_arg('Empty-Header;'),
self.key_value_arg('list:=["a", 1, {}, false]'),
self.key_value_arg('obj:={"a": "b"}'),
self.key_value_arg(r'nested\[2\][a][]=1'),
self.key_value_arg('nested[2][a][]:=1'),
2019-08-31 15:17:10 +02:00
self.key_value_arg('ed='),
self.key_value_arg('bool:=true'),
self.key_value_arg('file@' + FILE_PATH_ARG),
self.key_value_arg('query==value'),
self.key_value_arg('string-embed=@' + FILE_PATH_ARG),
self.key_value_arg('param-embed==@' + FILE_PATH_ARG),
2019-08-31 15:17:10 +02:00
self.key_value_arg('raw-json-embed:=@' + JSON_FILE_PATH_ARG),
2014-04-24 14:07:31 +02:00
])
# Parsed headers
# `HTTPHeadersDict` => `dict`
headers = dict(items.headers)
2016-07-02 12:03:19 +02:00
assert headers == {
'Header': 'value',
'Unset-Header': None,
'Empty-Header': ''
}
2014-04-24 14:07:31 +02:00
# Parsed data
raw_json_embed = items.data.pop('raw-json-embed')
assert raw_json_embed == load_json_preserve_order_and_dupe_keys(JSON_FILE_CONTENT)
items.data['string-embed'] = items.data['string-embed'].strip()
assert dict(items.data) == {
'ed': '',
'string': 'value',
'bool': True,
'list': ['a', 1, load_json_preserve_order_and_dupe_keys('{}'), False],
'nested[2]': {'a': ['1']},
'nested': [None, None, {'a': [1]}],
'obj': load_json_preserve_order_and_dupe_keys('{"a": "b"}'),
'string-embed': FILE_CONTENT
}
2014-04-24 14:07:31 +02:00
# Parsed query string parameters
2019-08-31 15:17:10 +02:00
assert items.params == {
'query': 'value',
'param-embed': FILE_CONTENT.rstrip('\n')
2019-08-31 15:17:10 +02:00
}
2014-04-24 14:07:31 +02:00
# Parsed file fields
assert 'file' in items.files
2016-07-19 18:23:40 +02:00
assert (items.files['file'][1].read().strip().
decode() == FILE_CONTENT)
2014-04-24 14:07:31 +02:00
def test_multiple_file_fields_with_same_field_name(self):
2019-08-31 15:17:10 +02:00
items = RequestItems.from_args([
self.key_value_arg('file_field@' + FILE_PATH_ARG),
self.key_value_arg('file_field@' + FILE_PATH_ARG),
])
assert len(items.files['file_field']) == 2
def test_multiple_text_fields_with_same_field_name(self):
2019-08-31 15:17:10 +02:00
items = RequestItems.from_args(
request_item_args=[
self.key_value_arg('text_field=a'),
self.key_value_arg('text_field=b')
],
request_type=constants.RequestType.FORM,
)
assert items.data['text_field'] == ['a', 'b']
assert list(items.data.items()) == [
('text_field', 'a'),
('text_field', 'b'),
]
2014-04-24 14:07:31 +02:00
class TestQuerystring:
def test_query_string_params_in_url(self, httpbin):
r = http('--print=Hhb', 'GET', httpbin.url + '/get?a=1&b=2')
2014-04-24 14:07:31 +02:00
path = '/get?a=1&b=2'
url = httpbin.url + path
assert HTTP_OK in r
assert f'GET {path} HTTP/1.1' in r
assert f'"url": "{url}"' in r
2014-04-24 14:07:31 +02:00
def test_query_string_params_items(self, httpbin):
r = http('--print=Hhb', 'GET', httpbin.url + '/get', 'a==1')
path = '/get?a=1'
url = httpbin.url + path
assert HTTP_OK in r
assert f'GET {path} HTTP/1.1' in r
assert f'"url": "{url}"' in r
2014-04-24 14:07:31 +02:00
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')
path = '/get?a=1&a=1&a=1&a=1'
url = httpbin.url + path
assert HTTP_OK in r
assert f'GET {path} HTTP/1.1' in r
assert f'"url": "{url}"' in r
2014-04-24 14:07:31 +02:00
@pytest.mark.parametrize(['program_name', 'url_arg', 'parsed_url'], [
('http', '://pie.dev/get', 'http://pie.dev/get'),
('https', '://pie.dev/get', 'https://pie.dev/get'),
])
def test_url_leading_colon_slash_slash(program_name, url_arg, parsed_url):
env = MockEnvironment(program_name=program_name)
args = parser.parse_args(args=[url_arg], env=env)
assert args.url == parsed_url
2021-11-05 14:10:32 +01:00
def test_url_colon_slash_slash_only():
r = http('://', tolerate_error_exit_status=True)
assert r.stderr.strip() == "http: error: InvalidURL: Invalid URL 'http://': No host supplied"
2016-03-03 11:47:12 +01:00
class TestLocalhostShorthand:
2014-04-24 14:07:31 +02:00
def test_expand_localhost_shorthand(self):
args = parser.parse_args(args=[':'], env=MockEnvironment())
assert args.url == 'http://localhost'
2014-04-24 14:07:31 +02:00
def test_expand_localhost_shorthand_with_slash(self):
args = parser.parse_args(args=[':/'], env=MockEnvironment())
assert args.url == 'http://localhost/'
2014-04-24 14:07:31 +02:00
def test_expand_localhost_shorthand_with_port(self):
args = parser.parse_args(args=[':3000'], env=MockEnvironment())
assert args.url == 'http://localhost:3000'
2014-04-24 14:07:31 +02:00
def test_expand_localhost_shorthand_with_path(self):
args = parser.parse_args(args=[':/path'], env=MockEnvironment())
assert args.url == 'http://localhost/path'
2014-04-24 14:07:31 +02:00
def test_expand_localhost_shorthand_with_port_and_slash(self):
args = parser.parse_args(args=[':3000/'], env=MockEnvironment())
assert args.url == 'http://localhost:3000/'
2014-04-24 14:07:31 +02:00
def test_expand_localhost_shorthand_with_port_and_path(self):
args = parser.parse_args(args=[':3000/path'], env=MockEnvironment())
assert args.url == 'http://localhost:3000/path'
2014-04-24 14:07:31 +02:00
def test_dont_expand_shorthand_ipv6_as_shorthand(self):
args = parser.parse_args(args=['::1'], env=MockEnvironment())
assert args.url == 'http://::1'
2014-04-24 14:07:31 +02:00
def test_dont_expand_longer_ipv6_as_shorthand(self):
args = parser.parse_args(
args=['::ffff:c000:0280'],
env=MockEnvironment()
2014-04-24 14:07:31 +02:00
)
assert args.url == 'http://::ffff:c000:0280'
2014-04-24 14:07:31 +02:00
def test_dont_expand_full_ipv6_as_shorthand(self):
args = parser.parse_args(
args=['0000:0000:0000:0000:0000:0000:0000:0001'],
env=MockEnvironment()
2014-04-24 14:07:31 +02:00
)
assert args.url == 'http://0000:0000:0000:0000:0000:0000:0000:0001'
2014-04-24 14:07:31 +02:00
class TestArgumentParser:
def setup_method(self, method):
2019-08-31 15:17:10 +02:00
self.parser = httpie.cli.argparser.HTTPieArgumentParser()
2014-04-24 14:07:31 +02:00
def test_guess_when_method_set_and_valid(self):
self.parser.args = argparse.Namespace()
self.parser.args.method = 'GET'
self.parser.args.url = 'http://example.com/'
2019-08-31 15:17:10 +02:00
self.parser.args.request_items = []
2014-04-24 14:07:31 +02:00
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
2014-04-24 14:07:31 +02:00
self.parser._guess_method()
assert self.parser.args.method == 'GET'
assert self.parser.args.url == 'http://example.com/'
2019-08-31 15:17:10 +02:00
assert self.parser.args.request_items == []
2014-04-24 14:07:31 +02:00
def test_guess_when_method_not_set(self):
self.parser.args = argparse.Namespace()
self.parser.args.method = None
self.parser.args.url = 'http://example.com/'
2019-08-31 15:17:10 +02:00
self.parser.args.request_items = []
2014-04-24 14:07:31 +02:00
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
2014-04-24 14:07:31 +02:00
self.parser._guess_method()
assert self.parser.args.method == 'GET'
assert self.parser.args.url == 'http://example.com/'
2019-08-31 15:17:10 +02:00
assert self.parser.args.request_items == []
2014-04-24 14:07:31 +02:00
def test_guess_when_method_set_but_invalid_and_data_field(self):
self.parser.args = argparse.Namespace()
self.parser.args.method = 'http://example.com/'
self.parser.args.url = 'data=field'
2019-08-31 15:17:10 +02:00
self.parser.args.request_items = []
2014-04-24 14:07:31 +02:00
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
2014-04-24 14:07:31 +02:00
self.parser._guess_method()
assert self.parser.args.method == 'POST'
assert self.parser.args.url == 'http://example.com/'
2019-08-31 15:17:10 +02:00
assert self.parser.args.request_items == [
KeyValueArg(key='data',
value='field',
sep='=',
orig='data=field')
]
2014-04-24 14:07:31 +02:00
def test_guess_when_method_set_but_invalid_and_header_field(self):
self.parser.args = argparse.Namespace()
self.parser.args.method = 'http://example.com/'
self.parser.args.url = 'test:header'
2019-08-31 15:17:10 +02:00
self.parser.args.request_items = []
2014-04-24 14:07:31 +02:00
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
2014-04-24 14:07:31 +02:00
self.parser._guess_method()
assert self.parser.args.method == 'GET'
assert self.parser.args.url == 'http://example.com/'
2019-08-31 15:17:10 +02:00
assert self.parser.args.request_items, [
KeyValueArg(key='test',
value='header',
sep=':',
orig='test:header')
]
2014-04-24 14:07:31 +02:00
def test_guess_when_method_set_but_invalid_and_item_exists(self):
self.parser.args = argparse.Namespace()
self.parser.args.method = 'http://example.com/'
self.parser.args.url = 'new_item=a'
2019-08-31 15:17:10 +02:00
self.parser.args.request_items = [
KeyValueArg(
2014-04-24 14:07:31 +02:00
key='old_item', value='b', sep='=', orig='old_item=b')
]
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
2014-04-24 14:07:31 +02:00
self.parser._guess_method()
2019-08-31 15:17:10 +02:00
assert self.parser.args.request_items, [
KeyValueArg(key='new_item', value='a', sep='=', orig='new_item=a'),
KeyValueArg(
2014-04-24 14:07:31 +02:00
key='old_item', value='b', sep='=', orig='old_item=b'),
]
2014-04-24 14:07:31 +02:00
class TestNoOptions:
def test_valid_no_options(self, httpbin):
r = http('--verbose', '--no-verbose', 'GET', httpbin.url + '/get')
assert 'GET /get HTTP/1.1' not in r
2014-04-24 14:07:31 +02:00
def test_invalid_no_options(self, httpbin):
r = http('--no-war', 'GET', httpbin.url + '/get',
tolerate_error_exit_status=True)
assert r.exit_status == ExitStatus.ERROR
assert 'unrecognized arguments: --no-war' in r.stderr
assert 'GET /get HTTP/1.1' not in r
2014-04-24 14:07:31 +02:00
2019-08-29 13:39:42 +02:00
class TestStdin:
def test_ignore_stdin(self, httpbin):
env = MockEnvironment(
stdin=StdinBytesIO(FILE_PATH.read_bytes()),
stdin_isatty=False,
)
r = http('--ignore-stdin', '--verbose', httpbin.url + '/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."
2014-04-24 14:07:31 +02:00
def test_ignore_stdin_cannot_prompt_password(self, httpbin):
r = http('--ignore-stdin', '--auth=no-password', httpbin.url + '/get',
tolerate_error_exit_status=True)
assert r.exit_status == ExitStatus.ERROR
assert 'because --ignore-stdin' in r.stderr
2019-08-29 13:39:42 +02:00
def test_stdin_closed(self, httpbin):
r = http(httpbin + '/get', env=MockEnvironment(stdin=None))
assert HTTP_OK in r
class TestSchemes:
def test_invalid_custom_scheme(self):
# InvalidSchema is expected because HTTPie
# shouldn't touch a formally valid scheme.
with pytest.raises(InvalidSchema):
http('foo+bar-BAZ.123://bah')
def test_invalid_scheme_via_via_default_scheme(self):
# InvalidSchema is expected because HTTPie
# shouldn't touch a formally valid scheme.
with pytest.raises(InvalidSchema):
http('bah', '--default=scheme=foo+bar-BAZ.123')
def test_default_scheme_option(self, httpbin_secure):
url = f'{httpbin_secure.host}:{httpbin_secure.port}'
assert HTTP_OK in http(url, '--default-scheme=https')
def test_scheme_when_invoked_as_https(self, httpbin_secure):
url = f'{httpbin_secure.host}:{httpbin_secure.port}'
assert HTTP_OK in http(url, program_name='https')