#![cfg(feature = "error-tracking")]
use httpmock::prelude::*;
use std::panic::{self, AssertUnwindSafe};
#[inline(never)]
fn integration_panic_site() {
panic!("integration panic boom");
}
fn is_panic_exception(req: &HttpMockRequest) -> bool {
let Ok(body) = serde_json::from_slice::<serde_json::Value>(req.body_ref()) else {
return false;
};
let is_panic = body.pointer("/batch/0/event").and_then(|v| v.as_str()) == Some("$exception")
&& body
.pointer("/batch/0/properties/$exception_list/0/type")
.and_then(|v| v.as_str())
== Some("Panic");
let Some(frames) = body
.pointer("/batch/0/properties/$exception_list/0/stacktrace/frames")
.and_then(|v| v.as_array())
else {
return false;
};
let function_names: Vec<&str> = frames
.iter()
.map(|frame| frame["function"].as_str().unwrap_or_default())
.collect();
let panic_site_in_app = frames.iter().any(|frame| {
frame["function"]
.as_str()
.is_some_and(|name| name.contains("integration_panic_site"))
&& frame["in_app"] == true
});
let no_sdk_frames = !function_names
.iter()
.any(|name| name.contains("posthog_rs::error_tracking"));
let tail_is_panic_runtime = function_names
.last()
.is_some_and(|name| name.contains("panicking::") || name.contains("panic_with_hook"));
let panic_site_before_runtime = function_names
.iter()
.position(|name| name.contains("integration_panic_site"))
.zip(
function_names
.iter()
.rposition(|name| name.contains("panic_with_hook")),
)
.is_some_and(|(site, runtime)| site < runtime);
is_panic
&& panic_site_in_app
&& no_sdk_frames
&& tail_is_panic_runtime
&& panic_site_before_runtime
}
#[cfg(feature = "async-client")]
fn init_global_for_test(options: posthog_rs::ClientOptions) -> Result<(), posthog_rs::Error> {
futures::executor::block_on(posthog_rs::init_global(options))
}
#[cfg(not(feature = "async-client"))]
fn init_global_for_test(options: posthog_rs::ClientOptions) -> Result<(), posthog_rs::Error> {
posthog_rs::init_global(options)
}
#[test]
fn init_global_installs_panic_capture_when_enabled() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(POST).matches(is_panic_exception);
then.status(200);
});
panic::set_hook(Box::new(|_| {}));
let options = posthog_rs::ClientOptionsBuilder::default()
.api_key("test_api_key".to_string())
.host(server.base_url())
.error_tracking(
posthog_rs::ErrorTrackingOptionsBuilder::default()
.capture_panics(true)
.build()
.unwrap(),
)
.build()
.unwrap();
init_global_for_test(options).expect("init_global should succeed");
let _ = panic::catch_unwind(AssertUnwindSafe(integration_panic_site));
mock.assert_hits(1);
}