import json import pytest import responses from httpie.cli.constants import PRETTY_MAP from httpie.cli.exceptions import ParseError from httpie.compat import is_windows from httpie.output.formatters.colors import ColorFormatter from httpie.utils import JsonDictPreservingDuplicateKeys from .fixtures import JSON_WITH_DUPE_KEYS_FILE_PATH from .utils import MockEnvironment, http, DUMMY_URL TEST_JSON_XXSI_PREFIXES = [ r")]}',\n", ")]}',", 'while(1);', 'for(;;)', ')', ']', '}', ] TEST_JSON_VALUES = [ # FIXME: missing int & float {}, {'a': 0, 'b': 0}, [], ['a', 'b'], 'foo', True, False, None, ] TEST_PREFIX_TOKEN_COLOR = '\x1b[38;5;15m' if is_windows else '\x1b[04m\x1b[91m' JSON_WITH_DUPES_RAW = '{"key": 15, "key": 15, "key": 3, "key": 7}' JSON_WITH_DUPES_FORMATTED_SORTED = '''{ "key": 3, "key": 7, "key": 15, "key": 15 }''' JSON_WITH_DUPES_FORMATTED_UNSORTED = '''{ "key": 15, "key": 15, "key": 3, "key": 7 }''' @pytest.mark.parametrize('data_prefix', TEST_JSON_XXSI_PREFIXES) @pytest.mark.parametrize('json_data', TEST_JSON_VALUES) @pytest.mark.parametrize('pretty', PRETTY_MAP.keys()) @responses.activate def test_json_formatter_with_body_preceded_by_non_json_data(data_prefix, json_data, pretty): """Test JSON bodies preceded by non-JSON data.""" body = data_prefix + json.dumps(json_data) content_type = 'application/json;charset=utf8' responses.add( responses.GET, DUMMY_URL, body=body, content_type=content_type, ) colored_output = pretty in {'all', 'colors'} env = MockEnvironment(colors=256) if colored_output else None r = http('--pretty', pretty, DUMMY_URL, env=env) indent = None if pretty in {'none', 'colors'} else 4 expected_body = data_prefix + json.dumps(json_data, indent=indent) if colored_output: fmt = ColorFormatter(env, format_options={'json': {'format': True, 'indent': 4}}) expected_body = fmt.format_body(expected_body, content_type) # Check to ensure the non-JSON data prefix is colored only one time, # meaning it was correctly handled as a whole. assert TEST_PREFIX_TOKEN_COLOR + data_prefix in expected_body, expected_body assert expected_body in r @responses.activate def test_duplicate_keys_support_from_response(): """JSON with duplicate keys should be handled correctly.""" responses.add( responses.GET, DUMMY_URL, body=JSON_WITH_DUPES_RAW, content_type='application/json', ) args = ('--pretty', 'format', DUMMY_URL) # Check implicit --sorted if JsonDictPreservingDuplicateKeys.SUPPORTS_SORTING: r = http(*args) assert JSON_WITH_DUPES_FORMATTED_SORTED in r # Check --unsorted r = http(*args, '--unsorted') assert JSON_WITH_DUPES_FORMATTED_UNSORTED in r def test_duplicate_keys_support_from_input_file(): """JSON file with duplicate keys should be handled correctly.""" args = ( '--verbose', '--offline', DUMMY_URL, f'@{JSON_WITH_DUPE_KEYS_FILE_PATH}', ) # Check implicit --sorted if JsonDictPreservingDuplicateKeys.SUPPORTS_SORTING: r = http(*args) assert JSON_WITH_DUPES_FORMATTED_SORTED in r # Check --unsorted r = http(*args, '--unsorted') assert JSON_WITH_DUPES_FORMATTED_UNSORTED in r @pytest.mark.parametrize("value", [ 1, 1.1, True, 'some_value' ]) def test_simple_json_arguments_with_non_json(httpbin, value): r = http( '--form', httpbin + '/post', f'option:={json.dumps(value)}', ) assert r.json['form'] == {'option': str(value)} @pytest.mark.parametrize("request_type", [ "--form", "--multipart", ]) @pytest.mark.parametrize("value", [ [1, 2, 3], {'a': 'b'}, None ]) def test_complex_json_arguments_with_non_json(httpbin, request_type, value): with pytest.raises(ParseError) as cm: http( request_type, httpbin + '/post', f'option:={json.dumps(value)}', ) cm.match('Can\'t use complex JSON value types') @pytest.mark.parametrize('input_json, expected_json', [ # Examples taken from https://www.w3.org/TR/html-json-forms/ ( ['bottle-on-wall:=1', 'bottle-on-wall:=2', 'bottle-on-wall:=3'], {'bottle-on-wall': [1, 2, 3]}, ), ( ['pet[species]=Dahut', 'pet[name]:="Hypatia"', 'kids[1]=Thelma', 'kids[0]:="Ashley"'], {'pet': {'species': 'Dahut', 'name': 'Hypatia'}, 'kids': ['Ashley', 'Thelma']}, ), ( ['pet[0][species]=Dahut', 'pet[0][name]=Hypatia', 'pet[1][species]=Felis Stultus', 'pet[1][name]:="Billie"'], {'pet': [{'species': 'Dahut', 'name': 'Hypatia'}, {'species': 'Felis Stultus', 'name': 'Billie'}]}, ), ( ['wow[such][deep][3][much][power][!]=Amaze'], {'wow': {'such': {'deep': [None, None, None, {'much': {'power': {'!': 'Amaze'}}}]}}}, ), ( ['mix=scalar', 'mix[0]=array 1', 'mix[2]:="array 2"', 'mix[key]:="key key"', 'mix[car]=car key'], {'mix': {'': 'scalar', '0': 'array 1', '2': 'array 2', 'key': 'key key', 'car': 'car key'}}, ), ( ['highlander[]=one'], {'highlander': ['one']}, ), ( ['error[good]=BOOM!', 'error[bad:="BOOM BOOM!"'], {'error': {'good': 'BOOM!'}, 'error[bad': 'BOOM BOOM!'}, ), ( ['special[]:=true', 'special[]:=false', 'special[]:="true"', 'special[]:=null'], {'special': [True, False, 'true', None]}, ), ( [r'\[\]:=1', r'escape\[d\]:=1', r'escaped\[\]:=1', r'e\[s\][c][a][p]\[ed\][]:=1'], {'[]': 1, 'escape[d]': 1, 'escaped[]': 1, 'e[s]': {'c': {'a': {'p': {'[ed]': [1]}}}}}, ), ( ['[]:=1', '[]=foo'], [1, 'foo'], ), ( [']:=1', '[]1:=1', '[1]]:=1'], {']': 1, '[]1': 1, '[1]]': 1}, ), ]) def test_nested_json_syntax(input_json, expected_json, httpbin_both): r = http(httpbin_both + '/post', *input_json) assert r.json['json'] == expected_json def test_nested_json_sparse_array(httpbin_both): r = http(httpbin_both + '/post', 'test[0]:=1', 'test[100]:=1') assert len(r.json['json']['test']) == 101