cli argument parsing related tests import argparse import pytest from requests exceptions import invalidschema 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 loadjsonpreserveorderanddupekeys from fixtures import filecontent filepath filepatharg jsonfilecontent jsonfilepatharg from utils import httpok mockenvironment stdinbytesio http class testitemparsing keyvaluearg keyvalueargtypeconstants separatorgroupallitems def testinvaliditemsself items noseparator for item in items pytest raisesargparse argumenttypeerror self keyvaluearg item def testescapeseparatorself items requestitems fromargs headers self keyvalueargr foo bar baz self keyvalueargr jackjill hill data self keyvalueargr bazbarfoo files self keyvalueargfr barbazfilepatharg httpheadersdict dict headers dictitems headers assert headers foo bar baz jackjill hill assert items data bazbar foo assert barbaz in items files pytest mark parametrize string key sep value pathc windows path c windows pathc windows path c windows pathc windows path c windows def testbackslashbeforenonspecialcharacterdoesnotescape self string key sep value expected keyvalueargorigstring keykey sepsep valuevalue actual self keyvalueargstring assert actual expected def testescapelongsepself items requestitems fromargs self keyvalueargr bob foo assert items params bob foo def testvaliditemsself items requestitems fromargs self keyvaluearg stringvalue self keyvaluearg header value self keyvaluearg unsetheader self keyvaluearg emptyheader self keyvaluearg list a 1 false self keyvaluearg obj a b self keyvalueargr nested2a1 self keyvaluearg nested2a 1 self keyvaluearg ed self keyvaluearg bool true self keyvaluearg file filepatharg self keyvaluearg queryvalue self keyvaluearg embeddedheader filepatharg self keyvaluearg stringembed filepatharg self keyvaluearg paramembed filepatharg self keyvaluearg rawjsonembed jsonfilepatharg parsed headers httpheadersdict dict headers dictitems headers assert headers header value unsetheader none emptyheader embeddedheader filecontent rstrip n parsed data rawjsonembed items data pop rawjsonembed assert rawjsonembed loadjsonpreserveorderanddupekeysjsonfilecontent items data stringembed items data stringembed strip assert dictitems data ed string value bool true list a 1 loadjsonpreserveorderanddupekeys false nested2 a 1 nested none none a 1 obj loadjsonpreserveorderanddupekeys a b stringembed filecontent parsed query string parameters assert items params query value paramembed filecontent rstrip n parsed file fields assert file in items files assert items files file 1 read strip decode filecontent def testmultiplefilefieldswithsamefieldnameself items requestitems fromargs self keyvaluearg filefield filepatharg self keyvaluearg filefield filepatharg assert lenitems files filefield 2 def testmultipletextfieldswithsamefieldnameself items requestitems fromargs requestitemargs self keyvaluearg textfielda self keyvaluearg textfieldb requesttypeconstants requesttype form assert items data textfield a b assert listitems data items textfield a textfield b class testquerystring def testquerystringparamsinurlself httpbin r http printhhb get httpbin url get a1b2 path get a1b2 url httpbin url path assert httpok in r assert f get path http1 1 in r assert f url url in r def testquerystringparamsitemsself httpbin r http printhhb get httpbin url get a1 path get a1 url httpbin url path assert httpok in r assert f get path http1 1 in r assert f url url in r def testquerystringparamsinurlanditemswithduplicatesself httpbin r http printhhb get httpbin url get a1a1 a1 a1 path get a1a1a1a1 url httpbin url path assert httpok in r assert f get path http1 1 in r assert f url url in r pytest mark parametrize programname urlarg parsedurl http pie devget http pie devget https pie devget https pie devget def testurlleadingcolonslashslashprogramname urlarg parsedurl env mockenvironmentprogramnameprogramname args parser parseargsargsurlarg envenv assert args url parsedurl def testurlcolonslashslashonly r http tolerateerrorexitstatustrue assert r stderr strip http error invalidurl invalid url http no host supplied class testlocalhostshorthand def testexpandlocalhostshorthandself args parser parseargsargs envmockenvironment assert args url http localhost def testexpandlocalhostshorthandwithslashself args parser parseargsargs envmockenvironment assert args url http localhost def testexpandlocalhostshorthandwithportself args parser parseargsargs 3000 envmockenvironment assert args url http localhost 3000 def testexpandlocalhostshorthandwithpathself args parser parseargsargs path envmockenvironment assert args url http localhostpath def testexpandlocalhostshorthandwithportandslashself args parser parseargsargs 3000 envmockenvironment assert args url http localhost 3000 def testexpandlocalhostshorthandwithportandpathself args parser parseargsargs 3000path envmockenvironment assert args url http localhost 3000path def testdontexpandshorthandipv6asshorthandself args parser parseargsargs 1 envmockenvironment assert args url http 1 def testdontexpandlongeripv6asshorthandself args parser parseargs args ffff c000 0280 envmockenvironment assert args url http ffff c000 0280 def testdontexpandfullipv6asshorthandself args parser parseargs args 0000 0000 0000 0000 0000 0000 0000 0001 envmockenvironment assert args url http 0000 0000 0000 0000 0000 0000 0000 0001 class testargumentparser def setupmethodself method self parser httpie cli argparser httpieargumentparser def testguesswhenmethodsetandvalidself self parser args argparse namespace self parser args method get self parser args url http example com self parser args requestitems self parser args ignorestdin false self parser env mockenvironment self parser guessmethod assert self parser args method get assert self parser args url http example com assert self parser args requestitems def testguesswhenmethodnotsetself self parser args argparse namespace self parser args method none self parser args url http example com self parser args requestitems self parser args ignorestdin false self parser env mockenvironment self parser guessmethod assert self parser args method get assert self parser args url http example com assert self parser args requestitems def testguesswhenmethodsetbutinvalidanddatafieldself self parser args argparse namespace self parser args method http example com self parser args url datafield self parser args requestitems self parser args ignorestdin false self parser env mockenvironment self parser guessmethod assert self parser args method post assert self parser args url http example com assert self parser args requestitems keyvalueargkey data value field sep orig datafield def testguesswhenmethodsetbutinvalidandheaderfieldself self parser args argparse namespace self parser args method http example com self parser args url test header self parser args requestitems self parser args ignorestdin false self parser env mockenvironment self parser guessmethod assert self parser args method get assert self parser args url http example com assert self parser args requestitems keyvalueargkey test value header sep orig test header def testguesswhenmethodsetbutinvalidanditemexistsself self parser args argparse namespace self parser args method http example com self parser args url newitema self parser args requestitems keyvaluearg key olditem value b sep orig olditemb self parser args ignorestdin false self parser env mockenvironment self parser guessmethod assert self parser args requestitems keyvalueargkey newitem value a sep orig newitema keyvaluearg key olditem value b sep orig olditemb class testnooptions def testvalidnooptionsself httpbin r http verbose noverbose get httpbin url get assert get get http1 1 not in r def testinvalidnooptionsself httpbin r http nowar get httpbin url get tolerateerrorexitstatustrue assert r exitstatus exitstatus error assert unrecognized arguments nowar in r stderr assert get get http1 1 not in r class teststdin def testignorestdinself httpbin env mockenvironment stdinstdinbytesiofilepath readbytes stdinisattyfalse r http ignorestdin verbose httpbin url get envenv assert httpok in r assert get get http in r don t default to post assert filecontent not in r don t send stdin data def testignorestdincannotpromptpasswordself httpbin r http ignorestdin authnopassword httpbin url get tolerateerrorexitstatustrue assert r exitstatus exitstatus error assert because ignorestdin in r stderr def teststdinclosedself httpbin r httphttpbin get envmockenvironmentstdinnone assert httpok in r class testschemes def testinvalidcustomschemeself invalidschema is expected because httpie shouldn t touch a formally valid scheme with pytest raisesinvalidschema http foobarbaz 123 bah def testinvalidschemeviaviadefaultschemeself invalidschema is expected because httpie shouldn t touch a formally valid scheme with pytest raisesinvalidschema http bah defaultschemefoobarbaz 123 def testdefaultschemeoptionself httpbinsecure url f httpbinsecure host httpbinsecure port assert httpok in httpurl defaultschemehttps def testschemewheninvokedashttpsself httpbinsecure url f httpbinsecure host httpbinsecure port assert httpok in httpurl programname https headers data files httpheadersdict dict parsed headers httpheadersdict dict parsed data parsed query string parameters parsed file fields invalidschema is expected because httpie shouldn t touch a formally valid scheme invalidschema is expected because httpie shouldn t touch a formally valid scheme
import argparse

import pytest
from requests.exceptions import InvalidSchema

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


class TestItemParsing:
    key_value_arg = KeyValueArgType(*constants.SEPARATOR_GROUP_ALL_ITEMS)

    def test_invalid_items(self):
        items = ['no-separator']
        for item in items:
            pytest.raises(argparse.ArgumentTypeError, self.key_value_arg, item)

    def test_escape_separator(self):
        items = RequestItems.from_args([
            
            self.key_value_arg(r'foo\:bar:baz'),
            self.key_value_arg(r'jack\@jill:hill'),

            
            self.key_value_arg(r'baz\=bar=foo'),

            
            self.key_value_arg(fr'bar\@baz@{FILE_PATH_ARG}'),
        ])
        
        headers = dict(items.headers)

        assert headers == {
            'foo:bar': 'baz',
            'jack@jill': 'hill',
        }
        assert items.data == {
            'baz=bar': 'foo'
        }
        assert 'bar@baz' in items.files

    @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(
        self, string, key, sep, value
    ):
        expected = KeyValueArg(orig=string, key=key, sep=sep, value=value)
        actual = self.key_value_arg(string)
        assert actual == expected

    def test_escape_longsep(self):
        items = RequestItems.from_args([
            self.key_value_arg(r'bob\:==foo'),
        ])
        assert items.params == {
            'bob:': 'foo'
        }

    def test_valid_items(self):
        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'),
            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('Embedded-Header:@' + FILE_PATH_ARG),
            self.key_value_arg('string-embed=@' + FILE_PATH_ARG),
            self.key_value_arg('param-embed==@' + FILE_PATH_ARG),
            self.key_value_arg('raw-json-embed:=@' + JSON_FILE_PATH_ARG),
        ])

        
        
        headers = dict(items.headers)
        assert headers == {
            'Header': 'value',
            'Unset-Header': None,
            'Empty-Header': '',
            'Embedded-Header': FILE_CONTENT.rstrip('\n')
        }

        
        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
        }

        
        assert items.params == {
            'query': 'value',
            'param-embed': FILE_CONTENT.rstrip('\n')
        }

        
        assert 'file' in items.files
        assert (items.files['file'][1].read().strip().
                decode() == FILE_CONTENT)

    def test_multiple_file_fields_with_same_field_name(self):
        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):
        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'),
        ]


class TestQuerystring:
    def test_query_string_params_in_url(self, httpbin):
        r = http('--print=Hhb', 'GET', httpbin.url + '/get?a=1&b=2')
        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

    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

    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


@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


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"


class TestLocalhostShorthand:
    def test_expand_localhost_shorthand(self):
        args = parser.parse_args(args=[':'], env=MockEnvironment())
        assert args.url == 'http://localhost'

    def test_expand_localhost_shorthand_with_slash(self):
        args = parser.parse_args(args=[':/'], env=MockEnvironment())
        assert args.url == 'http://localhost/'

    def test_expand_localhost_shorthand_with_port(self):
        args = parser.parse_args(args=[':3000'], env=MockEnvironment())
        assert args.url == 'http://localhost:3000'

    def test_expand_localhost_shorthand_with_path(self):
        args = parser.parse_args(args=[':/path'], env=MockEnvironment())
        assert args.url == 'http://localhost/path'

    def test_expand_localhost_shorthand_with_port_and_slash(self):
        args = parser.parse_args(args=[':3000/'], env=MockEnvironment())
        assert args.url == 'http://localhost:3000/'

    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'

    def test_dont_expand_shorthand_ipv6_as_shorthand(self):
        args = parser.parse_args(args=['::1'], env=MockEnvironment())
        assert args.url == 'http://::1'

    def test_dont_expand_longer_ipv6_as_shorthand(self):
        args = parser.parse_args(
            args=['::ffff:c000:0280'],
            env=MockEnvironment()
        )
        assert args.url == 'http://::ffff:c000:0280'

    def test_dont_expand_full_ipv6_as_shorthand(self):
        args = parser.parse_args(
            args=['0000:0000:0000:0000:0000:0000:0000:0001'],
            env=MockEnvironment()
        )
        assert args.url == 'http://0000:0000:0000:0000:0000:0000:0000:0001'


class TestArgumentParser:

    def setup_method(self, method):
        self.parser = httpie.cli.argparser.HTTPieArgumentParser()

    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/'
        self.parser.args.request_items = []
        self.parser.args.ignore_stdin = False
        self.parser.env = MockEnvironment()
        self.parser._guess_method()
        assert self.parser.args.method == 'GET'
        assert self.parser.args.url == 'http://example.com/'
        assert self.parser.args.request_items == []

    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/'
        self.parser.args.request_items = []
        self.parser.args.ignore_stdin = False
        self.parser.env = MockEnvironment()
        self.parser._guess_method()
        assert self.parser.args.method == 'GET'
        assert self.parser.args.url == 'http://example.com/'
        assert self.parser.args.request_items == []

    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'
        self.parser.args.request_items = []
        self.parser.args.ignore_stdin = False
        self.parser.env = MockEnvironment()
        self.parser._guess_method()
        assert self.parser.args.method == 'POST'
        assert self.parser.args.url == 'http://example.com/'
        assert self.parser.args.request_items == [
            KeyValueArg(key='data',
                        value='field',
                        sep='=',
                        orig='data=field')
        ]

    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'
        self.parser.args.request_items = []
        self.parser.args.ignore_stdin = False
        self.parser.env = MockEnvironment()
        self.parser._guess_method()
        assert self.parser.args.method == 'GET'
        assert self.parser.args.url == 'http://example.com/'
        assert self.parser.args.request_items, [
            KeyValueArg(key='test',
                        value='header',
                        sep=':',
                        orig='test:header')
        ]

    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'
        self.parser.args.request_items = [
            KeyValueArg(
                key='old_item', value='b', sep='=', orig='old_item=b')
        ]
        self.parser.args.ignore_stdin = False
        self.parser.env = MockEnvironment()
        self.parser._guess_method()
        assert self.parser.args.request_items, [
            KeyValueArg(key='new_item', value='a', sep='=', orig='new_item=a'),
            KeyValueArg(
                key='old_item', value='b', sep='=', orig='old_item=b'),
        ]


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

    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


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."

    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

    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):
        
        
        with pytest.raises(InvalidSchema):
            http('foo+bar-BAZ.123://bah')

    def test_invalid_scheme_via_via_default_scheme(self):
        
        
        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')