diff --git a/tests/integrations/falcon/test_falcon.py b/tests/integrations/falcon/test_falcon.py index 15122fc642..75ad903a04 100644 --- a/tests/integrations/falcon/test_falcon.py +++ b/tests/integrations/falcon/test_falcon.py @@ -64,15 +64,34 @@ def inner(): return inner -def test_has_context(sentry_init, capture_events, make_client): - sentry_init(integrations=[FalconIntegration()]) - events = capture_events() +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_has_context( + sentry_init, + capture_events, + capture_items, + make_client, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) client = make_client() - response = client.simulate_get("/message") - assert response.status == falcon.HTTP_200 + if span_streaming: + items = capture_items("event") + + response = client.simulate_get("/message") + assert response.status == falcon.HTTP_200 + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + response = client.simulate_get("/message") + assert response.status == falcon.HTTP_200 - (event,) = events + (event,) = events assert event["transaction"] == "/message" # Falcon URI template assert "data" not in event["request"] assert event["request"]["url"] == "http://falconframework.org/message" @@ -87,30 +106,55 @@ def test_has_context(sentry_init, capture_events, make_client): ("/message/123456", "path", "/message/123456", "url"), ], ) +@pytest.mark.parametrize("span_streaming", [True, False]) def test_transaction_style( sentry_init, make_client, capture_events, + capture_items, url, transaction_style, expected_transaction, expected_source, + span_streaming, ): integration = FalconIntegration(transaction_style=transaction_style) - sentry_init(integrations=[integration]) - events = capture_events() + sentry_init( + integrations=[integration], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) client = make_client() - response = client.simulate_get(url) - assert response.status == falcon.HTTP_200 + if span_streaming: + items = capture_items("event") + + response = client.simulate_get(url) + assert response.status == falcon.HTTP_200 + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + response = client.simulate_get(url) + assert response.status == falcon.HTTP_200 - (event,) = events + (event,) = events assert event["transaction"] == expected_transaction assert event["transaction_info"] == {"source": expected_source} -def test_unhandled_errors(sentry_init, capture_exceptions, capture_events): - sentry_init(integrations=[FalconIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_unhandled_errors( + sentry_init, + capture_exceptions, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) class Resource: def on_get(self, req, resp): @@ -119,26 +163,49 @@ def on_get(self, req, resp): app = falcon.API() app.add_route("/", Resource()) + client = falcon.testing.TestClient(app) + exceptions = capture_exceptions() - events = capture_events() + if span_streaming: + items = capture_items("event") - client = falcon.testing.TestClient(app) + try: + client.simulate_get("/") + except ZeroDivisionError: + pass - try: - client.simulate_get("/") - except ZeroDivisionError: - pass + (exc,) = exceptions + assert isinstance(exc, ZeroDivisionError) + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + try: + client.simulate_get("/") + except ZeroDivisionError: + pass - (exc,) = exceptions - assert isinstance(exc, ZeroDivisionError) + (exc,) = exceptions + assert isinstance(exc, ZeroDivisionError) - (event,) = events + (event,) = events assert event["exception"]["values"][0]["mechanism"]["type"] == "falcon" assert " by zero" in event["exception"]["values"][0]["value"] -def test_raised_5xx_errors(sentry_init, capture_exceptions, capture_events): - sentry_init(integrations=[FalconIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_raised_5xx_errors( + sentry_init, + capture_exceptions, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) class Resource: def on_get(self, req, resp): @@ -147,22 +214,43 @@ def on_get(self, req, resp): app = falcon.API() app.add_route("/", Resource()) + client = falcon.testing.TestClient(app) + exceptions = capture_exceptions() - events = capture_events() + if span_streaming: + items = capture_items("event") - client = falcon.testing.TestClient(app) - client.simulate_get("/") + client.simulate_get("/") + + (exc,) = exceptions + assert isinstance(exc, falcon.HTTPError) + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + client.simulate_get("/") - (exc,) = exceptions - assert isinstance(exc, falcon.HTTPError) + (exc,) = exceptions + assert isinstance(exc, falcon.HTTPError) - (event,) = events + (event,) = events assert event["exception"]["values"][0]["mechanism"]["type"] == "falcon" assert event["exception"]["values"][0]["type"] == "HTTPError" -def test_raised_4xx_errors(sentry_init, capture_exceptions, capture_events): - sentry_init(integrations=[FalconIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_raised_4xx_errors( + sentry_init, + capture_exceptions, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) class Resource: def on_get(self, req, resp): @@ -172,7 +260,10 @@ def on_get(self, req, resp): app.add_route("/", Resource()) exceptions = capture_exceptions() - events = capture_events() + if span_streaming: + events = capture_items("event") + else: + events = capture_events() client = falcon.testing.TestClient(app) client.simulate_get("/") @@ -181,12 +272,22 @@ def on_get(self, req, resp): assert len(events) == 0 -def test_http_status(sentry_init, capture_exceptions, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_http_status( + sentry_init, + capture_exceptions, + capture_events, + capture_items, + span_streaming, +): """ This just demonstrates, that if Falcon raises a HTTPStatus with code 500 (instead of a HTTPError with code 500) Sentry will not capture it. """ - sentry_init(integrations=[FalconIntegration()]) + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) class Resource: def on_get(self, req, resp): @@ -195,22 +296,36 @@ def on_get(self, req, resp): app = falcon.API() app.add_route("/", Resource()) + client = falcon.testing.TestClient(app) + exceptions = capture_exceptions() - events = capture_events() + if span_streaming: + events = capture_items("event") - client = falcon.testing.TestClient(app) - client.simulate_get("/") + client.simulate_get("/") + else: + events = capture_events() + + client.simulate_get("/") assert len(exceptions) == 0 assert len(events) == 0 @pytest.mark.parametrize("max_value_length", [1024, None]) -def test_falcon_large_json_request(sentry_init, capture_events, max_value_length): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_falcon_large_json_request( + sentry_init, + capture_events, + capture_items, + max_value_length, + span_streaming, +): sentry_init( integrations=[FalconIntegration()], max_request_body_size="always", max_value_length=max_value_length, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) data = {"foo": {"bar": "a" * (1034)}} @@ -224,13 +339,22 @@ def on_post(self, req, resp): app = falcon.API() app.add_route("/", Resource()) - events = capture_events() - client = falcon.testing.TestClient(app) - response = client.simulate_post("/", json=data) - assert response.status == falcon.HTTP_200 - (event,) = events + if span_streaming: + items = capture_items("event") + + response = client.simulate_post("/", json=data) + assert response.status == falcon.HTTP_200 + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + response = client.simulate_post("/", json=data) + assert response.status == falcon.HTTP_200 + + (event,) = events if max_value_length: assert event["_meta"]["request"]["data"]["foo"]["bar"] == { "": { @@ -244,8 +368,18 @@ def on_post(self, req, resp): @pytest.mark.parametrize("data", [{}, []], ids=["empty-dict", "empty-list"]) -def test_falcon_empty_json_request(sentry_init, capture_events, data): - sentry_init(integrations=[FalconIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_falcon_empty_json_request( + sentry_init, + capture_events, + capture_items, + data, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) class Resource: def on_post(self, req, resp): @@ -256,18 +390,36 @@ def on_post(self, req, resp): app = falcon.API() app.add_route("/", Resource()) - events = capture_events() - client = falcon.testing.TestClient(app) - response = client.simulate_post("/", json=data) - assert response.status == falcon.HTTP_200 - (event,) = events + if span_streaming: + items = capture_items("event") + + response = client.simulate_post("/", json=data) + assert response.status == falcon.HTTP_200 + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + response = client.simulate_post("/", json=data) + assert response.status == falcon.HTTP_200 + + (event,) = events assert event["request"]["data"] == data -def test_falcon_raw_data_request(sentry_init, capture_events): - sentry_init(integrations=[FalconIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_falcon_raw_data_request( + sentry_init, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) class Resource: def on_post(self, req, resp): @@ -277,20 +429,36 @@ def on_post(self, req, resp): app = falcon.API() app.add_route("/", Resource()) - events = capture_events() - client = falcon.testing.TestClient(app) - response = client.simulate_post("/", body="hi") - assert response.status == falcon.HTTP_200 - (event,) = events + if span_streaming: + items = capture_items("event") + + response = client.simulate_post("/", body="hi") + assert response.status == falcon.HTTP_200 + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + response = client.simulate_post("/", body="hi") + assert response.status == falcon.HTTP_200 + + (event,) = events assert event["request"]["headers"]["Content-Length"] == "2" assert event["request"]["data"] == "" -def test_logging(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_logging( + sentry_init, + capture_events, + capture_items, + span_streaming, +): sentry_init( - integrations=[FalconIntegration(), LoggingIntegration(event_level="ERROR")] + integrations=[FalconIntegration(), LoggingIntegration(event_level="ERROR")], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) logger = logging.getLogger() @@ -304,12 +472,20 @@ def on_get(self, req, resp): app.add_route("/", Resource()) - events = capture_events() - client = falcon.testing.TestClient(app) - client.simulate_get("/") - (event,) = events + if span_streaming: + items = capture_items("event") + + client.simulate_get("/") + + (event,) = (item.payload for item in items) + else: + events = capture_events() + + client.simulate_get("/") + + (event,) = events assert event["level"] == "error" @@ -336,8 +512,17 @@ def http500_handler(ex, req, resp, params): assert response.json == {"message": "Sentry error."} -def test_error_in_errorhandler(sentry_init, capture_events): - sentry_init(integrations=[FalconIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_error_in_errorhandler( + sentry_init, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) app = falcon.API() @@ -351,24 +536,39 @@ def http500_handler(ex, req, resp, params): 1 / 0 app.add_error_handler(Exception, http500_handler) + client = falcon.testing.TestClient(app) - events = capture_events() + if span_streaming: + items = capture_items("event") - client = falcon.testing.TestClient(app) + with pytest.raises(ZeroDivisionError): + client.simulate_get("/") - with pytest.raises(ZeroDivisionError): - client.simulate_get("/") + (event,) = (item.payload for item in items) + else: + events = capture_events() - (event,) = events + with pytest.raises(ZeroDivisionError): + client.simulate_get("/") + + (event,) = events last_ex_values = event["exception"]["values"][-1] assert last_ex_values["type"] == "ZeroDivisionError" assert last_ex_values["stacktrace"]["frames"][-1]["vars"]["ex"] == "ValueError()" -def test_bad_request_not_captured(sentry_init, capture_events): - sentry_init(integrations=[FalconIntegration()]) - events = capture_events() +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_bad_request_not_captured( + sentry_init, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) app = falcon.API() @@ -380,16 +580,27 @@ def on_get(self, req, resp): client = falcon.testing.TestClient(app) + if span_streaming: + events = capture_items("event") + else: + events = capture_events() + client.simulate_get("/") assert not events -def test_does_not_leak_scope(sentry_init, capture_events): - sentry_init(integrations=[FalconIntegration()]) - events = capture_events() - - sentry_sdk.get_isolation_scope().set_tag("request_data", False) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_does_not_leak_scope( + sentry_init, + capture_events, + capture_items, + span_streaming, +): + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) app = falcon.API() @@ -408,6 +619,14 @@ def generator(): app.add_route("/", Resource()) client = falcon.testing.TestClient(app) + + if span_streaming: + events = capture_items("event") + else: + events = capture_events() + + sentry_sdk.get_isolation_scope().set_tag("request_data", False) + response = client.simulate_get("/") expected_response = "".join(str(row) + "\n" for row in range(1000)) @@ -419,7 +638,8 @@ def generator(): @pytest.mark.skipif( not hasattr(falcon, "asgi"), reason="This Falcon version lacks ASGI support." ) -def test_falcon_not_breaking_asgi(sentry_init): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_falcon_not_breaking_asgi(sentry_init, span_streaming): """ This test simply verifies that the Falcon integration does not break ASGI Falcon apps. @@ -427,7 +647,10 @@ def test_falcon_not_breaking_asgi(sentry_init): The test does not verify ASGI Falcon support, since our Falcon integration currently lacks support for ASGI Falcon apps. """ - sentry_init(integrations=[FalconIntegration()]) + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) asgi_app = falcon.asgi.App() @@ -441,38 +664,73 @@ def test_falcon_not_breaking_asgi(sentry_init): (FALCON_VERSION or ()) < (3,), reason="The Sentry Falcon integration only supports custom error handlers on Falcon 3+", ) -def test_falcon_custom_error_handler(sentry_init, make_app, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_falcon_custom_error_handler( + sentry_init, + make_app, + capture_events, + capture_items, + span_streaming, +): """ When a custom error handler handles what otherwise would have resulted in a 5xx error, changing the HTTP status to a non-5xx status, no error event should be sent to Sentry. """ - sentry_init(integrations=[FalconIntegration()]) - events = capture_events() + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) app = make_app() client = falcon.testing.TestClient(app) + if span_streaming: + events = capture_items("event") + else: + events = capture_events() + client.simulate_get("/custom-error") assert len(events) == 0 -def test_span_origin(sentry_init, capture_events, make_client): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_span_origin( + sentry_init, + capture_events, + capture_items, + make_client, + span_streaming, +): sentry_init( integrations=[FalconIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() client = make_client() - client.simulate_get("/message") - (_, event) = events + if span_streaming: + items = capture_items("span") + + client.simulate_get("/message") + + sentry_sdk.flush() + spans = [item.payload for item in items] + + assert spans[0]["attributes"]["sentry.origin"] == "auto.http.falcon" + else: + events = capture_events() + + client.simulate_get("/message") - assert event["contexts"]["trace"]["origin"] == "auto.http.falcon" + (_, event) = events + assert event["contexts"]["trace"]["origin"] == "auto.http.falcon" -def test_falcon_request_media(sentry_init): + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_falcon_request_media(sentry_init, span_streaming): # test_passed stores whether the test has passed. test_passed = False @@ -498,7 +756,10 @@ def on_post(self, req, _): test_passed = raw_data != b"" test_failure_reason = "request body has been read" - sentry_init(integrations=[FalconIntegration()]) + sentry_init( + integrations=[FalconIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) try: app_class = falcon.App # Falcon ≥3.0