Skip to content

Commit a8b78ad

Browse files
authored
Merge pull request #3 from justmobilize/files_arg_tests
Add tests
2 parents c701ab7 + 9b9f7bc commit a8b78ad

6 files changed

+220
-0
lines changed

optional_requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
22
#
33
# SPDX-License-Identifier: Unlicense
4+
5+
requests

tests/files/green_red.png

125 Bytes
Loading

tests/files/green_red.png.license

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: 2024 Justin Myers
2+
# SPDX-License-Identifier: Unlicense

tests/files/red_green.png

123 Bytes
Loading

tests/files/red_green.png.license

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: 2024 Justin Myers
2+
# SPDX-License-Identifier: Unlicense

tests/files_test.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# SPDX-FileCopyrightText: 2024 Justin Myers
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
""" Post Files Tests """
6+
# pylint: disable=line-too-long
7+
8+
import re
9+
from unittest import mock
10+
11+
import mocket
12+
import pytest
13+
import requests as python_requests
14+
15+
16+
@pytest.fixture
17+
def log_stream():
18+
return []
19+
20+
21+
@pytest.fixture
22+
def post_url():
23+
return "https://httpbin.org/post"
24+
25+
26+
@pytest.fixture
27+
def request_logging(log_stream):
28+
"""Reset the ConnectionManager, since it's a singlton and will hold data"""
29+
import http.client # pylint: disable=import-outside-toplevel
30+
31+
def httpclient_log(*args):
32+
log_stream.append(args)
33+
34+
http.client.print = httpclient_log
35+
http.client.HTTPConnection.debuglevel = 1
36+
37+
38+
def get_actual_request_data(log_stream):
39+
boundary_pattern = r"(?<=boundary=)(.\w*)"
40+
content_length_pattern = r"(?<=Content-Length: )(.\d*)"
41+
42+
boundary = ""
43+
actual_request_post = ""
44+
content_length = ""
45+
for log in log_stream:
46+
for log_arg in log:
47+
boundary_search = re.findall(boundary_pattern, log_arg)
48+
content_length_search = re.findall(content_length_pattern, log_arg)
49+
if boundary_search:
50+
boundary = boundary_search[0]
51+
if content_length_search:
52+
content_length = content_length_search[0]
53+
if "Content-Disposition" in log_arg:
54+
# this will look like:
55+
# b\'{content}\'
56+
# and escaped characters look like:
57+
# \\r
58+
post_data = log_arg[2:-1]
59+
post_bytes = post_data.encode("utf-8")
60+
post_unescaped = post_bytes.decode("unicode_escape")
61+
actual_request_post = post_unescaped.encode("latin1")
62+
63+
return boundary, content_length, actual_request_post
64+
65+
66+
def test_post_files_text( # pylint: disable=unused-argument
67+
sock, requests, log_stream, post_url, request_logging
68+
):
69+
file_data = {
70+
"key_4": (None, "Value 5"),
71+
}
72+
73+
python_requests.post(post_url, files=file_data, timeout=30)
74+
boundary, content_length, actual_request_post = get_actual_request_data(log_stream)
75+
76+
requests._build_boundary_string = mock.Mock(return_value=boundary)
77+
requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data)
78+
79+
sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80))
80+
sock.send.assert_has_calls(
81+
[
82+
mock.call(b"Content-Type"),
83+
mock.call(b": "),
84+
mock.call(f"multipart/form-data; boundary={boundary}".encode()),
85+
mock.call(b"\r\n"),
86+
]
87+
)
88+
sock.send.assert_has_calls(
89+
[
90+
mock.call(b"Content-Length"),
91+
mock.call(b": "),
92+
mock.call(content_length.encode()),
93+
mock.call(b"\r\n"),
94+
]
95+
)
96+
97+
sent = b"".join(sock.sent_data)
98+
assert sent.endswith(actual_request_post)
99+
100+
101+
def test_post_files_file( # pylint: disable=unused-argument
102+
sock, requests, log_stream, post_url, request_logging
103+
):
104+
with open("tests/files/red_green.png", "rb") as file_1:
105+
file_data = {
106+
"file_1": (
107+
"red_green.png",
108+
file_1,
109+
"image/png",
110+
{
111+
"Key_1": "Value 1",
112+
"Key_2": "Value 2",
113+
"Key_3": "Value 3",
114+
},
115+
),
116+
}
117+
118+
python_requests.post(post_url, files=file_data, timeout=30)
119+
boundary, content_length, actual_request_post = get_actual_request_data(
120+
log_stream
121+
)
122+
123+
requests._build_boundary_string = mock.Mock(return_value=boundary)
124+
requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data)
125+
126+
sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80))
127+
sock.send.assert_has_calls(
128+
[
129+
mock.call(b"Content-Type"),
130+
mock.call(b": "),
131+
mock.call(f"multipart/form-data; boundary={boundary}".encode()),
132+
mock.call(b"\r\n"),
133+
]
134+
)
135+
sock.send.assert_has_calls(
136+
[
137+
mock.call(b"Content-Length"),
138+
mock.call(b": "),
139+
mock.call(content_length.encode()),
140+
mock.call(b"\r\n"),
141+
]
142+
)
143+
sent = b"".join(sock.sent_data)
144+
assert sent.endswith(actual_request_post)
145+
146+
147+
def test_post_files_complex( # pylint: disable=unused-argument
148+
sock, requests, log_stream, post_url, request_logging
149+
):
150+
with open("tests/files/red_green.png", "rb") as file_1, open(
151+
"tests/files/green_red.png", "rb"
152+
) as file_2:
153+
file_data = {
154+
"file_1": (
155+
"red_green.png",
156+
file_1,
157+
"image/png",
158+
{
159+
"Key_1": "Value 1",
160+
"Key_2": "Value 2",
161+
"Key_3": "Value 3",
162+
},
163+
),
164+
"key_4": (None, "Value 5"),
165+
"file_2": (
166+
"green_red.png",
167+
file_2,
168+
"image/png",
169+
),
170+
"key_6": (None, "Value 6"),
171+
}
172+
173+
python_requests.post(post_url, files=file_data, timeout=30)
174+
boundary, content_length, actual_request_post = get_actual_request_data(
175+
log_stream
176+
)
177+
178+
requests._build_boundary_string = mock.Mock(return_value=boundary)
179+
requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data)
180+
181+
sock.connect.assert_called_once_with((mocket.MOCK_POOL_IP, 80))
182+
sock.send.assert_has_calls(
183+
[
184+
mock.call(b"Content-Type"),
185+
mock.call(b": "),
186+
mock.call(f"multipart/form-data; boundary={boundary}".encode()),
187+
mock.call(b"\r\n"),
188+
]
189+
)
190+
sock.send.assert_has_calls(
191+
[
192+
mock.call(b"Content-Length"),
193+
mock.call(b": "),
194+
mock.call(content_length.encode()),
195+
mock.call(b"\r\n"),
196+
]
197+
)
198+
sent = b"".join(sock.sent_data)
199+
assert sent.endswith(actual_request_post)
200+
201+
202+
def test_post_files_not_binary(requests):
203+
with open("tests/files/red_green.png", "r") as file_1:
204+
file_data = {
205+
"file_1": (
206+
"red_green.png",
207+
file_1,
208+
"image/png",
209+
),
210+
}
211+
212+
with pytest.raises(AttributeError) as context:
213+
requests.post("http://" + mocket.MOCK_HOST_1 + "/post", files=file_data)
214+
assert "Files must be opened in binary mode" in str(context)

0 commit comments

Comments
 (0)