Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions tests/test_integration_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
assert_attachment_view_hierarchy,
assert_before_breadcrumb,
assert_no_breadcrumbs,
wait_for_file,
)
from .conditions import (
has_http,
Expand Down Expand Up @@ -366,6 +367,143 @@ def test_external_crash_reporter_http(cmake, httpserver, build_args):
assert_user_feedback(envelope)


@pytest.mark.parametrize(
"build_args",
[
({"SENTRY_BACKEND": "inproc"}),
pytest.param(
{"SENTRY_BACKEND": "breakpad"},
marks=pytest.mark.skipif(
not has_breakpad or is_qemu, reason="test needs breakpad backend"
),
),
],
)
def test_external_crash_reporter_consent_revoked(cmake, httpserver, build_args):
"""With consent revoked, must not launch the external reporter.
Before the fix, crash-reporter + user-consent-revoke still spawned
sentry_crash_reporter, which uploaded via HTTP. Consent should block that
the same way it blocks the normal transport path.
"""
tmp_path = cmake(["sentry_example", "sentry_crash_reporter"], build_args)
cache_dir = tmp_path.joinpath(".sentry-native/cache")
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run(
tmp_path,
"sentry_example",
[
"log",
"crash-reporter",
"cache-keep",
"require-user-consent",
"user-consent-revoke",
"crash",
],
expect_failure=True,
env=env,
)

assert len(httpserver.log) == 0
assert wait_for_file(cache_dir / "*.envelope")
assert len(list(cache_dir.glob("*.envelope"))) == 1


@pytest.mark.parametrize(
"build_args",
[
({"SENTRY_BACKEND": "inproc"}),
pytest.param(
{"SENTRY_BACKEND": "breakpad"},
marks=pytest.mark.skipif(
not has_breakpad or is_qemu, reason="test needs breakpad backend"
),
),
],
)
def test_external_crash_reporter_consent_revoked_no_cache(
cmake, httpserver, build_args
):
"""With consent revoked and no cache_keep, the crash envelope is discarded."""
tmp_path = cmake(["sentry_example", "sentry_crash_reporter"], build_args)
cache_dir = tmp_path.joinpath(".sentry-native/cache")
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run(
tmp_path,
"sentry_example",
[
"log",
"crash-reporter",
"require-user-consent",
"user-consent-revoke",
"crash",
],
expect_failure=True,
env=env,
)

assert len(httpserver.log) == 0
assert not cache_dir.exists() or len(list(cache_dir.glob("*.envelope"))) == 0


@pytest.mark.parametrize(
"build_args",
[
({"SENTRY_BACKEND": "inproc"}),
pytest.param(
{"SENTRY_BACKEND": "breakpad"},
marks=pytest.mark.skipif(
not has_breakpad or is_qemu, reason="test needs breakpad backend"
),
),
],
)
def test_external_crash_reporter_consent_flush(cmake, httpserver, build_args):
"""Cached crash envelope uploads once consent is given."""
tmp_path = cmake(["sentry_example", "sentry_crash_reporter"], build_args)
cache_dir = tmp_path.joinpath(".sentry-native/cache")
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run(
tmp_path,
"sentry_example",
[
"log",
"crash-reporter",
"cache-keep",
"http-retry",
"require-user-consent",
"user-consent-revoke",
"crash",
],
expect_failure=True,
env=env,
)

assert wait_for_file(cache_dir / "*.envelope")
assert len(list(cache_dir.glob("*.envelope"))) == 1
assert len(httpserver.log) == 0

httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK")
with httpserver.wait(timeout=10) as waiting:
run(
tmp_path,
"sentry_example",
[
"log",
"cache-keep",
"http-retry",
"require-user-consent",
"user-consent-give",
],
env=env,
)
assert waiting.result
assert len(list(cache_dir.glob("*.envelope"))) == 0


@pytest.mark.skipif(is_qemu, reason="unreliable under qemu-user")
def test_exception_and_session_http(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
Expand Down
70 changes: 70 additions & 0 deletions tests/test_integration_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,76 @@ def test_native_external_crash_reporter_consent_revoked(cmake, httpserver):
assert len(list(cache_dir.glob("*.envelope"))) == 1


def test_native_external_crash_reporter_consent_revoked_no_cache(cmake, httpserver):
"""With consent revoked and no cache_keep, the daemon discards the crash envelope."""
tmp_path = cmake(
["sentry_example", "sentry_crash_reporter"], {"SENTRY_BACKEND": "native"}
)
cache_dir = tmp_path / ".sentry-native" / "cache"
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run_crash(
tmp_path,
"sentry_example",
[
"log",
"crash-reporter",
"require-user-consent",
"user-consent-revoke",
"crash",
],
env=env,
)

assert len(httpserver.log) == 0
assert not cache_dir.exists() or len(list(cache_dir.glob("*.envelope"))) == 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race in native no-cache consent test

Medium Severity

test_native_external_crash_reporter_consent_revoked_no_cache asserts an empty cache and no HTTP upload right after run_crash returns, without waiting for the native daemon. Daemon crash handling is asynchronous, so those checks can pass before discard or upload finishes. The existing no-cache native pattern uses wait_for_daemon=True for this reason.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 719cd30. Configure here.



def test_native_external_crash_reporter_consent_flush(cmake, httpserver):
"""Cached crash envelope uploads once consent is given."""
tmp_path = cmake(
["sentry_example", "sentry_crash_reporter"], {"SENTRY_BACKEND": "native"}
)
cache_dir = tmp_path / ".sentry-native" / "cache"
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run_crash(
tmp_path,
"sentry_example",
[
"log",
"crash-reporter",
"cache-keep",
"http-retry",
"require-user-consent",
"user-consent-revoke",
"crash",
],
env=env,
)

assert wait_for_file(cache_dir / "*.envelope")
assert len(list(cache_dir.glob("*.envelope"))) == 1
assert len(httpserver.log) == 0

httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK")
with httpserver.wait(timeout=10) as waiting:
run(
tmp_path,
"sentry_example",
[
"log",
"cache-keep",
"http-retry",
"require-user-consent",
"user-consent-give",
],
env=env,
)
assert waiting.result
assert len(list(cache_dir.glob("*.envelope"))) == 0


def test_crash_mode_minidump_only(cmake, httpserver):
"""Mode 1: Should produce envelope with minidump attachment only"""
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "native"})
Expand Down
Loading