Multipart data values encoding (#121)
* multipart data values encoding (#119) * Update test_multipart.py
This commit is contained in:
parent
c1044cab84
commit
0e8dae4815
@ -15,7 +15,11 @@ class Field:
|
||||
|
||||
|
||||
class DataField(Field):
|
||||
def __init__(self, name: str, value: str) -> None:
|
||||
def __init__(self, name: str, value: typing.Union[str, bytes]) -> None:
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("Invalid type for name. Expected str.")
|
||||
if not isinstance(value, (str, bytes)):
|
||||
raise TypeError("Invalid type for value. Expected str or bytes.")
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
@ -26,7 +30,9 @@ class DataField(Field):
|
||||
)
|
||||
|
||||
def render_data(self) -> bytes:
|
||||
return self.value.encode("utf-8")
|
||||
return (
|
||||
self.value if isinstance(self.value, bytes) else self.value.encode("utf-8")
|
||||
)
|
||||
|
||||
|
||||
class FileField(Field):
|
||||
@ -73,7 +79,7 @@ class FileField(Field):
|
||||
|
||||
def iter_fields(data: dict, files: dict) -> typing.Iterator[Field]:
|
||||
for name, value in data.items():
|
||||
if isinstance(value, list):
|
||||
if isinstance(value, (list, dict)):
|
||||
for item in value:
|
||||
yield DataField(name=name, value=item)
|
||||
else:
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import binascii
|
||||
import cgi
|
||||
import io
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from http3 import (
|
||||
CertTypes,
|
||||
Client,
|
||||
Dispatcher,
|
||||
multipart,
|
||||
Request,
|
||||
Response,
|
||||
TimeoutTypes,
|
||||
@ -23,11 +29,12 @@ class MockDispatch(Dispatcher):
|
||||
return Response(200, content=request.read())
|
||||
|
||||
|
||||
def test_multipart():
|
||||
@pytest.mark.parametrize(("value,output"), (("abc", b"abc"), (b"abc", b"abc")))
|
||||
def test_multipart(value, output):
|
||||
client = Client(dispatch=MockDispatch())
|
||||
|
||||
# Test with a single-value 'data' argument, and a plain file 'files' argument.
|
||||
data = {"text": "abc"}
|
||||
data = {"text": value}
|
||||
files = {"file": io.BytesIO(b"<file content>")}
|
||||
response = client.post("http://127.0.0.1:8000/", data=data, files=files)
|
||||
assert response.status_code == 200
|
||||
@ -41,10 +48,30 @@ def test_multipart():
|
||||
|
||||
# Note that the expected return type for text fields
|
||||
# appears to differs from 3.6 to 3.7+
|
||||
assert multipart["text"] == ["abc"] or multipart["text"] == [b"abc"]
|
||||
assert multipart["text"] == [output.decode()] or multipart["text"] == [output]
|
||||
assert multipart["file"] == [b"<file content>"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("key"), (b"abc", 1, 2.3, None))
|
||||
def test_multipart_invalid_key(key):
|
||||
client = Client(dispatch=MockDispatch())
|
||||
data = {key: "abc"}
|
||||
files = {"file": io.BytesIO(b"<file content>")}
|
||||
with pytest.raises(TypeError) as e:
|
||||
client.post("http://127.0.0.1:8000/", data=data, files=files)
|
||||
assert "Invalid type for name" in str(e.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("value"), (1, 2.3, None, [None, "abc"], {None: "abc"}))
|
||||
def test_multipart_invalid_value(value):
|
||||
client = Client(dispatch=MockDispatch())
|
||||
data = {"text": value}
|
||||
files = {"file": io.BytesIO(b"<file content>")}
|
||||
with pytest.raises(TypeError) as e:
|
||||
client.post("http://127.0.0.1:8000/", data=data, files=files)
|
||||
assert "Invalid type for value" in str(e.value)
|
||||
|
||||
|
||||
def test_multipart_file_tuple():
|
||||
client = Client(dispatch=MockDispatch())
|
||||
|
||||
@ -65,3 +92,33 @@ def test_multipart_file_tuple():
|
||||
# appears to differs from 3.6 to 3.7+
|
||||
assert multipart["text"] == ["abc"] or multipart["text"] == [b"abc"]
|
||||
assert multipart["file"] == [b"<file content>"]
|
||||
|
||||
|
||||
def test_multipart_encode():
|
||||
data = {
|
||||
"a": "1",
|
||||
"b": b"C",
|
||||
"c": ["11", "22", "33"],
|
||||
"d": {"ff": ["1", b"2", "3"], "fff": ["11", b"22", "33"]},
|
||||
"f": "",
|
||||
}
|
||||
files = {"file": ("name.txt", io.BytesIO(b"<file content>"))}
|
||||
|
||||
with mock.patch("os.urandom", return_value=os.urandom(16)):
|
||||
boundary = binascii.hexlify(os.urandom(16)).decode("ascii")
|
||||
body, content_type = multipart.multipart_encode(data=data, files=files)
|
||||
assert content_type == f"multipart/form-data; boundary={boundary}"
|
||||
assert body == (
|
||||
'--{0}\r\nContent-Disposition: form-data; name="a"\r\n\r\n1\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="b"\r\n\r\nC\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n11\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n22\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n33\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\nff\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\nfff\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="f"\r\n\r\n\r\n'
|
||||
'--{0}\r\nContent-Disposition: form-data; name="file"; filename="name.txt"\r\n'
|
||||
"Content-Type: text/plain\r\n\r\n<file content>\r\n"
|
||||
"--{0}--\r\n"
|
||||
"".format(boundary).encode("ascii")
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user