use std::rc::Rc;
use std::time::{Duration, SystemTime};
use teksilo_core::telemetry::{ConsentScope, ConsentState, Event, EventCategory, TelemetryError};
use teksilo_settings::{AppPaths, SettingsStore};
use teksilo_telemetry::{StubReporter, TelemetryBundle, TelemetryMode, UsageReporter};
use tempfile::tempdir;
fn make_settings(paths: &AppPaths) -> SettingsStore {
SettingsStore::open_with_delay(paths.config_file("general"), Duration::ZERO).unwrap()
}
fn make_event<'a>(name: &'static str, install_id: Option<&'a str>) -> Event<'a> {
Event {
name,
category: EventCategory::Intent,
timestamp: SystemTime::UNIX_EPOCH,
install_id,
session_id: "test-session",
schema_version: 1,
props: &[],
}
}
#[test]
fn end_to_end_pseudonymous_lifecycle() {
let dir = tempdir().unwrap();
let paths = AppPaths::for_testing(dir.path());
let settings = make_settings(&paths);
let stub = Rc::new(StubReporter::pseudonymous("test-uuid"));
let stub_handle = stub.clone();
let opened = TelemetryBundle::new(1)
.with_pseudonymous(stub as Rc<dyn UsageReporter>)
.with_default_mode(TelemetryMode::Pseudonymous)
.with_debounce(Duration::ZERO)
.with_data_processor_name("Test Co.")
.with_privacy_policy_url("https://example.com/privacy")
.open(&paths, &settings)
.unwrap();
assert!(matches!(
opened.consent.state_signal().get(),
ConsentState::Unknown
));
assert!(opened.install_id.is_some());
let install_id = opened.install_id.as_ref().unwrap().get();
assert!(!install_id.is_empty());
assert_eq!(stub_handle.recorded_count(), 0);
let event = make_event("intent.dispatched", Some(&install_id));
opened.reporter.record(&event);
assert_eq!(stub_handle.recorded_count(), 0, "consent gate must drop");
opened
.consent
.grant(ConsentScope::all(), opened.reporter.endpoint())
.unwrap();
opened.reporter.record(&event);
opened.reporter.record(&event);
assert_eq!(stub_handle.recorded_count(), 2);
let export = opened.reporter.fetch_remote_data().unwrap();
assert_eq!(export.install_id, "test-uuid");
assert_eq!(export.events.len(), 2);
opened.reporter.erase_remote_data().unwrap();
assert_eq!(stub_handle.recorded_count(), 0);
opened.consent.withdraw().unwrap();
opened.reporter.record(&event);
assert_eq!(stub_handle.recorded_count(), 0);
}
#[test]
fn end_to_end_anonymous_no_install_id_no_fetch_no_erase() {
let dir = tempdir().unwrap();
let paths = AppPaths::for_testing(dir.path());
let settings = make_settings(&paths);
let stub = Rc::new(StubReporter::anonymous());
let stub_handle = stub.clone();
let opened = TelemetryBundle::new(1)
.with_anonymous(stub as Rc<dyn UsageReporter>)
.with_default_mode(TelemetryMode::Anonymous)
.with_debounce(Duration::ZERO)
.open(&paths, &settings)
.unwrap();
assert!(opened.install_id.is_none());
opened
.consent
.grant(ConsentScope::anonymous_metrics_only(), "stub://")
.unwrap();
let event = make_event("intent.dispatched", None);
opened.reporter.record(&event);
assert_eq!(stub_handle.recorded_count(), 1);
assert!(matches!(
opened.reporter.fetch_remote_data(),
Err(TelemetryError::FetchUnsupported)
));
assert!(matches!(
opened.reporter.erase_remote_data(),
Err(TelemetryError::ErasureUnsupported)
));
}
#[test]
fn schema_version_bump_triggers_reprompt() {
let dir = tempdir().unwrap();
let paths = AppPaths::for_testing(dir.path());
let settings = make_settings(&paths);
{
let stub = Rc::new(StubReporter::anonymous());
let opened = TelemetryBundle::new(1)
.with_anonymous(stub as Rc<dyn UsageReporter>)
.with_debounce(Duration::ZERO)
.open(&paths, &settings)
.unwrap();
opened
.consent
.grant(
ConsentScope::anonymous_metrics_only(),
opened.reporter.endpoint(),
)
.unwrap();
opened.consent.flush_now().unwrap();
}
let stub = Rc::new(StubReporter::anonymous());
let opened = TelemetryBundle::new(2)
.with_anonymous(stub as Rc<dyn UsageReporter>)
.with_debounce(Duration::ZERO)
.open(&paths, &settings)
.unwrap();
assert!(matches!(
opened.consent.state_signal().get(),
ConsentState::Unknown
));
}
#[test]
fn mode_switch_changes_active_adapter() {
let dir = tempdir().unwrap();
let paths = AppPaths::for_testing(dir.path());
let settings = make_settings(&paths);
let anon = Rc::new(StubReporter::anonymous());
let pseudo = Rc::new(StubReporter::pseudonymous("uuid-2"));
let anon_handle = anon.clone();
let pseudo_handle = pseudo.clone();
let opened = TelemetryBundle::new(1)
.with_anonymous(anon as Rc<dyn UsageReporter>)
.with_pseudonymous(pseudo as Rc<dyn UsageReporter>)
.with_default_mode(TelemetryMode::Anonymous)
.with_debounce(Duration::ZERO)
.open(&paths, &settings)
.unwrap();
opened
.consent
.grant(ConsentScope::all(), opened.reporter.endpoint())
.unwrap();
let id = opened.install_id.as_ref().unwrap().get();
let event = make_event("intent.dispatched", Some(&id));
opened.reporter.record(&event);
assert_eq!(anon_handle.recorded_count(), 1);
assert_eq!(pseudo_handle.recorded_count(), 0);
opened.reporter.set_active_mode(TelemetryMode::Pseudonymous);
opened.reporter.record(&event);
assert_eq!(anon_handle.recorded_count(), 1, "no new anon events");
assert_eq!(pseudo_handle.recorded_count(), 1);
assert!(opened.reporter.supports_mode_switch());
}
#[test]
fn opened_telemetry_is_clone() {
let dir = tempdir().unwrap();
let paths = AppPaths::for_testing(dir.path());
let settings = make_settings(&paths);
let opened = TelemetryBundle::new(1)
.with_anonymous(Rc::new(StubReporter::anonymous()) as Rc<dyn UsageReporter>)
.with_debounce(Duration::ZERO)
.open(&paths, &settings)
.unwrap();
let clone1 = opened.clone();
let clone2 = opened.clone();
assert_eq!(clone1.event_schema_version, clone2.event_schema_version);
}