Skip to content

Commit 97edab0

Browse files
authored
fix(python): fixing issue with closing LimitedStream + test cases (#98)
* fix(django issue): fixing issue with closing LimitedStream + test cases * style(tests): minor cleanup * reformatting * using werkzeug limitedstream implementation * updated paths for github test runner * reformatting (again)
1 parent 02bcc2c commit 97edab0

File tree

7 files changed

+183
-10
lines changed

7 files changed

+183
-10
lines changed

.vscode/settings.json

Whitespace-only changes.

packages/python/readme_metrics/MetricsMiddleware.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ def _start_response(_status, _response_headers, *args):
6868
content_length = int(environ["CONTENT_LENGTH"])
6969
content_body = environ["wsgi.input"].read(content_length)
7070

71-
environ["wsgi.input"].close()
71+
# guarding check to close stream
72+
if hasattr(environ["CONTENT_LENGTH"], "close"):
73+
environ["wsgi.input"].close()
74+
7275
environ["wsgi.input"] = io.BytesIO(content_body)
7376

7477
req.rm_content_length = content_length

packages/python/readme_metrics/tests/MetricsMiddleware_test.py

+72-9
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import requests
33
import json
44

5+
from .fixtures import Environ
6+
57
from readme_metrics import MetricsApiConfig
68
from readme_metrics import MetricsMiddleware
79

8-
910
# for this, I'm not exactly sure how to test the __call__ function
1011
# possible options I considered was making a mock server inside this test case
1112
# connected to the middleware somehow
@@ -36,21 +37,83 @@ def sendRequestsAtOnce(self, requestQueue):
3637
return requestQueue
3738

3839

40+
# Mock middleware config
41+
def mockMiddlewareConfig():
42+
return MetricsApiConfig(
43+
"koSyKkViOR5gD6yjBxlsprHfjAIlWOh6",
44+
lambda req: {"id": "123", "label": "testuser", "email": "[email protected]"},
45+
buffer_length=1,
46+
)
47+
48+
49+
# Mock callback for handling middleware response
50+
class MetricsCoreMock:
51+
def process(self, req, res):
52+
self.req = req
53+
self.res = res
54+
55+
56+
# Mock application
57+
class MockApplication:
58+
def __init__(self, responseObjectString):
59+
self.responseObjectString = responseObjectString
60+
61+
def __call__(self, environ, start_response):
62+
self.environ = environ
63+
self.start_response = start_response
64+
return [self.responseObjectString.encode("utf-8")]
65+
66+
def mockStartResponse(self, status, headers):
67+
self.status = status
68+
self.headers = headers
69+
70+
3971
class TestMetricsMiddleware:
4072
def setUp(self):
4173
self.mockserver = MockServer()
4274

43-
@pytest.mark.skip(reason="@todo")
75+
# @pytest.mark.skip(reason="@todo")
4476
def testNoRequest(self):
45-
# Test no request (None) but the function is called
46-
# Test no request ([]) but the function is called
4777
pass
4878

49-
@pytest.mark.skip(reason="@todo")
50-
def testSingleRequest(self):
51-
# Test if a single request got through and processed
52-
# Test if a single request is sent but with trash data(?)
53-
pass
79+
def testGetRequest(self):
80+
emptyByteString = b""
81+
responseObjectString = "{ responseObject: 'value' }"
82+
environ = Environ.MockEnviron().getEnvironForRequest(emptyByteString, "GET")
83+
app = MockApplication(responseObjectString)
84+
metrics = MetricsCoreMock()
85+
middleware = MetricsMiddleware(app, mockMiddlewareConfig())
86+
middleware.metrics_core = metrics
87+
next(middleware(environ, app.mockStartResponse))
88+
assert metrics.req.data == emptyByteString
89+
assert metrics.req.method == "GET"
90+
assert metrics.res.body == responseObjectString
91+
92+
def testEmptyPostRequest(self):
93+
jsonString = b""
94+
responseObjectString = "{ responseObject: 'value' }"
95+
environ = Environ.MockEnviron().getEnvironForRequest(jsonString, "POST")
96+
app = MockApplication(responseObjectString)
97+
metrics = MetricsCoreMock()
98+
middleware = MetricsMiddleware(app, mockMiddlewareConfig())
99+
middleware.metrics_core = metrics
100+
next(middleware(environ, app.mockStartResponse))
101+
assert metrics.req.data == jsonString
102+
assert metrics.req.method == "POST"
103+
assert metrics.res.body == responseObjectString
104+
105+
def testNonEmptyPostRequest(self):
106+
jsonString = b"{abc: 123}"
107+
responseObjectString = "{ responseObject: 'value' }"
108+
environ = Environ.MockEnviron().getEnvironForRequest(jsonString, "POST")
109+
app = MockApplication(responseObjectString)
110+
metrics = MetricsCoreMock()
111+
middleware = MetricsMiddleware(app, mockMiddlewareConfig())
112+
middleware.metrics_core = metrics
113+
next(middleware(environ, app.mockStartResponse))
114+
assert metrics.req.data == jsonString
115+
assert metrics.req.method == "POST"
116+
assert metrics.res.body == responseObjectString
54117

55118
@pytest.mark.skip(reason="@todo")
56119
def testMultipleRequests(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import json
2+
import io
3+
import os
4+
5+
from werkzeug import wsgi
6+
7+
8+
class MockEnviron:
9+
def __init__(self):
10+
with open("./readme_metrics/tests/fixtures/environ_get.json") as json_file:
11+
self.environGetData = json.load(json_file)
12+
13+
with open("./readme_metrics/tests/fixtures/environ_post.json") as json_file:
14+
self.environPostData = json.load(json_file)
15+
16+
def getEnvironForRequest(self, jsonByteString, httpRequestMethod):
17+
environ = self.environGetData
18+
if httpRequestMethod == "POST":
19+
environ = self.environPostData
20+
elif httpRequestMethod == "GET":
21+
environ = self.environGetData
22+
23+
contentLength = len(jsonByteString)
24+
stream = io.BytesIO(jsonByteString)
25+
environ["wsgi.input"] = wsgi.LimitedStream(stream, contentLength)
26+
environ["CONTENT_LENGTH"] = contentLength
27+
return environ

packages/python/readme_metrics/tests/fixtures/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"SECURITYSESSIONID": "186a6",
3+
"COMMAND_MODE": "unix2003",
4+
"SHELL": "/bin/zsh",
5+
"LaunchInstanceID": "69CF0CFE-BF35-4864-B4B9-7B7F4C0E8CB4",
6+
"XPC_SERVICE_NAME": "0",
7+
"XPC_FLAGS": "0x0",
8+
"TMPDIR": "/var/folders/n_/fm5j91vn28774yvp2xtsr2p00000gn/T/",
9+
"ORIGINAL_XDG_CURRENT_DESKTOP": "undefined",
10+
"SHLVL": "1",
11+
"TERM_PROGRAM": "vscode",
12+
"TERM_PROGRAM_VERSION": "1.49.2",
13+
"LANG": "en_US.UTF-8",
14+
"COLORTERM": "truecolor",
15+
"TERM": "xterm-256color",
16+
"PS1": "(env) %n@%m %1~ %# ",
17+
"DJANGO_SETTINGS_MODULE": "django_metrics_test.settings",
18+
"TZ": "UTC",
19+
"RUN_MAIN": "true",
20+
"SERVER_NAME": "1.0.0.127.in-addr.arpa",
21+
"GATEWAY_INTERFACE": "CGI/1.1",
22+
"SERVER_PORT": "8000",
23+
"REMOTE_HOST": "",
24+
"CONTENT_LENGTH": "",
25+
"SCRIPT_NAME": "",
26+
"SERVER_PROTOCOL": "HTTP/1.1",
27+
"SERVER_SOFTWARE": "WSGIServer/0.2",
28+
"REQUEST_METHOD": "GET",
29+
"PATH_INFO": "/",
30+
"QUERY_STRING": "",
31+
"REMOTE_ADDR": "127.0.0.1",
32+
"CONTENT_TYPE": "text/plain",
33+
"HTTP_HOST": "localhost: 8000",
34+
"HTTP_USER_AGENT": "curl/7.64.1",
35+
"HTTP_ACCEPT": "*/*",
36+
"wsgi.run_once": false,
37+
"wsgi.url_scheme": "http",
38+
"wsgi.multithread": true,
39+
"wsgi.multiprocess": false
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"SECURITYSESSIONID": "186a6",
3+
"LaunchInstanceID": "69CF0CFE-BF35-4864-B4B9-7B7F4C0E8CB4",
4+
"__CF_USER_TEXT_ENCODING": "0x1F5: 0x0: 0x0",
5+
"XPC_SERVICE_NAME": "0",
6+
"XPC_FLAGS": "0x0",
7+
"TMPDIR": "/var/folders/n_/fm5j91vn28774yvp2xtsr2p00000gn/T/",
8+
"ORIGINAL_XDG_CURRENT_DESKTOP": "undefined",
9+
"SHLVL": "1",
10+
"TERM_PROGRAM": "vscode",
11+
"TERM_PROGRAM_VERSION": "1.49.2",
12+
"LANG": "en_US.UTF-8",
13+
"COLORTERM": "truecolor",
14+
"TERM": "xterm-256color",
15+
"PS1": "(env) %n@%m %1~ %# ",
16+
"DJANGO_SETTINGS_MODULE": "django_metrics_test.settings",
17+
"TZ": "UTC",
18+
"RUN_MAIN": "true",
19+
"SERVER_NAME": "1.0.0.127.in-addr.arpa",
20+
"GATEWAY_INTERFACE": "CGI/1.1",
21+
"SERVER_PORT": "8000",
22+
"REMOTE_HOST": "",
23+
"CONTENT_LENGTH": "",
24+
"SCRIPT_NAME": "",
25+
"SERVER_PROTOCOL": "HTTP/1.1",
26+
"SERVER_SOFTWARE": "WSGIServer/0.2",
27+
"REQUEST_METHOD": "POST",
28+
"PATH_INFO": "/",
29+
"QUERY_STRING": "",
30+
"REMOTE_ADDR": "127.0.0.1",
31+
"CONTENT_TYPE": "text/plain",
32+
"HTTP_HOST": "localhost: 8000",
33+
"HTTP_USER_AGENT": "curl/7.64.1",
34+
"HTTP_ACCEPT": "*/*",
35+
"wsgi.input": "",
36+
"wsgi.run_once": false,
37+
"wsgi.url_scheme": "http",
38+
"wsgi.multithread": true,
39+
"wsgi.multiprocess": false
40+
}

0 commit comments

Comments
 (0)