|
| 1 | +import base64 |
1 | 2 | import json |
2 | 3 | import logging |
| 4 | +import os |
3 | 5 | import threading |
4 | 6 | import warnings |
5 | 7 | from unittest import mock |
6 | 8 |
|
7 | 9 | import fastapi |
8 | 10 | import pytest |
9 | 11 | import starlette |
10 | | -from fastapi import FastAPI, HTTPException, Request |
| 12 | +from fastapi import Body, FastAPI, File, Form, HTTPException, Request, UploadFile |
11 | 13 | from fastapi.middleware.trustedhost import TrustedHostMiddleware |
12 | 14 | from fastapi.testclient import TestClient |
13 | 15 |
|
|
22 | 24 | FASTAPI_VERSION = parse_version(fastapi.__version__) |
23 | 25 | STARLETTE_VERSION = parse_version(starlette.__version__) |
24 | 26 |
|
| 27 | +PICTURE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "photo.jpg") |
| 28 | + |
| 29 | +BODY_JSON = {"some": "json", "for": "testing", "nested": {"numbers": 123}} |
| 30 | + |
| 31 | +BODY_FORM = """--fd721ef49ea403a6\r\nContent-Disposition: form-data; name="username"\r\n\r\nJane\r\n--fd721ef49ea403a6\r\nContent-Disposition: form-data; name="password"\r\n\r\nhello123\r\n--fd721ef49ea403a6\r\nContent-Disposition: form-data; name="photo"; filename="photo.jpg"\r\nContent-Type: image/jpg\r\nContent-Transfer-Encoding: base64\r\n\r\n{{image_data}}\r\n--fd721ef49ea403a6--\r\n""".replace( |
| 32 | + "{{image_data}}", str(base64.b64encode(open(PICTURE, "rb").read())) |
| 33 | +) |
| 34 | + |
| 35 | +PARSED_FORM = starlette.datastructures.FormData( |
| 36 | + [ |
| 37 | + ("username", "Jane"), |
| 38 | + ("password", "hello123"), |
| 39 | + ( |
| 40 | + "photo", |
| 41 | + starlette.datastructures.UploadFile( |
| 42 | + filename="photo.jpg", |
| 43 | + file=open(PICTURE, "rb"), |
| 44 | + ), |
| 45 | + ), |
| 46 | + ] |
| 47 | +) |
| 48 | + |
25 | 49 | from tests.integrations.conftest import parametrize_test_configurable_status_codes |
26 | 50 | from tests.integrations.starlette import test_starlette |
27 | 51 |
|
@@ -70,9 +94,137 @@ async def _thread_ids_async(): |
70 | 94 | "active": str(threading.current_thread().ident), |
71 | 95 | } |
72 | 96 |
|
| 97 | + @app.post("/body/json") |
| 98 | + async def body_json(payload: dict = Body(...)): |
| 99 | + capture_message("hi") |
| 100 | + return {"status": "ok"} |
| 101 | + |
| 102 | + @app.post("/body/form") |
| 103 | + async def body_form( |
| 104 | + username: str = Form(...), |
| 105 | + password: str = Form(...), |
| 106 | + photo: UploadFile = File(...), |
| 107 | + ): |
| 108 | + capture_message("hi") |
| 109 | + return {"status": "ok"} |
| 110 | + |
73 | 111 | return app |
74 | 112 |
|
75 | 113 |
|
| 114 | +@pytest.mark.asyncio |
| 115 | +async def test_request_info_json_body(sentry_init, capture_events): |
| 116 | + sentry_init( |
| 117 | + traces_sample_rate=1.0, |
| 118 | + send_default_pii=True, |
| 119 | + integrations=[StarletteIntegration()], |
| 120 | + ) |
| 121 | + |
| 122 | + app = fastapi_app_factory() |
| 123 | + client = TestClient(app) |
| 124 | + |
| 125 | + events = capture_events() |
| 126 | + |
| 127 | + client.post( |
| 128 | + "/body/json", |
| 129 | + json=BODY_JSON, |
| 130 | + headers={ |
| 131 | + "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", |
| 132 | + }, |
| 133 | + ) |
| 134 | + |
| 135 | + (event, transaction_event) = events |
| 136 | + |
| 137 | + assert event["request"]["cookies"] == { |
| 138 | + "tasty_cookie": "strawberry", |
| 139 | + "yummy_cookie": "choco", |
| 140 | + } |
| 141 | + assert event["request"]["data"] == BODY_JSON |
| 142 | + |
| 143 | + assert transaction_event["request"]["cookies"] == { |
| 144 | + "tasty_cookie": "strawberry", |
| 145 | + "yummy_cookie": "choco", |
| 146 | + } |
| 147 | + assert transaction_event["request"]["data"] == BODY_JSON |
| 148 | + |
| 149 | + |
| 150 | +@pytest.mark.asyncio |
| 151 | +async def test_formdata_request_body(sentry_init, capture_events): |
| 152 | + sentry_init( |
| 153 | + traces_sample_rate=1.0, |
| 154 | + send_default_pii=True, |
| 155 | + max_request_body_size="always", |
| 156 | + integrations=[StarletteIntegration()], |
| 157 | + ) |
| 158 | + |
| 159 | + app = fastapi_app_factory() |
| 160 | + client = TestClient(app) |
| 161 | + |
| 162 | + events = capture_events() |
| 163 | + |
| 164 | + client.post( |
| 165 | + "/body/form", |
| 166 | + data=BODY_FORM.encode("utf-8"), |
| 167 | + headers={ |
| 168 | + "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", |
| 169 | + }, |
| 170 | + ) |
| 171 | + |
| 172 | + (event, transaction_event) = events |
| 173 | + assert event["request"]["data"].keys() == PARSED_FORM.keys() |
| 174 | + assert event["request"]["data"]["username"] == PARSED_FORM["username"] |
| 175 | + assert event["request"]["data"]["password"] == "[Filtered]" |
| 176 | + assert event["request"]["data"]["photo"] == "" |
| 177 | + assert event["_meta"]["request"]["data"]["photo"] == {"": {"rem": [["!raw", "x"]]}} |
| 178 | + |
| 179 | + assert transaction_event["request"]["data"].keys() == PARSED_FORM.keys() |
| 180 | + assert transaction_event["request"]["data"]["username"] == PARSED_FORM["username"] |
| 181 | + assert transaction_event["request"]["data"]["password"] == "[Filtered]" |
| 182 | + assert transaction_event["request"]["data"]["photo"] == "" |
| 183 | + assert transaction_event["_meta"]["request"]["data"]["photo"] == { |
| 184 | + "": {"rem": [["!raw", "x"]]} |
| 185 | + } |
| 186 | + |
| 187 | + |
| 188 | +@pytest.mark.asyncio |
| 189 | +async def test_request_body_too_big(sentry_init, capture_events): |
| 190 | + sentry_init( |
| 191 | + traces_sample_rate=1.0, |
| 192 | + send_default_pii=True, |
| 193 | + integrations=[StarletteIntegration()], |
| 194 | + ) |
| 195 | + |
| 196 | + app = fastapi_app_factory() |
| 197 | + client = TestClient(app) |
| 198 | + |
| 199 | + events = capture_events() |
| 200 | + |
| 201 | + client.post( |
| 202 | + "/body/form", |
| 203 | + data=BODY_FORM.encode("utf-8"), |
| 204 | + headers={ |
| 205 | + "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", |
| 206 | + "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", |
| 207 | + }, |
| 208 | + ) |
| 209 | + |
| 210 | + (event, transaction_event) = events |
| 211 | + assert event["request"]["cookies"] == { |
| 212 | + "tasty_cookie": "strawberry", |
| 213 | + "yummy_cookie": "choco", |
| 214 | + } |
| 215 | + # Because request is too big only the AnnotatedValue is extracted. |
| 216 | + assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} |
| 217 | + |
| 218 | + assert transaction_event["request"]["cookies"] == { |
| 219 | + "tasty_cookie": "strawberry", |
| 220 | + "yummy_cookie": "choco", |
| 221 | + } |
| 222 | + # Because request is too big only the AnnotatedValue is extracted. |
| 223 | + assert transaction_event["_meta"]["request"]["data"] == { |
| 224 | + "": {"rem": [["!config", "x"]]} |
| 225 | + } |
| 226 | + |
| 227 | + |
76 | 228 | @pytest.mark.asyncio |
77 | 229 | async def test_response(sentry_init, capture_events): |
78 | 230 | # FastAPI is heavily based on Starlette so we also need |
|
0 commit comments