1
0
mirror of https://github.com/httpie/cli.git synced 2026-04-26 20:02:11 +02:00

Support multiple headers sharing the same name (#1190)

* Support multiple headers sharing the same name

* Apply suggestions

* Don't normalize HTTP header names

* apply visual suggestions

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>

* bump down multidict to 4.7.0

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
This commit is contained in:
Batuhan Taskaya
2021-10-31 15:04:39 +01:00
committed by GitHub
parent d40f06687f
commit 7cdd74fece
11 changed files with 221 additions and 13 deletions
+4 -4
View File
@@ -39,8 +39,8 @@ class TestItemParsing:
# files
self.key_value_arg(fr'bar\@baz@{FILE_PATH_ARG}'),
])
# `requests.structures.CaseInsensitiveDict` => `dict`
headers = dict(items.headers._store.values())
# `RequestHeadersDict` => `dict`
headers = dict(items.headers)
assert headers == {
'foo:bar': 'baz',
@@ -88,8 +88,8 @@ class TestItemParsing:
])
# Parsed headers
# `requests.structures.CaseInsensitiveDict` => `dict`
headers = dict(items.headers._store.values())
# `RequestHeadersDict` => `dict`
headers = dict(items.headers)
assert headers == {
'Header': 'value',
'Unset-Header': None,
+82
View File
@@ -209,6 +209,88 @@ def test_headers_empty_value_with_value_gives_error(httpbin):
http('GET', httpbin + '/headers', 'Accept;SYNTAX_ERROR')
def test_headers_omit(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Accept:')
assert 'Accept' not in r.json['headers']
def test_headers_multiple_omit(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Bar:baz',
'Foo:', 'Baz:quux')
assert 'Foo' not in r.json['headers']
assert r.json['headers']['Bar'] == 'baz'
assert r.json['headers']['Baz'] == 'quux'
def test_headers_same_after_omit(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:',
'Foo:quux')
assert r.json['headers']['Foo'] == 'quux'
def test_headers_fully_omit(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:baz',
'Foo:')
assert 'Foo' not in r.json['headers']
def test_headers_multiple_values(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:baz')
assert r.json['headers']['Foo'] == 'bar,baz'
def test_headers_multiple_values_repeated(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:baz',
'Foo:bar')
assert r.json['headers']['Foo'] == 'bar,baz,bar'
@pytest.mark.parametrize("headers, expected", [
(
["Foo;", "Foo:bar"],
",bar"
),
(
["Foo:bar", "Foo;"],
"bar,"
),
(
["Foo:bar", "Foo;", "Foo:baz"],
"bar,,baz"
),
])
def test_headers_multiple_values_with_empty(httpbin_both, headers, expected):
r = http('GET', httpbin_both + '/headers', *headers)
assert r.json['headers']['Foo'] == expected
def test_headers_multiple_values_mixed(httpbin_both):
r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Vary:XXX',
'Foo:baz', 'Vary:YYY', 'Foo:quux')
assert r.json['headers']['Vary'] == 'XXX,YYY'
assert r.json['headers']['Foo'] == 'bar,baz,quux'
def test_headers_preserve_prepared_headers(httpbin_both):
r = http('POST', httpbin_both + '/post', 'Content-Length:0',
'--raw', 'foo')
assert r.json['headers']['Content-Length'] == '3'
@pytest.mark.parametrize('pretty', ['format', 'none'])
def test_headers_multiple_headers_representation(httpbin_both, pretty):
r = http('--offline', '--pretty', pretty, 'example.org',
'A:A', 'A:B', 'A:C', 'B:A', 'B:B', 'C:C', 'c:c')
assert 'A: A' in r
assert 'A: B' in r
assert 'A: C' in r
assert 'B: A' in r
assert 'B: B' in r
assert 'C: C' in r
assert 'c: c' in r
def test_json_input_preserve_order(httpbin_both):
r = http('PATCH', httpbin_both + '/patch',
'order:={"map":{"1":"first","2":"second"}}')
+11
View File
@@ -73,12 +73,17 @@ def test_follow_redirect_with_repost(httpbin, status_code):
r = http(
'--follow',
httpbin.url + '/redirect-to',
'A:A',
'A:B',
'B:B',
f'url=={httpbin.url}/post',
f'status_code=={status_code}',
'@' + FILE_PATH_ARG,
)
assert HTTP_OK in r
assert FILE_CONTENT in r
assert r.json['headers']['A'] == 'A,B'
assert r.json['headers']['B'] == 'B'
@pytest.mark.skipif(is_windows, reason='occasionally fails w/ ConnectionError for no apparent reason')
@@ -88,11 +93,17 @@ def test_verbose_follow_redirect_with_repost(httpbin, status_code):
'--follow',
'--verbose',
httpbin.url + '/redirect-to',
'A:A',
'A:B',
'B:B',
f'url=={httpbin.url}/post',
f'status_code=={status_code}',
'@' + FILE_PATH_ARG,
)
assert f'HTTP/1.1 {status_code}' in r
assert 'A: A' in r
assert 'A: B' in r
assert 'B: B' in r
assert r.count('POST /redirect-to') == 1
assert r.count('POST /post') == 1
assert r.count(FILE_CONTENT) == 3 # two requests + final response contain it
+18
View File
@@ -143,6 +143,24 @@ class TestSessionFlow(SessionTestBase):
# Should be the same as before r3.
assert r2.json == r4.json
def test_session_overwrite_header(self, httpbin):
self.start_session(httpbin)
r2 = http('--session=test', 'GET', httpbin.url + '/get',
'Hello:World2', env=self.env())
assert HTTP_OK in r2
assert r2.json['headers']['Hello'] == 'World2'
r3 = http('--session=test', 'GET', httpbin.url + '/get',
'Hello:World2', 'Hello:World3', env=self.env())
assert HTTP_OK in r3
assert r3.json['headers']['Hello'] == 'World2,World3'
r3 = http('--session=test', 'GET', httpbin.url + '/get',
'Hello:', 'Hello:World3', env=self.env())
assert HTTP_OK in r3
assert 'Hello' not in r3.json['headers']['Hello']
class TestSession(SessionTestBase):
"""Stand-alone session tests."""