diff --git a/.travis.yml b/.travis.yml index 3be70c78..513cf330 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ python: - pypy - 3.1 - 3.2 -script: python tests/tests.py +script: python setup.py test install: - - pip install requests pygments - - "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]] || [[ $TRAVIS_PYTHON_VERSION == '3.1' ]]; then pip install argparse; fi" - + - pip install . --use-mirrors diff --git a/README.rst b/README.rst index 440e66c0..3e07b89e 100644 --- a/README.rst +++ b/README.rst @@ -104,28 +104,28 @@ Flags Most of the flags mirror the arguments understood by ``requests.request``. See ``http -h`` for more details:: usage: http [-h] [--version] [--json | --form] [--traceback] - [--pretty | --ugly] - [--print OUTPUT_OPTIONS | --verbose | --headers | --body] - [--style STYLE] [--auth AUTH] [--auth-type {basic,digest}] - [--verify VERIFY] [--proxy PROXY] [--allow-redirects] - [--timeout TIMEOUT] - [METHOD] URL [ITEM [ITEM ...]] + [--pretty | --ugly] + [--print OUTPUT_OPTIONS | --verbose | --headers | --body] + [--style STYLE] [--auth AUTH] [--auth-type {basic,digest}] + [--verify VERIFY] [--proxy PROXY] [--allow-redirects] + [--timeout TIMEOUT] + [METHOD] URL [ITEM [ITEM ...]] HTTPie - cURL for humans. positional arguments: METHOD The HTTP method to be used for the request (GET, POST, - PUT, DELETE, PATCH, ...). If this argument is omitted - then httpie will guess HTTP method. If there is either - simple data field or JSON data field or file field - presents then method is POST otherwise it is GET. + PUT, DELETE, PATCH, ...). If this argument is omitted, + then HTTPie will guess the HTTP method. If there is + some data to be sent, then it will be POST, otherwise + GET. URL The protocol defaults to http:// if the URL does not include one. ITEM A key-value pair whose type is defined by the separator used. It can be an HTTP header (header:value), a data field to be used in the request body (field_name=value), a raw JSON data field - (field_name:=value) or a file field + (field_name:=value), or a file field (field_name@/path/to/file). You can use a backslash to escape a colliding separator in the field name. @@ -159,12 +159,12 @@ Most of the flags mirror the arguments understood by ``requests.request``. See ` --style STYLE, -s STYLE Output coloring style, one of autumn, borland, bw, colorful, default, emacs, friendly, fruity, manni, - monokai, murphy, native, pastie, perldoc, solarized, - tango, trac, vim, vs. Defaults to solarized. For this - option to work properly, please make sure that the - $TERM environment variable is set to "xterm-256color" - or similar (e.g., via `export TERM=xterm-256color' in - your ~/.bashrc). + monokai, murphy, native, pastie, perldoc, rrt, + solarized, tango, trac, vim, vs. Defaults to + solarized. For this option to work properly, please + make sure that the $TERM environment variable is set + to "xterm-256color" or similar (e.g., via `export TERM + =xterm-256color' in your ~/.bashrc). --auth AUTH, -a AUTH username:password --auth-type {basic,digest} The authentication mechanism to be used. Defaults to @@ -189,7 +189,7 @@ Contribute If you have found a bug or have a feature request, the `issue tracker `_ is the place to start a discussion about it. -To contribute code or documentation, please first browse the exsiting issues to see if the feature/bug has previously been discussed. Then fork `the repository `_, make changes in your develop branch and submit a pull request. Note: Pull requests with tests and documentation are 53.6% more awesome :) +To contribute code or documentation, please first browse the existing issues to see if the feature/bug has previously been discussed. Then fork `the repository `_, make changes in your develop branch and submit a pull request. Note: Pull requests with tests and documentation are 53.6% more awesome :) Before a pull requests is submitted, it's a good idea to run the existing suite of tests:: @@ -206,7 +206,9 @@ Before a pull requests is submitted, it's a good idea to run the existing suite Changelog --------- -* `New in development version `_ +* `0.2.2dev `_ + * The ``METHOD`` positional argument can now be omitted (defaults to ``GET``, or to ``POST`` with data). + * Fixed --verbose --form. * `0.2.1 `_ (2012-06-13) * Added compatibility with ``requests-0.12.1``. * Dropped custom JSON and HTTP lexers in favor of the ones newly included in ``pygments-1.5``. diff --git a/httpie/cli.py b/httpie/cli.py index 92eb43d3..6e84bf04 100644 --- a/httpie/cli.py +++ b/httpie/cli.py @@ -14,7 +14,7 @@ def _(text): desc = '%s ' -parser = cliparse.HTTPieArgumentParser(description=desc % __doc__.strip(),) +parser = cliparse.Parser(description=desc % __doc__.strip(),) parser.add_argument('--version', action='version', version=__version__) @@ -176,8 +176,8 @@ parser.add_argument( help=_(''' The HTTP method to be used for the request (GET, POST, PUT, DELETE, PATCH, ...). - If this argument is omitted then httpie will guess the HTTP method. - If there is any data to be sent then method is POST otherwise it is GET. + If this argument is omitted, then HTTPie will guess the HTTP method. + If there is some data to be sent, then it will be POST, otherwise GET. ''') ) parser.add_argument( @@ -200,7 +200,7 @@ parser.add_argument( A key-value pair whose type is defined by the separator used. It can be an HTTP header (header:value), a data field to be used in the request body (field_name=value), - a raw JSON data field (field_name:=value) + a raw JSON data field (field_name:=value), or a file field (field_name@/path/to/file). You can use a backslash to escape a colliding separator in the field name. ''') diff --git a/httpie/cliparse.py b/httpie/cliparse.py index ef86f66a..5ccb790b 100644 --- a/httpie/cliparse.py +++ b/httpie/cliparse.py @@ -25,11 +25,11 @@ SEP_HEADERS = SEP_COMMON SEP_DATA = '=' SEP_DATA_RAW_JSON = ':=' SEP_FILES = '@' -DATA_ITEM_SEPARATORS = { +DATA_ITEM_SEPARATORS = [ SEP_DATA, SEP_DATA_RAW_JSON, SEP_FILES -} +] OUT_REQ_HEADERS = 'H' @@ -47,12 +47,12 @@ PRETTIFY_STDOUT_TTY_ONLY = object() DEFAULT_UA = 'HTTPie/%s' % __version__ -class HTTPieArgumentParser(argparse.ArgumentParser): +class Parser(argparse.ArgumentParser): def parse_args(self, args=None, namespace=None, stdin=sys.stdin, stdin_isatty=sys.stdin.isatty()): - args = super(HTTPieArgumentParser, self).parse_args(args, namespace) + args = super(Parser, self).parse_args(args, namespace) self._validate_output_options(args) self._validate_auth_options(args) self._guess_method(args, stdin_isatty) @@ -68,28 +68,9 @@ class HTTPieArgumentParser(argparse.ArgumentParser): args.data = stdin.read() def _guess_method(self, args, stdin_isatty=sys.stdin.isatty()): - """Suggests HTTP method by positional argument values. - - In following description by data item it means one of: - * simple data item (key=value) - * JSON raw item (key:=value) - * file item (key@value) - - If METHOD argument is omitted and no data ITEM is given then method is GET: - http http://example.com/ - - is shortcut for - - http GET http://example.com. - - If METHOD argument is omitted but at least one data ITEM - is present then method is POST: - http http://example.com/ hello=world - - is shortcut for - - http POST http://example.com hello=world. - - If METHOD is specified then http behaves as it is now. - - The first argument should be treated as method - if it matches ^[a-zA-Z]+$ regexp. Otherwise it is url. + """ + Set `args.method`, if not specified, to either POST or GET + based on whether the request has data or not. """ if args.method is None: diff --git a/setup.py b/setup.py index 93ec3685..b1d5cb48 100644 --- a/setup.py +++ b/setup.py @@ -5,12 +5,14 @@ import httpie if sys.argv[-1] == 'test': - os.system('python tests/tests.py') - sys.exit() + sys.exit(os.system('python tests/tests.py')) -# Debian has only requests==0.10.1 and httpie.deb depends on that. -requirements = ['requests>=0.10.1', 'Pygments>=1.5'] +requirements = [ + # Debian has only requests==0.10.1 and httpie.deb depends on that. + 'requests>=0.10.1', + 'Pygments>=1.5' +] if sys.version_info[:2] in ((2, 6), (3, 1)): # argparse has been added in Python 3.2 / 2.7 requirements.append('argparse>=1.2.1') diff --git a/tests/test_cliparse.py b/tests/test_cliparse.py deleted file mode 100644 index 05d42756..00000000 --- a/tests/test_cliparse.py +++ /dev/null @@ -1,75 +0,0 @@ -import unittest -from argparse import Namespace -from httpie.cliparse import HTTPieArgumentParser, KeyValue - - -__author__ = 'vladimir' - - -class HTTPieArgumentParserTestCase(unittest.TestCase): - - def setUp(self): - self.HTTPieArgumentParserStub = type(HTTPieArgumentParser.__name__, (HTTPieArgumentParser,), {}) - self.HTTPieArgumentParserStub.__init__ = lambda self: None - self.parser = HTTPieArgumentParser() - - def test_guess_when_method_set_and_valid(self): - args = Namespace() - args.method = 'GET' - args.url = 'http://example.com/' - args.items = [] - - self.parser._guess_method(args) - - self.assertEquals(args.method, 'GET') - self.assertEquals(args.url, 'http://example.com/') - self.assertEquals(args.items, []) - - def test_guess_when_method_not_set(self): - args = Namespace() - args.method = None - args.url = 'http://example.com/' - args.items = [] - - self.parser._guess_method(args) - - self.assertEquals(args.method, 'GET') - self.assertEquals(args.url, 'http://example.com/') - self.assertEquals(args.items, []) - - def test_guess_when_method_set_but_invalid_and_data_field(self): - args = Namespace() - args.method = 'http://example.com/' - args.url = 'data=field' - args.items = [] - - self.parser._guess_method(args) - - self.assertEquals(args.method, 'POST') - self.assertEquals(args.url, 'http://example.com/') - self.assertEquals(args.items, [KeyValue(key='data', value='field', sep='=', orig='data=field')]) - - def test_guess_when_method_set_but_invalid_and_header_field(self): - args = Namespace() - args.method = 'http://example.com/' - args.url = 'test:header' - args.items = [] - - self.parser._guess_method(args) - - self.assertEquals(args.method, 'GET') - self.assertEquals(args.url, 'http://example.com/') - self.assertEquals(args.items, [KeyValue(key='test', value='header', sep=':', orig='test:header')]) - - def test_guess_when_method_set_but_invalid_and_item_exists(self): - args = Namespace() - args.method = 'http://example.com/' - args.url = 'new_item=a' - args.items = [KeyValue(key='old_item', value='b', sep='=', orig='old_item=b')] - - self.parser._guess_method(args) - - self.assertEquals(args.items, [ - KeyValue(key='new_item', value='a', sep='=', orig='new_item=a'), - KeyValue(key='old_item', value='b', sep='=', orig='old_item=b'), - ]) diff --git a/tests/tests.py b/tests/tests.py index 7b0d9a09..e2fd8c5e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,24 +1,36 @@ -# coding:utf-8 -import os -import sys +""" +High-level tests. + +""" import unittest import argparse -from requests.compat import is_py26 +import os +import sys import tempfile +from requests.compat import is_py26 +################################################################# +# Utils/setup +################################################################# + +# HACK: Prepend ../ to PYTHONPATH so that we can import httpie form there. TESTS_ROOT = os.path.dirname(__file__) sys.path.insert(0, os.path.realpath(os.path.join(TESTS_ROOT, '..'))) -from httpie import __main__ -from httpie import cliparse +from httpie import __main__, cliparse -TEST_FILE = os.path.join(TESTS_ROOT, 'file.txt') +TEST_FILE_PATH = os.path.join(TESTS_ROOT, 'file.txt') TERMINAL_COLOR_PRESENCE_CHECK = '\x1b[' def http(*args, **kwargs): + """ + Invoke `httpie.__main__.main` with `args` and `kwargs`, + and return a unicode response. + + """ http_kwargs = { 'stdin_isatty': True, 'stdout_isatty': False @@ -32,7 +44,7 @@ def http(*args, **kwargs): return response -class BaseTest(unittest.TestCase): +class BaseTestCase(unittest.TestCase): if is_py26: def assertIn(self, member, container, msg=None): @@ -46,7 +58,149 @@ class BaseTest(unittest.TestCase): self.assertEqual(sorted(d1.values()), sorted(d2.values()), msg) -class TestItemParsing(BaseTest): +################################################################# +# High-level tests using httpbin.org. +################################################################# + +class HTTPieTest(BaseTestCase): + + def test_GET(self): + r = http('GET', 'http://httpbin.org/get') + self.assertIn('HTTP/1.1 200', r) + + def test_DELETE(self): + r = http('DELETE', 'http://httpbin.org/delete') + self.assertIn('HTTP/1.1 200', r) + + def test_PUT(self): + r = http('PUT', 'http://httpbin.org/put', 'foo=bar') + self.assertIn('HTTP/1.1 200', r) + self.assertIn('"foo": "bar"', r) + + def test_POST_JSON_data(self): + r = http('POST', 'http://httpbin.org/post', 'foo=bar') + self.assertIn('HTTP/1.1 200', r) + self.assertIn('"foo": "bar"', r) + + def test_GET_JSON_implicit_accept(self): + r = http('-j', 'GET', 'http://httpbin.org/headers') + self.assertIn('HTTP/1.1 200', r) + self.assertIn('"Accept": "application/json"', r) + + def test_GET_JSON_explicit_accept(self): + r = http('-j', 'GET', 'http://httpbin.org/headers', 'Accept:application/xml') + self.assertIn('HTTP/1.1 200', r) + self.assertIn('"Accept": "application/xml"', r) + + def test_POST_form(self): + response = http('--form', 'POST', 'http://httpbin.org/post', 'foo=bar') + self.assertIn('"foo": "bar"', response) + + def test_POST_stdin(self): + r = http('--form', 'POST', 'http://httpbin.org/post', + stdin=open(TEST_FILE_PATH), stdin_isatty=False) + self.assertIn('HTTP/1.1 200', r) + + def test_headers(self): + response = http('GET', 'http://httpbin.org/headers', 'Foo:bar') + self.assertIn('"User-Agent": "HTTPie', response) + self.assertIn('"Foo": "bar"', response) + + +class ImplicitHTTPMethodTest(BaseTestCase): + + def test_implicit_GET(self): + r = http('http://httpbin.org/get') + self.assertIn('HTTP/1.1 200', r) + + def test_implicit_GET_with_headers(self): + r = http('http://httpbin.org/headers', 'Foo:bar') + self.assertIn('"Foo": "bar"', r) + self.assertIn('HTTP/1.1 200', r) + + def test_implicit_POST_json(self): + r = http('http://httpbin.org/post', 'hello=world') + self.assertIn('"hello": "world"', r) + self.assertIn('HTTP/1.1 200', r) + + def test_implicit_POST_form(self): + r = http('--form', 'http://httpbin.org/post', 'foo=bar') + self.assertIn('"foo": "bar"', r) + self.assertIn('HTTP/1.1 200', r) + + def test_implicit_POST_stdin(self): + r = http('--form', 'http://httpbin.org/post', + stdin=open(TEST_FILE_PATH), stdin_isatty=False) + self.assertIn('HTTP/1.1 200', r) + + +class PrettyFlagTest(BaseTestCase): + """Test the --pretty / --ugly flag handling.""" + + def test_pretty_enabled_by_default(self): + r = http('GET', 'http://httpbin.org/get', stdout_isatty=True) + self.assertIn(TERMINAL_COLOR_PRESENCE_CHECK, r) + + def test_pretty_enabled_by_default_unless_stdin_redirected(self): + r = http('GET', 'http://httpbin.org/get', stdout_isatty=False) + self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r) + + def test_force_pretty(self): + r = http('--pretty', 'GET', 'http://httpbin.org/get', stdout_isatty=False) + self.assertIn(TERMINAL_COLOR_PRESENCE_CHECK, r) + + def test_force_ugly(self): + r = http('--ugly', 'GET', 'http://httpbin.org/get', stdout_isatty=True) + self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r) + + +class VerboseFlagTest(BaseTestCase): + + def test_verbose(self): + r = http('--verbose', 'GET', 'http://httpbin.org/get', 'test-header:__test__') + self.assertEqual(r.count('__test__'), 2) + + def test_verbose_form(self): + # https://github.com/jkbr/httpie/issues/53 + r = http('--verbose', '--form', 'POST', 'http://httpbin.org/post', 'foo=bar', 'baz=bar') + self.assertIn('foo=bar&baz=bar', r) + + +class MultipartFormDataFileUploadTest(BaseTestCase): + + def test_non_existent_file_raises_parse_error(self): + self.assertRaises(cliparse.ParseError, http, + '--form', '--traceback', + 'POST', 'http://httpbin.org/post', + 'foo@/__does_not_exist__') + + def test_upload_ok(self): + r = http('--form', 'POST', 'http://httpbin.org/post', + 'test-file@%s' % TEST_FILE_PATH, 'foo=bar') + self.assertIn('"test-file": "__test_file_content__', r) + self.assertIn('"foo": "bar"', r) + + +class AuthTest(BaseTestCase): + + def test_basic_auth(self): + r = http('--auth', 'user:password', + 'GET', 'httpbin.org/basic-auth/user/password') + self.assertIn('"authenticated": true', r) + self.assertIn('"user": "user"', r) + + def test_digest_auth(self): + r = http('--auth-type=digest', '--auth', 'user:password', + 'GET', 'httpbin.org/digest-auth/auth/user/password') + self.assertIn('"authenticated": true', r) + self.assertIn('"user": "user"', r) + + +################################################################# +# CLI argument parsing related tests. +################################################################# + +class ItemParsingTest(BaseTestCase): def setUp(self): self.key_value_type = cliparse.KeyValueType( @@ -70,7 +224,7 @@ class TestItemParsing(BaseTest): # data self.key_value_type('baz\\=bar=foo'), # files - self.key_value_type('bar\\@baz@%s' % TEST_FILE) + self.key_value_type('bar\\@baz@%s' % TEST_FILE_PATH) ]) self.assertDictEqual(headers, { 'foo:bar': 'baz', @@ -98,7 +252,7 @@ class TestItemParsing(BaseTest): self.key_value_type('eh:'), self.key_value_type('ed='), self.key_value_type('bool:=true'), - self.key_value_type('test-file@%s' % TEST_FILE), + self.key_value_type('test-file@%s' % TEST_FILE_PATH), ]) self.assertDictEqual(headers, { 'header': 'value', @@ -114,112 +268,77 @@ class TestItemParsing(BaseTest): self.assertIn('test-file', files) -class TestHTTPie(BaseTest): +class HTTPieArgumentParserTestCase(unittest.TestCase): - def test_get(self): - http('GET', 'http://httpbin.org/get') + def setUp(self): + self.parser = cliparse.Parser() - def test_verbose(self): - r = http('--verbose', 'GET', 'http://httpbin.org/get', 'test-header:__test__') - self.assertEqual(r.count('__test__'), 2) + def test_guess_when_method_set_and_valid(self): + args = argparse.Namespace() + args.method = 'GET' + args.url = 'http://example.com/' + args.items = [] - def test_verbose_form(self): - # https://github.com/jkbr/httpie/issues/53 - r = http('--verbose', '--form', 'POST', 'http://httpbin.org/post', 'foo=bar', 'baz=bar') - self.assertIn('foo=bar&baz=bar', r) + self.parser._guess_method(args) - def test_json(self): - response = http('POST', 'http://httpbin.org/post', 'foo=bar') - self.assertIn('"foo": "bar"', response) - response2 = http('-j', 'GET', 'http://httpbin.org/headers') - self.assertIn('"Accept": "application/json"', response2) - response3 = http('-j', 'GET', 'http://httpbin.org/headers', 'Accept:application/xml') - self.assertIn('"Accept": "application/xml"', response3) + self.assertEquals(args.method, 'GET') + self.assertEquals(args.url, 'http://example.com/') + self.assertEquals(args.items, []) - def test_form(self): - response = http('--form', 'POST', 'http://httpbin.org/post', 'foo=bar') - self.assertIn('"foo": "bar"', response) + def test_guess_when_method_not_set(self): + args = argparse.Namespace() + args.method = None + args.url = 'http://example.com/' + args.items = [] - def test_headers(self): - response = http('GET', 'http://httpbin.org/headers', 'Foo:bar') - self.assertIn('"User-Agent": "HTTPie', response) - self.assertIn('"Foo": "bar"', response) + self.parser._guess_method(args) + self.assertEquals(args.method, 'GET') + self.assertEquals(args.url, 'http://example.com/') + self.assertEquals(args.items, []) -class TestImplicitHTTPMethod(BaseTest): + def test_guess_when_method_set_but_invalid_and_data_field(self): + args = argparse.Namespace() + args.method = 'http://example.com/' + args.url = 'data=field' + args.items = [] - def test_implicit_GET(self): - r = http('http://httpbin.org/get') - self.assertIn('HTTP/1.1 200', r) + self.parser._guess_method(args) - def test_implicit_GET_with_headers(self): - r = http('http://httpbin.org/headers', 'Foo:bar') - self.assertIn('"Foo": "bar"', r) - self.assertIn('HTTP/1.1 200', r) + self.assertEquals(args.method, 'POST') + self.assertEquals(args.url, 'http://example.com/') + self.assertEquals( + args.items, + [cliparse.KeyValue(key='data', value='field', sep='=', orig='data=field')]) - def test_implicit_POST_json(self): - r = http('http://httpbin.org/post', 'hello=world') - self.assertIn('"hello": "world"', r) - self.assertIn('HTTP/1.1 200', r) + def test_guess_when_method_set_but_invalid_and_header_field(self): + args = argparse.Namespace() + args.method = 'http://example.com/' + args.url = 'test:header' + args.items = [] - def test_implicit_POST_form(self): - r = http('--form', 'http://httpbin.org/post', 'foo=bar') - self.assertIn('"foo": "bar"', r) - self.assertIn('HTTP/1.1 200', r) + self.parser._guess_method(args) - def test_implicit_POST_stdin(self): - r = http('--form', 'http://httpbin.org/post', - stdin=open(TEST_FILE), stdin_isatty=False) - self.assertIn('HTTP/1.1 200', r) + self.assertEquals(args.method, 'GET') + self.assertEquals(args.url, 'http://example.com/') + self.assertEquals( + args.items, + [cliparse.KeyValue(key='test', value='header', sep=':', orig='test:header')]) + def test_guess_when_method_set_but_invalid_and_item_exists(self): + args = argparse.Namespace() + args.method = 'http://example.com/' + args.url = 'new_item=a' + args.items = [ + cliparse.KeyValue(key='old_item', value='b', sep='=', orig='old_item=b') + ] -class TestPrettyFlag(BaseTest): - """Test the --pretty / --ugly flag handling.""" + self.parser._guess_method(args) - def test_pretty_enabled_by_default(self): - r = http('GET', 'http://httpbin.org/get', stdout_isatty=True) - self.assertIn(TERMINAL_COLOR_PRESENCE_CHECK, r) - - def test_pretty_enabled_by_default_unless_stdin_redirected(self): - r = http('GET', 'http://httpbin.org/get', stdout_isatty=False) - self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r) - - def test_force_pretty(self): - r = http('--pretty', 'GET', 'http://httpbin.org/get', stdout_isatty=False) - self.assertIn(TERMINAL_COLOR_PRESENCE_CHECK, r) - - def test_force_ugly(self): - r = http('--ugly', 'GET', 'http://httpbin.org/get', stdout_isatty=True) - self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r) - - -class TestFileUpload(BaseTest): - - def test_non_existent_file_raises_parse_error(self): - self.assertRaises(cliparse.ParseError, http, - '--form', '--traceback', - 'POST', 'http://httpbin.org/post', - 'foo@/__does_not_exist__') - - def test_upload_ok(self): - r = http('--form', 'POST', 'http://httpbin.org/post', - 'test-file@%s' % TEST_FILE) - self.assertIn('"test-file": "__test_file_content__', r) - - -class TestAuth(BaseTest): - - def test_basic_auth(self): - r = http('--auth', 'user:password', - 'GET', 'httpbin.org/basic-auth/user/password') - self.assertIn('"authenticated": true', r) - self.assertIn('"user": "user"', r) - - def test_digest_auth(self): - r = http('--auth-type=digest', '--auth', 'user:password', - 'GET', 'httpbin.org/digest-auth/auth/user/password') - self.assertIn('"authenticated": true', r) - self.assertIn('"user": "user"', r) + self.assertEquals(args.items, [ + cliparse.KeyValue(key='new_item', value='a', sep='=', orig='new_item=a'), + cliparse.KeyValue(key='old_item', value='b', sep='=', orig='old_item=b'), + ]) if __name__ == '__main__':