2021-09-21 11:15:43 +02:00
import json
import pytest
import responses
from httpie . cli . constants import PRETTY_MAP
2021-11-26 13:45:46 +02:00
from httpie . cli . exceptions import ParseError
2022-01-04 11:04:20 +02:00
from httpie . cli . nested_json import HTTPieSyntaxError
2021-09-21 11:15:43 +02:00
from httpie . output . formatters . colors import ColorFormatter
2021-09-21 19:07:59 +02:00
from httpie . utils import JsonDictPreservingDuplicateKeys
2021-09-21 11:15:43 +02:00
2022-01-04 11:04:20 +02:00
from . fixtures import (
FILE_CONTENT ,
FILE_PATH ,
JSON_FILE_CONTENT ,
JSON_FILE_PATH ,
JSON_WITH_DUPE_KEYS_FILE_PATH ,
)
from . utils import DUMMY_URL , MockEnvironment , http
2021-10-06 17:27:07 +02:00
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 ,
]
2022-01-14 18:47:10 +02:00
TEST_PREFIX_TOKEN_COLOR = ' \x1b [04m \x1b [91m '
2021-09-21 11:15:43 +02:00
2021-09-21 19:07:59 +02:00
JSON_WITH_DUPES_RAW = ' { " key " : 15, " key " : 15, " key " : 3, " key " : 7} '
2022-01-04 11:04:20 +02:00
JSON_WITH_DUPES_FORMATTED_SORTED = """ {
2021-09-21 19:07:59 +02:00
" key " : 3 ,
" key " : 7 ,
" key " : 15 ,
" key " : 15
2022-01-04 11:04:20 +02:00
} """
JSON_WITH_DUPES_FORMATTED_UNSORTED = """ {
2021-09-21 19:07:59 +02:00
" key " : 15 ,
" key " : 15 ,
" key " : 3 ,
" key " : 7
2022-01-04 11:04:20 +02:00
} """
2021-09-21 19:07:59 +02:00
2021-09-21 11:15:43 +02:00
@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
2022-01-04 11:04:20 +02:00
def test_json_formatter_with_body_preceded_by_non_json_data (
data_prefix , json_data , pretty
) :
2021-09-21 11:15:43 +02:00
""" Test JSON bodies preceded by non-JSON data. """
body = data_prefix + json . dumps ( json_data )
2021-10-06 17:27:07 +02:00
content_type = ' application/json;charset=utf8 '
responses . add (
responses . GET ,
DUMMY_URL ,
body = body ,
content_type = content_type ,
)
colored_output = pretty in { ' all ' , ' colors ' }
2021-09-21 11:15:43 +02:00
env = MockEnvironment ( colors = 256 ) if colored_output else None
2021-10-06 17:27:07 +02:00
r = http ( ' --pretty ' , pretty , DUMMY_URL , env = env )
2021-09-21 11:15:43 +02:00
2021-10-06 17:27:07 +02:00
indent = None if pretty in { ' none ' , ' colors ' } else 4
2021-09-21 11:15:43 +02:00
expected_body = data_prefix + json . dumps ( json_data , indent = indent )
if colored_output :
2022-01-04 11:04:20 +02:00
fmt = ColorFormatter (
env , format_options = { ' json ' : { ' format ' : True , ' indent ' : 4 } }
)
2021-09-21 11:15:43 +02:00
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.
2022-01-04 11:04:20 +02:00
assert (
TEST_PREFIX_TOKEN_COLOR + data_prefix in expected_body
) , expected_body
2021-09-21 11:15:43 +02:00
assert expected_body in r
2021-09-21 19:07:59 +02:00
@responses.activate
def test_duplicate_keys_support_from_response ( ) :
""" JSON with duplicate keys should be handled correctly. """
2021-10-06 17:27:07 +02:00
responses . add (
responses . GET ,
DUMMY_URL ,
body = JSON_WITH_DUPES_RAW ,
content_type = ' application/json ' ,
)
args = ( ' --pretty ' , ' format ' , DUMMY_URL )
2021-09-21 19:07:59 +02:00
# 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. """
2021-10-06 17:27:07 +02:00
args = (
' --verbose ' ,
' --offline ' ,
DUMMY_URL ,
f ' @ { JSON_WITH_DUPE_KEYS_FILE_PATH } ' ,
)
2021-09-21 19:07:59 +02:00
# 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
2021-11-26 13:45:46 +02:00
2022-01-04 11:04:20 +02:00
@pytest.mark.parametrize ( ' value ' , [ 1 , 1.1 , True , ' some_value ' ] )
2021-11-26 13:45:46 +02:00
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 ) }
2022-01-04 11:04:20 +02:00
@pytest.mark.parametrize (
' request_type ' ,
[
' --form ' ,
' --multipart ' ,
] ,
)
@pytest.mark.parametrize ( ' value ' , [ [ 1 , 2 , 3 ] , { ' a ' : ' b ' } , None ] )
2021-11-26 13:45:46 +02:00
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 ) } ' ,
)
2022-01-04 11:04:20 +02:00
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[2]=something ' , ' mix[4]:= " something 2 " ' ] ,
{ ' mix ' : [ ' scalar ' , None , ' something ' , None , ' something 2 ' ] } ,
) ,
(
[ ' highlander[]=one ' ] ,
{ ' highlander ' : [ ' one ' ] } ,
) ,
(
[ ' error[good]=BOOM! ' , r ' 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 ' ] ,
) ,
(
[ r ' \ ]:=1 ' , r ' \ [ \ ]1:=1 ' , r ' \ [1 \ ] \ ]:=1 ' ] ,
{ ' ] ' : 1 , ' []1 ' : 1 , ' [1]] ' : 1 } ,
) ,
(
[
r ' foo \ [bar \ ][baz]:=1 ' ,
r ' foo \ [bar \ ] \ [baz \ ]:=3 ' ,
r ' foo[bar][ \ [baz \ ]]:=4 ' ,
] ,
{
' foo[bar] ' : { ' baz ' : 1 } ,
' foo[bar][baz] ' : 3 ,
' foo ' : { ' bar ' : { ' [baz] ' : 4 } } ,
} ,
) ,
(
[ ' key[]:=1 ' , ' key[][]:=2 ' , ' key[][][]:=3 ' , ' key[][][]:=4 ' ] ,
{ ' key ' : [ 1 , [ 2 ] , [ [ 3 ] ] , [ [ 4 ] ] ] } ,
) ,
(
[ ' x[0]:=1 ' , ' x[]:=2 ' , ' x[]:=3 ' , ' x[][]:=4 ' , ' x[][]:=5 ' ] ,
{ ' x ' : [ 1 , 2 , 3 , [ 4 ] , [ 5 ] ] } ,
) ,
(
[
f ' x=@ { FILE_PATH } ' ,
f ' y[z]=@ { FILE_PATH } ' ,
f ' q[u][]:=@ { JSON_FILE_PATH } ' ,
] ,
{
' x ' : FILE_CONTENT ,
' y ' : { ' z ' : FILE_CONTENT } ,
' q ' : { ' u ' : [ json . loads ( JSON_FILE_CONTENT ) ] } ,
} ,
) ,
(
[
' foo[bar][5][]:=5 ' ,
' foo[bar][]:=6 ' ,
' foo[bar][][]:=7 ' ,
' foo[bar][][x]=dfasfdas ' ,
' foo[baz]:=[1, 2, 3] ' ,
' foo[baz][]:=4 ' ,
] ,
{
' foo ' : {
' bar ' : [
None ,
None ,
None ,
None ,
None ,
[ 5 ] ,
6 ,
[ 7 ] ,
{ ' x ' : ' dfasfdas ' } ,
] ,
' baz ' : [ 1 , 2 , 3 , 4 ] ,
}
} ,
) ,
(
[
' foo[]:=1 ' ,
' foo[]:=2 ' ,
' foo[][key]=value ' ,
' foo[2][key 2]=value 2 ' ,
r ' foo[2][key \ []=value 3 ' ,
r ' [nesting][under][!][empty][?][ \\ key]:=4 ' ,
] ,
{
' foo ' : [
1 ,
2 ,
{ ' key ' : ' value ' , ' key 2 ' : ' value 2 ' , ' key [ ' : ' value 3 ' } ,
] ,
' ' : {
' nesting ' : { ' under ' : { ' ! ' : { ' empty ' : { ' ? ' : { ' \\ key ' : 4 } } } } }
} ,
} ,
) ,
(
[
r ' foo \ [key \ ]:=1 ' ,
r ' bar \ [1 \ ]:=2 ' ,
r ' baz \ [ \ ]:3 ' ,
r ' quux[key \ [escape \ ]]:=4 ' ,
r ' quux[key 2][ \\ ][ \\ \\ ][ \\ \ [ \ ] \\ \ ] \\ \ [ \ n \\ ]:=5 ' ,
] ,
{
' foo[key] ' : 1 ,
' bar[1] ' : 2 ,
' quux ' : {
' key[escape] ' : 4 ,
' key 2 ' : { ' \\ ' : { ' \\ \\ ' : { ' \\ [] \\ ] \\ [ \\ n \\ ' : 5 } } } ,
} ,
} ,
) ,
(
[ r ' A[B \\ ]=C ' , r ' A[B \\ \\ ]=C ' , r ' A[ \ B \\ ]=C ' ] ,
{ ' A ' : { ' B \\ ' : ' C ' , ' B \\ \\ ' : ' C ' , ' \\ B \\ ' : ' C ' } } ,
) ,
(
[
' name=python ' ,
' version:=3 ' ,
' date[year]:=2021 ' ,
' date[month]=December ' ,
' systems[]=Linux ' ,
' systems[]=Mac ' ,
' systems[]=Windows ' ,
' people[known_ids][1]:=1000 ' ,
' people[known_ids][5]:=5000 ' ,
] ,
{
' name ' : ' python ' ,
' version ' : 3 ,
' date ' : { ' year ' : 2021 , ' month ' : ' December ' } ,
' systems ' : [ ' Linux ' , ' Mac ' , ' Windows ' ] ,
' people ' : { ' known_ids ' : [ None , 1000 , None , None , None , 5000 ] } ,
} ,
) ,
2022-01-07 11:52:13 +02:00
(
[
r ' foo[ \ 1][type]=migration ' ,
r ' foo[ \ 2][type]=migration ' ,
r ' foo[ \ dates]:=[2012, 2013] ' ,
r ' foo[ \ dates][0]:=2014 ' ,
r ' foo[ \ 2012 bleh]:=2013 ' ,
r ' foo[bleh \ 2012]:=2014 ' ,
r ' \ 2012[x]:=2 ' ,
r ' \ 2012[ \ [3 \ ]]:=4 ' ,
] ,
{
' foo ' : {
' 1 ' : { ' type ' : ' migration ' } ,
' 2 ' : { ' type ' : ' migration ' } ,
' \\ dates ' : [ 2014 , 2013 ] ,
' \\ 2012 bleh ' : 2013 ,
' bleh \\ 2012 ' : 2014 ,
} ,
' 2012 ' : { ' x ' : 2 , ' [3] ' : 4 } ,
} ,
) ,
2022-02-01 12:10:55 +02:00
(
[
r ' a[ \ 0]:=0 ' ,
r ' a[ \\ 1]:=1 ' ,
r ' a[ \\ \ 2]:=2 ' ,
r ' a[ \\ \\ \ 3]:=3 ' ,
r ' a[-1 \\ ]:=-1 ' ,
r ' a[-2 \\ \\ ]:=-2 ' ,
r ' a[ \\ -3 \\ \\ ]:=-3 ' ,
] ,
{
" a " : {
" 0 " : 0 ,
r " \ 1 " : 1 ,
r " \\ 2 " : 2 ,
r " \\ \ 3 " : 3 ,
" -1 \\ " : - 1 ,
" -2 \\ \\ " : - 2 ,
" \\ -3 \\ \\ " : - 3 ,
}
}
) ,
2022-01-04 11:04:20 +02:00
] ,
)
def test_nested_json_syntax ( input_json , expected_json , httpbin ) :
r = http ( httpbin + ' /post ' , * input_json )
2021-12-03 12:17:45 +02:00
assert r . json [ ' json ' ] == expected_json
2022-01-04 11:04:20 +02:00
@pytest.mark.parametrize (
' input_json, expected_error ' ,
[
(
[ ' A[:=1 ' ] ,
" HTTPie Syntax Error: Expecting a text, a number or ' ] ' \n A[ \n ^ " ,
) ,
( [ ' A[1:=1 ' ] , " HTTPie Syntax Error: Expecting ' ] ' \n A[1 \n ^ " ) ,
( [ ' A[text:=1 ' ] , " HTTPie Syntax Error: Expecting ' ] ' \n A[text \n ^ " ) ,
(
[ ' A[text][:=1 ' ] ,
" HTTPie Syntax Error: Expecting a text, a number or ' ] ' \n A[text][ \n ^ " ,
) ,
(
[ ' A[key]=value ' , ' B[something]=u ' , ' A[text][:=1 ' , ' C[key]=value ' ] ,
" HTTPie Syntax Error: Expecting a text, a number or ' ] ' \n A[text][ \n ^ " ,
) ,
(
[ ' A[text]1:=1 ' ] ,
" HTTPie Syntax Error: Expecting ' [ ' \n A[text]1 \n ^ " ,
) ,
( [ ' A \\ []:=1 ' ] , " HTTPie Syntax Error: Expecting ' [ ' \n A \\ [] \n ^ " ) ,
(
[ ' A[something \\ ]:=1 ' ] ,
" HTTPie Syntax Error: Expecting ' ] ' \n A[something \\ ] \n ^ " ,
) ,
(
[ ' foo \\ [bar \\ ] \\ \\ [ bleh:=1 ' ] ,
" HTTPie Syntax Error: Expecting ' ] ' \n foo \\ [bar \\ ] \\ \\ [ bleh \n ^ " ,
) ,
(
[ ' foo \\ [bar \\ ] \\ \\ [ bleh :=1 ' ] ,
" HTTPie Syntax Error: Expecting ' ] ' \n foo \\ [bar \\ ] \\ \\ [ bleh \n ^ " ,
) ,
(
[ ' foo[bar][1]][]:=2 ' ] ,
" HTTPie Syntax Error: Expecting ' [ ' \n foo[bar][1]][] \n ^ " ,
) ,
(
[ ' foo[bar][1]something[]:=2 ' ] ,
" HTTPie Syntax Error: Expecting ' [ ' \n foo[bar][1]something[] \n ^^^^^^^^^ " ,
) ,
(
[ ' foo[bar][1][142241[]:=2 ' ] ,
" HTTPie Syntax Error: Expecting ' ] ' \n foo[bar][1][142241[] \n ^ " ,
) ,
(
[ ' foo[bar][1] \\ [142241[]:=2 ' ] ,
" HTTPie Syntax Error: Expecting ' [ ' \n foo[bar][1] \\ [142241[] \n ^^^^^^^^ " ,
) ,
(
[ ' foo=1 ' , ' foo[key]:=2 ' ] ,
" HTTPie Type Error: Can ' t perform ' key ' based access on ' foo ' which has a type of ' string ' but this operation requires a type of ' object ' . \n foo[key] \n ^^^^^ " ,
) ,
(
[ ' foo=1 ' , ' foo[0]:=2 ' ] ,
" HTTPie Type Error: Can ' t perform ' index ' based access on ' foo ' which has a type of ' string ' but this operation requires a type of ' array ' . \n foo[0] \n ^^^ " ,
) ,
(
[ ' foo=1 ' , ' foo[]:=2 ' ] ,
" HTTPie Type Error: Can ' t perform ' append ' based access on ' foo ' which has a type of ' string ' but this operation requires a type of ' array ' . \n foo[] \n ^^ " ,
) ,
(
[ ' data[key]=value ' , ' data[key 2]=value 2 ' , ' data[0]=value ' ] ,
" HTTPie Type Error: Can ' t perform ' index ' based access on ' data ' which has a type of ' object ' but this operation requires a type of ' array ' . \n data[0] \n ^^^ " ,
) ,
(
[ ' data[key]=value ' , ' data[key 2]=value 2 ' , ' data[]=value ' ] ,
" HTTPie Type Error: Can ' t perform ' append ' based access on ' data ' which has a type of ' object ' but this operation requires a type of ' array ' . \n data[] \n ^^ " ,
) ,
(
[
' foo[bar][baz][5]:=[1,2,3] ' ,
' foo[bar][baz][5][]:=4 ' ,
' foo[bar][baz][key][]:=5 ' ,
] ,
" HTTPie Type Error: Can ' t perform ' key ' based access on ' foo[bar][baz] ' which has a type of ' array ' but this operation requires a type of ' object ' . \n foo[bar][baz][key][] \n ^^^^^ " ,
) ,
(
[ ' foo[-10]:=[1,2] ' ] ,
' HTTPie Value Error: Negative indexes are not supported. \n foo[-10] \n ^^^ ' ,
) ,
2022-01-07 11:52:13 +02:00
(
[ ' foo[0]:=1 ' , ' foo[]:=2 ' , ' foo[ \\ 2]:=3 ' ] ,
" HTTPie Type Error: Can ' t perform ' key ' based access on ' foo ' which has a type of ' array ' but this operation requires a type of ' object ' . \n foo[ \\ 2] \n ^^^^ " ,
) ,
(
[ ' foo[ \\ 1]:=2 ' , ' foo[5]:=3 ' ] ,
" HTTPie Type Error: Can ' t perform ' index ' based access on ' foo ' which has a type of ' object ' but this operation requires a type of ' array ' . \n foo[5] \n ^^^ " ,
) ,
2022-01-04 11:04:20 +02:00
] ,
)
def test_nested_json_errors ( input_json , expected_error , httpbin ) :
with pytest . raises ( HTTPieSyntaxError ) as exc :
http ( httpbin + ' /post ' , * input_json )
assert str ( exc . value ) == expected_error
2021-12-03 12:17:45 +02:00
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