use serde_json::json;
use umbral_analytics::{AnalyticsClient, DEFAULT_POSTHOG_HOST, http_client};
#[test]
fn capture_payload_has_required_fields() {
let client = AnalyticsClient::new("phc_test_key", DEFAULT_POSTHOG_HOST);
let payload = client.build_payload(
"user_42",
"signup",
json!({ "plan": "pro", "amount_cents": 999 }),
);
assert_eq!(
payload["api_key"], "phc_test_key",
"api_key must be present"
);
assert_eq!(payload["event"], "signup", "event must be present");
assert_eq!(
payload["distinct_id"], "user_42",
"distinct_id must be present"
);
assert!(
payload["properties"].is_object(),
"properties must be a JSON object"
);
assert!(
payload["timestamp"].is_string(),
"timestamp must be an RFC 3339 string"
);
let ts = payload["timestamp"].as_str().unwrap();
assert!(
ts.contains('T'),
"timestamp should be in RFC 3339 / ISO 8601 form: {ts}"
);
assert!(
ts.ends_with('Z') || ts.contains('+'),
"timestamp should carry UTC offset: {ts}"
);
assert_eq!(payload["properties"]["plan"], "pro");
assert_eq!(payload["properties"]["amount_cents"], 999);
}
#[test]
fn identify_payload_shape() {
let client = AnalyticsClient::new("phc_test_key", DEFAULT_POSTHOG_HOST);
let props = json!({ "$set": { "email": "a@b.com", "plan": "free" } });
let payload = client.build_payload("user_1", "$identify", props.clone());
assert_eq!(payload["event"], "$identify");
assert_eq!(payload["properties"]["$set"]["email"], "a@b.com");
}
#[test]
fn capture_payload_with_empty_properties() {
let client = AnalyticsClient::new("phc_k", DEFAULT_POSTHOG_HOST);
let payload = client.build_payload("anon", "page_view", json!({}));
assert!(payload["properties"].is_object());
assert!(payload["properties"].as_object().unwrap().is_empty());
}
#[tokio::test]
async fn capture_without_client_is_noop() {
umbral_analytics::capture("anon", "test_event", json!({})).await;
}
#[tokio::test]
async fn identify_without_client_is_noop() {
umbral_analytics::identify("anon", json!({ "$set": { "x": 1 } })).await;
}
#[test]
fn http_client_builds_with_timeout() {
let client = http_client();
let req = client
.get("https://us.i.posthog.com/capture/")
.build()
.expect("should be able to build a GET request");
assert_eq!(req.url().host_str(), Some("us.i.posthog.com"));
}
#[test]
fn http_client_is_shared_onclock() {
let a = http_client();
let b = http_client();
let _ra = a.get("https://us.i.posthog.com").build().unwrap();
let _rb = b.get("https://us.i.posthog.com").build().unwrap();
let c = http_client();
let _rc = c.get("https://us.i.posthog.com").build().unwrap();
}
#[tokio::test]
#[ignore]
async fn live_posthog_capture_round_trip() {
let api_key = std::env::var("UMBRAL_POSTHOG_API_KEY")
.expect("UMBRAL_POSTHOG_API_KEY must be set to run this test");
let host =
std::env::var("UMBRAL_POSTHOG_HOST").unwrap_or_else(|_| DEFAULT_POSTHOG_HOST.to_string());
let client = AnalyticsClient::new(api_key, host.clone());
let payload = client.build_payload(
"test_distinct_id_umbral",
"test_event",
json!({ "source": "umbral-analytics integration test" }),
);
let resp = http_client()
.post(format!("{}/capture/", host.trim_end_matches('/')))
.json(&payload)
.send()
.await
.expect("PostHog request should not fail at the network level");
assert!(
resp.status().is_success(),
"PostHog /capture/ returned non-success: {} — check your API key",
resp.status()
);
}