Files
openhuman/app/src-tauri/src/lib_tests.rs
T

1035 lines
37 KiB
Rust

use super::*;
// Tests that read/write process-global env vars must serialize through this
// mutex. Rust's test runner executes tests in parallel by default; without
// coordination, concurrent set_var / remove_var calls race and produce
// spurious failures.
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
// NOTE: `is_daemon_mode_detects_daemon_flag` removed (plan.md §2.1) — it
// discarded the result with `let _` and asserted nothing.
// Regression for #4809 + #4818: the restore command is chosen per frame.
// - A minimized frame (desktop shortcut / taskbar / tray while minimized) must
// un-minimize the OS frame; `SW_SHOW` leaves it minimized, so it uses
// `SW_RESTORE` (#4809).
// - A hidden-but-not-minimized frame may be maximized; `SW_RESTORE` would drop
// it back to normal size, so it uses `SW_SHOW` to preserve the maximized
// geometry (#4818).
// Windows-only: `window_show_command` is `cfg(windows)`.
#[cfg(target_os = "windows")]
#[test]
fn restore_command_preserves_minimized_and_maximized_frames() {
use windows_sys::Win32::UI::WindowsAndMessaging::{SW_HIDE, SW_RESTORE, SW_SHOW};
// Minimized restore un-minimizes AND un-hides.
assert_eq!(window_show_command(false, true), SW_RESTORE);
// Guard against a regression back to SW_SHOW, which no-ops on a
// minimized frame and reproduces #4809.
assert_ne!(window_show_command(false, true), SW_SHOW);
// Hidden-but-not-minimized restore uses SW_SHOW so a tray-hidden maximized
// window comes back maximized (#4818), not shrunk to normal size.
assert_eq!(window_show_command(false, false), SW_SHOW);
assert_ne!(window_show_command(false, false), SW_RESTORE);
// Hide path is unchanged regardless of iconic state.
assert_eq!(window_show_command(true, false), SW_HIDE);
assert_eq!(window_show_command(true, true), SW_HIDE);
}
/// Test core_rpc_url returns expected format
#[test]
fn core_rpc_url_returns_expected_format() {
let _g = ENV_LOCK.lock().unwrap();
let original = std::env::var("OPENHUMAN_CORE_RPC_URL").ok();
std::env::set_var("OPENHUMAN_CORE_RPC_URL", "http://localhost:9999/rpc");
let url = core_rpc_url();
assert_eq!(url, "http://localhost:9999/rpc");
std::env::remove_var("OPENHUMAN_CORE_RPC_URL");
let url = core_rpc_url();
assert_eq!(url, "http://127.0.0.1:7788/rpc");
match original {
Some(v) => std::env::set_var("OPENHUMAN_CORE_RPC_URL", v),
None => std::env::remove_var("OPENHUMAN_CORE_RPC_URL"),
}
}
/// Test overlay_parent_rpc_url handles empty env var
#[test]
fn overlay_parent_rpc_url_handles_empty() {
let _g = ENV_LOCK.lock().unwrap();
let original = std::env::var("OPENHUMAN_CORE_RPC_URL").ok();
std::env::set_var("OPENHUMAN_CORE_RPC_URL", "");
assert!(overlay_parent_rpc_url().is_none());
std::env::set_var("OPENHUMAN_CORE_RPC_URL", " ");
assert!(overlay_parent_rpc_url().is_none());
std::env::set_var("OPENHUMAN_CORE_RPC_URL", "http://127.0.0.1:7788/rpc");
assert_eq!(
overlay_parent_rpc_url(),
Some("http://127.0.0.1:7788/rpc".to_string())
);
match original {
Some(v) => std::env::set_var("OPENHUMAN_CORE_RPC_URL", v),
None => std::env::remove_var("OPENHUMAN_CORE_RPC_URL"),
}
}
// NOTE: `setup_tray_function_signature_compiles` and `app_runtime_type_exists`
// removed (plan.md §2.1) — one had an empty body, the other's only real
// assertion was commented out; both were compile-only no-ops.
#[test]
fn no_app_update_available_result_is_quiet_unavailable() {
let info = no_app_update_available("0.53.43".to_string());
assert_eq!(info.current_version, "0.53.43");
assert!(!info.available);
assert!(info.available_version.is_none());
assert!(info.body.is_none());
}
// NOTE: `tray_setup_logging_patterns_exist` removed (plan.md §2.1) — a
// comments-only body that asserted nothing.
// -------------------------------------------------------------------------
// macos_os_version (issue #1012)
// -------------------------------------------------------------------------
/// On macOS, sw_vers is always present and must return a version string.
#[cfg(target_os = "macos")]
#[test]
fn macos_os_version_returns_some() {
assert!(
macos_os_version().is_some(),
"sw_vers -productVersion must succeed on macOS"
);
}
/// The returned version must be a non-empty trimmed string.
#[cfg(target_os = "macos")]
#[test]
fn macos_os_version_is_nonempty() {
let ver = macos_os_version().expect("sw_vers must return a version on macOS");
assert!(!ver.is_empty());
// No leading/trailing whitespace (the impl trims).
assert_eq!(ver, ver.trim());
}
/// The version string must look like dot-separated integers ("14.5", "13.2.1").
#[cfg(target_os = "macos")]
#[test]
fn macos_os_version_is_dotted_integer_format() {
let ver = macos_os_version().expect("sw_vers must return a version on macOS");
let all_numeric_parts = ver
.split('.')
.all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit()));
assert!(
all_numeric_parts,
"os version {ver:?} must be dot-separated integers (e.g. '14.5')"
);
}
/// The version must have at least one component (e.g. a bare major "15" is valid).
#[cfg(target_os = "macos")]
#[test]
fn macos_os_version_has_at_least_one_component() {
let ver = macos_os_version().expect("sw_vers must return a version on macOS");
assert!(
!ver.split('.').next().unwrap_or("").is_empty(),
"version must have at least one numeric component"
);
}
// -------------------------------------------------------------------------
// WSL + X11 desktop startup warning (issue #1653)
// -------------------------------------------------------------------------
#[test]
fn wsl_x11_warning_detects_classic_x11_forwarding() {
assert!(should_warn_for_wsl_x11_desktop(true, true, false, false));
}
#[test]
fn wsl_x11_warning_skips_non_wsl_or_headless_runs() {
assert!(!should_warn_for_wsl_x11_desktop(false, true, false, false));
assert!(!should_warn_for_wsl_x11_desktop(true, false, false, false));
}
#[test]
fn wsl_x11_warning_skips_wslg_or_wayland_runs() {
assert!(!should_warn_for_wsl_x11_desktop(true, true, true, false));
assert!(!should_warn_for_wsl_x11_desktop(true, true, false, true));
}
// -------------------------------------------------------------------------
// Linux display-server pre-flight (Sentry OPENHUMAN-TAURI-K1)
// -------------------------------------------------------------------------
#[test]
fn linux_display_present_with_x11() {
assert!(linux_display_server_present(true, false));
}
#[test]
fn linux_display_present_with_wayland() {
assert!(linux_display_server_present(false, true));
}
#[test]
fn linux_display_present_with_both() {
assert!(linux_display_server_present(true, true));
}
#[test]
fn linux_display_absent_without_either() {
assert!(!linux_display_server_present(false, false));
}
#[test]
fn linux_root_uid_detected() {
assert!(linux_is_root_uid(0));
}
#[test]
fn linux_non_root_uid_not_detected() {
assert!(!linux_is_root_uid(1000));
assert!(!linux_is_root_uid(1));
}
// -------------------------------------------------------------------------
// Linux D-Bus session-bus probe (Sentry OPENHUMAN-TAURI-TM)
// -------------------------------------------------------------------------
#[test]
fn dbus_address_unix_is_supported() {
assert!(dbus_address_is_supported("unix:path=/run/user/1000/bus"));
assert!(dbus_address_is_supported("unix:abstract=/tmp/dbus-abc"));
}
#[test]
fn dbus_address_tcp_and_launchd_supported() {
assert!(dbus_address_is_supported("tcp:host=localhost,port=1234"));
assert!(dbus_address_is_supported(
"launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET"
));
assert!(dbus_address_is_supported("autolaunch:"));
}
#[test]
fn dbus_address_disabled_is_unsupported() {
// The literal value WSL2-without-WSLg sets — root cause of the panic.
assert!(!dbus_address_is_supported("disabled"));
assert!(!dbus_address_is_supported(""));
assert!(!dbus_address_is_supported(" "));
}
#[test]
fn dbus_address_unknown_transport_is_unsupported() {
assert!(!dbus_address_is_supported("nonce-tcp:host=localhost"));
assert!(!dbus_address_is_supported("bogus:"));
}
#[test]
fn dbus_address_picks_first_supported_in_list() {
// zbus walks the semicolon-separated list and uses the first reachable
// transport, so one good entry is enough.
assert!(dbus_address_is_supported(
"disabled;unix:path=/run/user/1000/bus"
));
assert!(dbus_address_is_supported(
"bogus:;tcp:host=localhost,port=55"
));
assert!(!dbus_address_is_supported("disabled;bogus:"));
}
#[test]
fn dbus_reachable_when_env_addr_is_supported() {
assert!(linux_dbus_session_reachable(
Some("unix:path=/run/user/1000/bus"),
false,
));
}
#[test]
fn dbus_unreachable_when_env_addr_disabled() {
// Even if the socket exists, an explicit `disabled` value means the
// session bus is intentionally turned off and zbus will reject it.
assert!(!linux_dbus_session_reachable(Some("disabled"), true));
}
#[test]
fn dbus_falls_back_to_runtime_socket_when_env_unset() {
assert!(linux_dbus_session_reachable(None, true));
assert!(!linux_dbus_session_reachable(None, false));
}
// -------------------------------------------------------------------------
// Platform constants (issue #1012 Sentry tagging)
// -------------------------------------------------------------------------
/// cpu_arch tag is derived from std::env::consts::ARCH which must be non-empty.
#[test]
fn platform_arch_constant_is_nonempty() {
assert!(
!std::env::consts::ARCH.is_empty(),
"ARCH constant used for Sentry cpu_arch tag must be non-empty"
);
}
/// os_name tag is derived from std::env::consts::OS which must be non-empty.
#[test]
fn platform_os_constant_is_nonempty() {
assert!(
!std::env::consts::OS.is_empty(),
"OS constant used for Sentry os_name tag must be non-empty"
);
}
/// On a macOS build the OS constant must equal "macos".
#[cfg(target_os = "macos")]
#[test]
fn platform_os_is_macos_on_macos_build() {
assert_eq!(std::env::consts::OS, "macos");
}
#[test]
fn platform_cef_gpu_workarounds_force_swiftshader_on_linux() {
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "linux", "x86_64", None, None);
// #4193: the GPU process must NOT be killed outright — `--disable-gpu`
// takes every WebGL surface (the Tiny Place world renderer) down with it.
assert!(
!args.contains(&("--disable-gpu", None)),
"--disable-gpu kills WebGL and must not be set, got: {args:?}"
);
// Instead the GPU process is pinned to ANGLE/SwiftShader software GL, which
// keeps WebGL available while still avoiding the #1697 hardware-EGL abort.
assert!(args.contains(&("--use-gl", Some("angle"))));
assert!(args.contains(&("--use-angle", Some("swiftshader"))));
assert!(args.contains(&("--enable-unsafe-swiftshader", None)));
// Page compositing stays on the CPU exactly as before.
assert!(args.contains(&("--disable-gpu-compositing", None)));
}
#[test]
fn platform_cef_gpu_workarounds_disable_intel_macos_compositing_only() {
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "macos", "x86_64", None, None);
assert_eq!(args, vec![("--disable-gpu-compositing", None)]);
}
#[test]
fn platform_cef_gpu_workarounds_leave_other_platforms_alone() {
for (os, arch) in [("macos", "aarch64"), ("windows", "x86_64")] {
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, os, arch, None, None);
assert!(
args.is_empty(),
"unexpected CEF GPU flags for {os}/{arch}: {args:?}"
);
}
}
// -------------------------------------------------------------------------
// OPENHUMAN_FORCE_GPU override (re-enables WebGL2 surfaces — Rive mascot)
// -------------------------------------------------------------------------
#[test]
fn force_gpu_default_off_when_env_unset() {
assert!(!cef_force_gpu_enabled(None));
}
#[test]
fn force_gpu_explicit_enable_values_match_prewarm_pattern() {
for v in ["1", "true", "yes", "on", "TRUE", "Yes", "On"] {
assert!(
cef_force_gpu_enabled(Some(v)),
"OPENHUMAN_FORCE_GPU={v:?} should opt in"
);
}
}
#[test]
fn force_gpu_anything_else_is_off() {
// Mirrors prewarm semantics: only explicit truthy values opt in.
for v in ["", "0", "false", "no", "off", "FALSE", "Off", "maybe", " "] {
assert!(
!cef_force_gpu_enabled(Some(v)),
"OPENHUMAN_FORCE_GPU={v:?} must not silently opt in"
);
}
}
#[test]
fn platform_cef_gpu_workarounds_skip_linux_disable_when_force_gpu_set() {
// Assert the two GPU-disable flags are absent rather than the whole
// arg list being empty: on root-Linux runners (CI in some configs)
// the function still appends `--no-sandbox` via the orthogonal
// OPENHUMAN-TAURI-K1 branch, which would make a strict `is_empty()`
// check fail spuriously. We only care about the GPU branch here.
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "linux", "x86_64", Some("1"), None);
assert!(
!args.contains(&("--disable-gpu", None)),
"OPENHUMAN_FORCE_GPU=1 must suppress --disable-gpu, got: {args:?}"
);
assert!(
!args.contains(&("--disable-gpu-compositing", None)),
"OPENHUMAN_FORCE_GPU=1 must suppress --disable-gpu-compositing, got: {args:?}"
);
// With hardware acceleration opted in, the SwiftShader software-GL fallback
// must NOT be forced either — otherwise WebGL would still be stuck on the
// software rasteriser despite the override.
assert!(
!args.contains(&("--use-angle", Some("swiftshader"))),
"OPENHUMAN_FORCE_GPU=1 must not force SwiftShader, got: {args:?}"
);
}
#[test]
fn platform_cef_gpu_workarounds_force_gpu_does_not_affect_intel_macos_path() {
// OPENHUMAN_FORCE_GPU only governs the Linux #1697 workaround; the
// separate Intel-macOS #1012 disable must still apply, regardless of
// the env var.
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "macos", "x86_64", Some("1"), None);
assert_eq!(args, vec![("--disable-gpu-compositing", None)]);
}
// -------------------------------------------------------------------------
// OPENHUMAN_DISABLE_GPU override (emergency CEF startup escape hatch)
// -------------------------------------------------------------------------
#[test]
fn disable_gpu_default_off_when_env_unset() {
assert!(!cef_disable_gpu_enabled(None));
}
#[test]
fn disable_gpu_explicit_enable_values_match_force_gpu_pattern() {
for v in ["1", "true", "yes", "on", "TRUE", "Yes", "On"] {
assert!(
cef_disable_gpu_enabled(Some(v)),
"OPENHUMAN_DISABLE_GPU={v:?} should opt in"
);
}
}
#[test]
fn disable_gpu_anything_else_is_off() {
for v in ["", "0", "false", "no", "off", "FALSE", "Off", "maybe", " "] {
assert!(
!cef_disable_gpu_enabled(Some(v)),
"OPENHUMAN_DISABLE_GPU={v:?} must not silently opt in"
);
}
}
#[test]
fn platform_cef_gpu_workarounds_disable_windows_gpu_when_requested() {
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "windows", "x86_64", None, Some("1"));
// #4385: on Windows the escape hatch pins CEF to the pure-software
// ANGLE/SwiftShader backend instead of bare `--disable-gpu`, which is
// insufficient on NVIDIA Blackwell / RTX 50-series stacks (cef::initialize
// still returns 0). This mirrors the proven Linux #1697/#4193 fallback.
assert_eq!(
args,
vec![
("--use-gl", Some("angle")),
("--use-angle", Some("swiftshader")),
("--enable-unsafe-swiftshader", None),
("--disable-gpu-compositing", None),
]
);
}
#[test]
fn platform_cef_gpu_workarounds_windows_disable_gpu_avoids_bare_disable_gpu() {
// Regression guard for #4385: `--disable-gpu` alone leaves CEF without a
// working software GL path on unsupported (Blackwell) GPUs and init keeps
// failing. The Windows escape hatch must route through SwiftShader and must
// NOT emit the lethal `--disable-gpu` switch.
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "windows", "x86_64", None, Some("1"));
assert!(
!args.contains(&("--disable-gpu", None)),
"Windows OPENHUMAN_DISABLE_GPU must not emit bare --disable-gpu (#4385), got: {args:?}"
);
assert!(
args.contains(&("--use-angle", Some("swiftshader"))),
"Windows OPENHUMAN_DISABLE_GPU must force SwiftShader software GL (#4385), got: {args:?}"
);
}
#[test]
fn platform_cef_gpu_workarounds_disable_gpu_wins_over_linux_force_gpu() {
let mut args = Vec::new();
append_platform_cef_gpu_workarounds(&mut args, "linux", "x86_64", Some("1"), Some("1"));
assert!(args.contains(&("--disable-gpu", None)));
assert!(args.contains(&("--disable-gpu-compositing", None)));
assert!(
!args.contains(&("--use-angle", Some("swiftshader"))),
"OPENHUMAN_DISABLE_GPU=1 must not also force SwiftShader, got: {args:?}"
);
}
// -------------------------------------------------------------------------
// #3554 — never forward --time-ticks-at-unix-epoch to CEF (wrong system time)
// -------------------------------------------------------------------------
#[test]
fn time_ticks_flag_matches_any_dash_and_casing_form() {
for flag in [
"--time-ticks-at-unix-epoch",
"-time-ticks-at-unix-epoch",
"time-ticks-at-unix-epoch",
"--Time-Ticks-At-Unix-Epoch",
] {
assert!(
is_time_ticks_at_unix_epoch_flag(flag),
"{flag:?} should be recognised as the time-ticks switch"
);
}
}
#[test]
fn time_ticks_flag_does_not_match_unrelated_flags() {
for flag in [
"--disable-gpu",
"--use-mock-keychain",
"--time-zone",
"--enable-features",
] {
assert!(
!is_time_ticks_at_unix_epoch_flag(flag),
"{flag:?} must not be treated as the time-ticks switch"
);
}
}
#[test]
fn strip_time_ticks_removes_negative_value_and_keeps_the_rest() {
// The corrupt value reported in #3554, alongside flags we must preserve.
let mut args = vec![
("--use-mock-keychain", None),
("--time-ticks-at-unix-epoch", Some("-1780937467390432")),
("--disable-gpu", None),
];
strip_time_ticks_at_unix_epoch(&mut args);
assert_eq!(
args,
vec![("--use-mock-keychain", None), ("--disable-gpu", None)],
"the corrupt time-ticks switch must be removed; everything else kept"
);
}
#[test]
fn strip_time_ticks_removes_inline_value_form() {
// The critical bypass case: when the value is carried inline as
// `--flag=value` (a single token) rather than as a separate value, the
// matcher must still recognise and strip it.
let mut args = vec![
("--use-mock-keychain", None),
("--time-ticks-at-unix-epoch=-1780937467390432", None),
("--disable-gpu", None),
];
strip_time_ticks_at_unix_epoch(&mut args);
assert_eq!(
args,
vec![("--use-mock-keychain", None), ("--disable-gpu", None)],
"the inline time-ticks switch must be removed; everything else kept"
);
}
#[test]
fn strip_time_ticks_removes_the_flag_regardless_of_value() {
// Even a non-negative value is dropped: OpenHuman must let Chromium
// compute the clock origin rather than anchor it from the shell.
let mut args = vec![
("--time-ticks-at-unix-epoch", Some("1780937467390432")),
("--time-ticks-at-unix-epoch", None),
];
strip_time_ticks_at_unix_epoch(&mut args);
assert!(
args.is_empty(),
"all time-ticks variants must be stripped, got: {args:?}"
);
}
#[test]
fn strip_time_ticks_is_a_noop_without_the_flag() {
let mut args = vec![("--use-mock-keychain", None), ("--disable-gpu", None)];
let expected = args.clone();
strip_time_ticks_at_unix_epoch(&mut args);
assert_eq!(args, expected, "unrelated args must be left untouched");
}
/// On an Intel macOS build the ARCH constant must equal "x86_64".
/// This is the architecture that triggers --disable-gpu-compositing.
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
#[test]
fn platform_arch_is_x86_64_on_intel_build() {
assert_eq!(std::env::consts::ARCH, "x86_64");
}
/// On Apple Silicon the ARCH constant must equal "aarch64"; the GPU flag
/// must NOT be compiled in (verified by this test existing in the binary).
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
#[test]
fn platform_arch_is_aarch64_on_apple_silicon_build() {
assert_eq!(std::env::consts::ARCH, "aarch64");
}
// -------------------------------------------------------------------------
// cef_prewarm_enabled (issue #2463 — Wayland/XWayland BadWindow guard)
// -------------------------------------------------------------------------
#[test]
fn prewarm_enabled_by_default_on_non_wayland() {
assert!(cef_prewarm_enabled(None, false));
}
#[test]
fn prewarm_auto_disabled_on_wayland_when_env_unset() {
assert!(!cef_prewarm_enabled(None, true));
}
#[test]
fn prewarm_explicit_disable_respected_on_non_wayland() {
assert!(!cef_prewarm_enabled(Some("0"), false));
assert!(!cef_prewarm_enabled(Some("false"), false));
assert!(!cef_prewarm_enabled(Some("no"), false));
assert!(!cef_prewarm_enabled(Some("off"), false));
}
#[test]
fn prewarm_explicit_disable_respected_on_wayland() {
assert!(!cef_prewarm_enabled(Some("0"), true));
assert!(!cef_prewarm_enabled(Some("false"), true));
}
#[test]
fn prewarm_explicit_enable_overrides_wayland_guard() {
// OPENHUMAN_CEF_PREWARM=1 (or any non-disable value) lets ops
// force prewarm even on Wayland sessions.
assert!(cef_prewarm_enabled(Some("1"), true));
assert!(cef_prewarm_enabled(Some("true"), true));
assert!(cef_prewarm_enabled(Some("yes"), true));
assert!(cef_prewarm_enabled(Some("on"), true));
}
#[test]
fn prewarm_disable_flags_are_case_insensitive() {
assert!(!cef_prewarm_enabled(Some("FALSE"), false));
assert!(!cef_prewarm_enabled(Some("OFF"), true));
assert!(!cef_prewarm_enabled(Some(" 0 "), false));
assert!(!cef_prewarm_enabled(Some(" No "), true));
}
#[test]
fn prewarm_unknown_env_value_treated_as_enable() {
// Any string that is not a recognised disable token → treat as enable.
assert!(cef_prewarm_enabled(Some("enabled"), false));
assert!(cef_prewarm_enabled(Some("yes"), false));
assert!(cef_prewarm_enabled(Some(""), false));
}
// -------------------------------------------------------------------------
// build_sentry_release_tag
// -------------------------------------------------------------------------
#[test]
fn sentry_release_tag_starts_with_openhuman() {
let tag = build_sentry_release_tag();
assert!(
tag.starts_with("openhuman@"),
"release tag must start with 'openhuman@', got: {tag:?}"
);
}
#[test]
fn sentry_release_tag_contains_cargo_pkg_version() {
let tag = build_sentry_release_tag();
let version = env!("CARGO_PKG_VERSION");
assert!(
tag.contains(version),
"release tag {tag:?} must embed CARGO_PKG_VERSION {version:?}"
);
}
#[test]
fn sentry_release_tag_version_part_is_nonempty() {
let tag = build_sentry_release_tag();
let after_prefix = tag.strip_prefix("openhuman@").unwrap_or("");
assert!(!after_prefix.is_empty(), "version part must not be empty");
}
/// When a SHA is baked in the tag takes the form `openhuman@<ver>+<sha12>`.
/// When it is not, the tag is simply `openhuman@<ver>` with no `+`.
/// Either way the full tag must be non-empty.
#[test]
fn sentry_release_tag_is_nonempty() {
assert!(!build_sentry_release_tag().is_empty());
}
// -------------------------------------------------------------------------
// resolve_sentry_environment
// -------------------------------------------------------------------------
#[test]
fn sentry_environment_reads_openhuman_app_env() {
let _g = ENV_LOCK.lock().unwrap();
let key = "OPENHUMAN_APP_ENV";
let original = std::env::var(key).ok();
std::env::set_var(key, "staging");
let env = resolve_sentry_environment();
match original {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
assert_eq!(env, "staging");
}
#[test]
fn sentry_environment_trims_whitespace_from_openhuman_app_env() {
let _g = ENV_LOCK.lock().unwrap();
let key = "OPENHUMAN_APP_ENV";
let original = std::env::var(key).ok();
std::env::set_var(key, " dev ");
let env = resolve_sentry_environment();
match original {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
assert_eq!(env, "dev");
}
#[test]
fn sentry_environment_skips_empty_openhuman_app_env() {
let _g = ENV_LOCK.lock().unwrap();
let key = "OPENHUMAN_APP_ENV";
let original = std::env::var(key).ok();
std::env::set_var(key, "");
let env = resolve_sentry_environment();
match original {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
// Falls through to VITE_ compile-time value or "production"; must be non-empty.
assert!(!env.is_empty());
}
#[test]
fn sentry_environment_skips_whitespace_only_openhuman_app_env() {
let _g = ENV_LOCK.lock().unwrap();
let key = "OPENHUMAN_APP_ENV";
let original = std::env::var(key).ok();
std::env::set_var(key, " ");
let env = resolve_sentry_environment();
match original {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
assert!(!env.is_empty());
}
/// When neither runtime env var nor compile-time VITE_ is set, the fallback
/// must be "production". Guard with a compile-time check so this test only
/// asserts the hard default when no compile-time override is present.
#[test]
fn sentry_environment_defaults_to_production_when_unset() {
let _g = ENV_LOCK.lock().unwrap();
if option_env!("VITE_OPENHUMAN_APP_ENV").is_some() {
// A compile-time override is baked in; skip — the fallback path is
// exercised by sentry_environment_skips_empty_openhuman_app_env.
return;
}
let key = "OPENHUMAN_APP_ENV";
let original = std::env::var(key).ok();
std::env::remove_var(key);
let env = resolve_sentry_environment();
match original {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
assert_eq!(env, "production");
}
// ── Sentry before_send filter: drop "Failed to request http://localhost:…"
// noise emitted by the vendored tauri-runtime-cef dev proxy in packaged
// builds (issue OPENHUMAN-TAURI-V). Tests target the pure
// `message_is_localhost_dev_fetch_noise` helper so the rule can be
// asserted without standing up a Sentry client.
#[test]
fn localhost_dev_fetch_noise_drops_vite_dev_url_1420() {
// The exact message shape reported by the latest event tag in Sentry
// (URL repeated by reqwest's `error sending request for url (…)`).
let msg = "Failed to request http://localhost:1420/components/skills/SkillCard.tsx: \
error sending request for url (http://localhost:1420/components/skills/SkillCard.tsx)";
assert!(
message_is_localhost_dev_fetch_noise(msg),
"expected Vite dev-server fetch failure to be filtered"
);
}
#[test]
fn localhost_dev_fetch_noise_drops_127_0_0_1_dev_url() {
// Some environments resolve `localhost` to 127.0.0.1 at the reqwest
// layer; the formatted message can carry either spelling.
let msg = "Failed to request http://127.0.0.1:1420/index.html: \
error sending request for url (http://127.0.0.1:1420/index.html)";
assert!(
message_is_localhost_dev_fetch_noise(msg),
"expected 127.0.0.1 dev-server fetch failure to be filtered"
);
}
#[test]
fn localhost_dev_fetch_noise_passes_production_url_through() {
// Real upstream failures (e.g. backend API errors surfaced via the
// same `Failed to request …` wording elsewhere) must NOT be filtered —
// they're the high-signal events Sentry exists for.
let msg = "Failed to request https://api.openhuman.ai/v1/skills: \
error sending request for url (https://api.openhuman.ai/v1/skills)";
assert!(
!message_is_localhost_dev_fetch_noise(msg),
"production API errors must NOT be filtered out"
);
}
#[test]
fn localhost_dev_fetch_noise_passes_unrelated_localhost_messages() {
// The filter is anchored on the dev-proxy's exact prefix to avoid
// accidentally dropping any error that happens to mention localhost
// (e.g. core-sidecar transport errors logged from coreRpcClient).
let msg =
"[core_rpc] transport error: error sending request for url (http://localhost:7788/rpc)";
assert!(
!message_is_localhost_dev_fetch_noise(msg),
"non-tauri-cef localhost errors must NOT be filtered"
);
}
#[test]
fn event_filter_uses_message_field() {
// event-level coverage: when sentry-tracing populates
// `event.message` (default with `attach_stacktrace=false`), the
// filter should see the noise payload through the primary read
// path. Per graycyrus on PR #1545.
let mut event = sentry::protocol::Event::new();
event.message = Some("Failed to request http://localhost:1420/foo: timeout".into());
assert!(
event_is_localhost_dev_fetch_noise(&event),
"event.message read path must catch noise messages"
);
}
#[test]
fn event_filter_falls_back_to_last_exception_value() {
// event-level coverage: if `attach_stacktrace` is ever turned on,
// sentry-tracing populates `event.exception` instead of (or in
// addition to) `event.message`. Filter must still see the noise
// payload through the exception fallback. Per graycyrus on PR #1545.
let mut event = sentry::protocol::Event::new();
event.message = None;
event.exception.values.push(sentry::protocol::Exception {
ty: "log".into(),
value: Some("Failed to request http://localhost:1420/foo: timeout".into()),
..Default::default()
});
assert!(
event_is_localhost_dev_fetch_noise(&event),
"exception fallback must catch noise messages when event.message is absent"
);
}
#[test]
fn event_filter_passes_through_when_neither_field_matches() {
// Negative event-level case: no noise prefix in either field →
// event must NOT be filtered.
let mut event = sentry::protocol::Event::new();
event.message = Some("genuine production error".into());
event.exception.values.push(sentry::protocol::Exception {
ty: "log".into(),
value: Some("connection refused (10061)".into()),
..Default::default()
});
assert!(
!event_is_localhost_dev_fetch_noise(&event),
"legitimate production events must pass through"
);
}
#[test]
fn localhost_dev_fetch_noise_anchors_to_message_start() {
// CodeRabbit (PR #1545) caught that the predicate used
// `contains` rather than `starts_with`. Regression: a message
// that merely embeds the dev-proxy prefix later in its text
// must NOT be filtered — only messages that *begin* with it.
let msg = "User report: `Failed to request http://localhost:1420/foo` was logged earlier";
assert!(
!message_is_localhost_dev_fetch_noise(msg),
"messages that merely contain the dev-proxy prefix must NOT be filtered"
);
}
// -------------------------------------------------------------------------
// path_has_executable / deep-link xdg-mime pre-flight (OPENHUMAN-TAURI-AS)
// -------------------------------------------------------------------------
/// With a controlled `$PATH` containing one dir that holds a file named
/// `xdg-mime`, the lookup must succeed (mirrors a Linux desktop install
/// where xdg-utils ships the binary).
#[cfg(target_os = "linux")]
#[test]
fn path_has_executable_finds_file_on_path() {
let _g = ENV_LOCK.lock().unwrap();
let original = std::env::var_os("PATH");
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("xdg-mime"), b"#!/bin/sh\n").expect("write stub");
std::env::set_var("PATH", dir.path());
assert!(
path_has_executable("xdg-mime"),
"must discover xdg-mime when present in a $PATH entry"
);
match original {
Some(v) => std::env::set_var("PATH", v),
None => std::env::remove_var("PATH"),
}
}
/// With a controlled `$PATH` that does NOT contain `xdg-mime`, the lookup
/// must fail (mirrors WSL2 / minimal containers without xdg-utils — the
/// case OPENHUMAN-TAURI-AS protects against).
#[cfg(target_os = "linux")]
#[test]
fn path_has_executable_returns_false_when_missing() {
let _g = ENV_LOCK.lock().unwrap();
let original = std::env::var_os("PATH");
let dir = tempfile::tempdir().expect("tempdir");
// Intentionally do not create xdg-mime in `dir`.
std::env::set_var("PATH", dir.path());
assert!(
!path_has_executable("xdg-mime"),
"must return false when xdg-mime is not in any $PATH entry"
);
match original {
Some(v) => std::env::set_var("PATH", v),
None => std::env::remove_var("PATH"),
}
}
/// When `$PATH` is unset entirely, the lookup must short-circuit to false
/// rather than panic or fall back to the cwd.
#[cfg(target_os = "linux")]
#[test]
fn path_has_executable_returns_false_when_path_unset() {
let _g = ENV_LOCK.lock().unwrap();
let original = std::env::var_os("PATH");
std::env::remove_var("PATH");
assert!(
!path_has_executable("xdg-mime"),
"unset $PATH must yield false (skip register_all on the missing-xdg-utils branch)"
);
match original {
Some(v) => std::env::set_var("PATH", v),
None => std::env::remove_var("PATH"),
}
}
/// Regression guard for OPENHUMAN-TAURI-5V: a Linux host with `xdg-mime`
/// installed but `update-desktop-database` missing must classify as
/// "skip register_all" — the pre-#5V code only checked `xdg-mime` and
/// would have entered the plugin call, which then fires the noisy
/// `Failed to run OS command \`update-desktop-database\`` internal log
/// that escapes to Sentry. The Wave-4 fix pre-flights every xdg-utils
/// binary the plugin shells out to; this test pins that contract by
/// checking each binary lookup independently with a `$PATH` that
/// contains only `xdg-mime`.
#[cfg(target_os = "linux")]
#[test]
fn path_has_executable_returns_false_for_partial_xdg_utils_install() {
let _g = ENV_LOCK.lock().unwrap();
let original = std::env::var_os("PATH");
let dir = tempfile::tempdir().expect("tempdir");
// Only `xdg-mime` exists; `update-desktop-database` and
// `xdg-icon-resource` are deliberately absent.
std::fs::write(dir.path().join("xdg-mime"), b"#!/bin/sh\n").expect("write stub");
std::env::set_var("PATH", dir.path());
assert!(
path_has_executable("xdg-mime"),
"xdg-mime stub must be discoverable in the partial-install $PATH"
);
assert!(
!path_has_executable("update-desktop-database"),
"partial xdg-utils install must NOT report update-desktop-database present (OPENHUMAN-TAURI-5V)"
);
assert!(
!path_has_executable("xdg-icon-resource"),
"partial xdg-utils install must NOT report xdg-icon-resource present"
);
match original {
Some(v) => std::env::set_var("PATH", v),
None => std::env::remove_var("PATH"),
}
}
/// Regression guard for issue #2228: `tauri-plugin-single-instance` must
/// enable the `deep-link` feature so that second-launch deep-link payloads
/// (e.g. `openhuman://oauth/...` callbacks from Windows/Linux system
/// browsers) are forwarded into the primary instance. Without it, hot OAuth
/// callbacks silently no-op while only focusing the existing window.
#[test]
fn single_instance_dep_enables_deep_link_feature() {
let manifest_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");
let manifest = std::fs::read_to_string(&manifest_path).expect("read app/src-tauri/Cargo.toml");
let parsed: toml::Value = manifest.parse().expect("parse Cargo.toml");
let dep = parsed
.get("dependencies")
.and_then(|d| d.get("tauri-plugin-single-instance"))
.expect("tauri-plugin-single-instance dependency must exist");
let features = dep.get("features").and_then(|f| f.as_array()).expect(
"tauri-plugin-single-instance must be a table with a `features` array \
— issue #2228 requires the `deep-link` feature to forward hot-instance \
OAuth callbacks on Windows/Linux",
);
assert!(
features.iter().any(|v| v.as_str() == Some("deep-link")),
"tauri-plugin-single-instance must enable the `deep-link` feature \
(issue #2228 — hot-instance OAuth callback forwarding)"
);
}