Skip to content

Commit 9669409

Browse files
committed
Add test for fastapi-route-discovery
1 parent 561b5ca commit 9669409

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

.github/workflows/end2end.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
- { name: flask-postgres-xml, testfile: end2end/flask_postgres_xml_lxml_test.py }
3838
- { name: quart-postgres-uvicorn, testfile: end2end/quart_postgres_uvicorn_test.py }
3939
- { name: starlette-postgres-uvicorn, testfile: end2end/starlette_postgres_uvicorn_test.py }
40+
- { name: fastapi-postgres-uvicorn, testfile: end2end/fastapi_postgres_uvicorn_test.py }
4041
python-version: ["3.10", "3.11", "3.12", "3.13"]
4142
steps:
4243
- name: Install packages
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import time
2+
import pytest
3+
import requests
4+
from .server.check_events_from_mock import (
5+
fetch_events_from_mock,
6+
validate_started_event,
7+
validate_heartbeat,
8+
filter_on_event_type,
9+
)
10+
11+
post_url_fw = "http://localhost:8112/create"
12+
post_url_nofw = "http://localhost:8113/create"
13+
sync_route_fw = "http://localhost:8112/sync_route"
14+
sync_route_nofw = "http://localhost:8113/sync_route"
15+
16+
17+
def test_firewall_started_okay():
18+
events = fetch_events_from_mock("http://localhost:5000")
19+
started_events = filter_on_event_type(events, "started")
20+
assert len(started_events) == 1
21+
validate_started_event(started_events[0], None)
22+
23+
24+
def test_safe_response_with_firewall():
25+
res = requests.post(post_url_fw, data={"dog_name": "Bobby Tables"})
26+
assert res.status_code == 201
27+
28+
29+
def test_safe_response_without_firewall():
30+
res = requests.post(post_url_nofw, data={"dog_name": "Bobby Tables"})
31+
assert res.status_code == 201
32+
33+
34+
def test_dangerous_response_with_firewall():
35+
dog_name = "Dangerous Bobby', TRUE); -- "
36+
res = requests.post(post_url_fw, data={"dog_name": dog_name})
37+
assert res.status_code == 500
38+
39+
time.sleep(5) # Wait for attack to be reported
40+
events = fetch_events_from_mock("http://localhost:5000")
41+
attacks = filter_on_event_type(events, "detected_attack")
42+
43+
assert len(attacks) == 1
44+
del attacks[0]["attack"]["stack"]
45+
assert attacks[0]["attack"]["blocked"] == True
46+
assert attacks[0]["attack"]["kind"] == "sql_injection"
47+
assert attacks[0]["attack"]["metadata"]["sql"] == "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous Bobby', TRUE); -- ', FALSE)"
48+
assert attacks[0]["attack"]["metadata"]["dialect"] == "postgres"
49+
assert attacks[0]["attack"]["operation"] == "asyncpg.connection.Connection.execute"
50+
assert attacks[0]["attack"]["pathToPayload"] == ".dog_name"
51+
assert attacks[0]["attack"]["payload"] == "\"Dangerous Bobby', TRUE); -- \""
52+
assert attacks[0]["attack"]["source"] == "body"
53+
assert attacks[0]["attack"]["user"]["id"] == "user123"
54+
assert attacks[0]["attack"]["user"]["name"] == "John Doe"
55+
56+
# These assertions verify the pre/post response hooks fired for FastAPI APIRoute endpoints.
57+
# Without patching fastapi.routing.request_response, route will be None/empty and
58+
# source will not reflect the fastapi framework context.
59+
assert attacks[0]["request"]["route"] == "/create"
60+
assert attacks[0]["request"]["userAgent"] == "python-requests/2.32.3"
61+
62+
63+
def test_dangerous_response_without_firewall():
64+
dog_name = "Dangerous Bobby', TRUE); -- "
65+
res = requests.post(post_url_nofw, data={"dog_name": dog_name})
66+
assert res.status_code == 201
67+
68+
69+
def test_sync_route_with_firewall():
70+
res = requests.get(sync_route_fw)
71+
assert res.status_code == 200
72+
73+
74+
def test_sync_route_without_firewall():
75+
res = requests.get(sync_route_nofw)
76+
assert res.status_code == 200
77+
78+
79+
def test_routes_discovered_in_heartbeat():
80+
# This test verifies that FastAPI APIRoute endpoints are discovered via route discovery.
81+
# Without patching fastapi.routing.request_response, the heartbeat will report
82+
# current_routes: {} even with active traffic, because the post_response hook never fires.
83+
time.sleep(55) # Wait for first heartbeat (fires ~60s after start)
84+
85+
events = fetch_events_from_mock("http://localhost:5000")
86+
heartbeat_events = filter_on_event_type(events, "heartbeat")
87+
assert len(heartbeat_events) >= 1
88+
89+
validate_heartbeat(
90+
heartbeat_events[0],
91+
routes=[{
92+
"apispec": {
93+
"body": {
94+
"type": "form-urlencoded",
95+
"schema": {
96+
"type": "object",
97+
"properties": {
98+
"dog_name": {
99+
"items": {"type": "string"},
100+
"type": "array",
101+
}
102+
},
103+
},
104+
},
105+
"query": None,
106+
"auth": None,
107+
},
108+
"hits": 1,
109+
"hits_delta_since_sync": 0,
110+
"method": "POST",
111+
"path": "/create",
112+
}],
113+
)

0 commit comments

Comments
 (0)