use super::*;
#[test]
fn test_build_full_script() {
let config = StealthConfig::default();
let script = build_evasion_script(&config);
assert!(script.contains("webdriver"));
assert!(script.contains("WebGLRenderingContext"));
assert!(script.contains("HTMLCanvasElement"));
assert!(script.contains("AudioBuffer"));
assert!(!script.contains("const documentProxy"));
}
#[test]
fn test_aggressive_cdp_evasion_is_opt_in() {
let config = StealthConfig {
aggressive_cdp_evasion: true,
..StealthConfig::default()
};
assert!(build_evasion_script(&config).contains("const documentProxy"));
}
#[test]
fn test_placeholders_are_filled() {
let script = build_evasion_script(&StealthConfig::default());
assert!(
!script.contains("__EOKA_"),
"unresolved placeholder in script"
);
assert!(script.contains("_eoka_mark"));
}
#[test]
fn test_script_is_wrapped_in_iife() {
let config = StealthConfig::default();
let script = build_evasion_script(&config);
assert!(script.starts_with("(function()"));
assert!(script.ends_with("})();"));
}
#[test]
fn test_build_script_for_known_windows_fp() {
let fp = Fingerprint::resolve(
Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7312.86 Safari/537.36"),
None,
);
let config = StealthConfig::default();
let script = build_evasion_script_for(&config, &fp);
assert!(
script.contains("'Win32'"),
"script must contain the fingerprint nav_platform 'Win32'"
);
assert!(
!script.contains("'MacIntel'"),
"script must not contain the old hardcoded 'MacIntel'"
);
assert!(
script.contains(&fp.webgl_renderer),
"script must contain the fingerprint webgl_renderer: {}",
fp.webgl_renderer
);
assert!(
script.contains(&fp.hardware_concurrency.to_string()),
"script must contain hardware_concurrency"
);
assert!(
script.contains(&fp.device_memory.to_string()),
"script must contain device_memory"
);
assert!(script.contains("_eoka_mark"));
assert!(script.contains("[native code]"));
assert!(
!script.contains("__EOKA_"),
"script must not contain unresolved placeholders"
);
assert!(
!script.contains("__eoka_pending_requests"),
"removed request-beacon must not reappear"
);
}
#[test]
fn test_config_values_are_js_escaped() {
let config = StealthConfig {
timezone: Some("Foo'Bar".into()),
..StealthConfig::default()
};
let script = build_evasion_script(&config);
assert!(
script.contains(r"Foo\'Bar"),
"timezone single quote must be escaped via escape_js_string"
);
assert!(
!script.contains("'Foo'Bar'"),
"unescaped single quote must not appear"
);
}
#[test]
fn test_chrome_object_matches_stock() {
let script = build_evasion_script(&StealthConfig::default());
assert!(
script.contains("loadTimes: function"),
"chrome.loadTimes must be spoofed (present in stock Chrome)"
);
assert!(
script.contains("csi: function"),
"chrome.csi must be spoofed (present in stock Chrome)"
);
assert!(
!script.contains("onConnect"),
"chrome.runtime stub must not be re-added: plain pages have no runtime"
);
assert!(
!script.contains("sendMessage: function"),
"chrome.runtime stub must not be re-added"
);
}
#[test]
fn test_match_media_is_not_wrapped() {
let script = build_evasion_script(&StealthConfig::default());
assert!(
!script.contains("originalMatchMedia"),
"matchMedia must not be wrapped: stock headless/new reports the correct pair"
);
}
#[test]
fn test_speech_voices_match_platform() {
let voices_for = |ua: &str| {
let fp = Fingerprint::resolve(Some(ua), None);
let script = build_evasion_script_for(&StealthConfig::default(), &fp);
let start = script
.find("const defaultVoices = ")
.expect("voices array present")
+ "const defaultVoices = ".len();
let end = script[start..]
.find(".map(v =>")
.expect("voices array terminated");
script[start..start + end].trim().to_string()
};
let linux = voices_for("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36");
assert_eq!(
linux, "[]",
"Linux identities must pass through with no voices"
);
let mac = voices_for("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36");
let mac_json: serde_json::Value =
serde_json::from_str(&mac).expect("macOS voices must be valid JSON");
let mac_names: Vec<&str> = mac_json
.as_array()
.unwrap()
.iter()
.map(|v| v["name"].as_str().unwrap())
.collect();
assert!(mac_names.contains(&"Alex"), "macOS list must contain Alex");
assert!(!mac_names.iter().any(|n| n.starts_with("Microsoft")));
let win = voices_for("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36");
let win_json: serde_json::Value =
serde_json::from_str(&win).expect("Windows voices must be valid JSON");
let win_names: Vec<&str> = win_json
.as_array()
.unwrap()
.iter()
.map(|v| v["name"].as_str().unwrap())
.collect();
assert!(
win_names.iter().any(|n| n.starts_with("Microsoft David")),
"Windows list must contain Microsoft David"
);
assert!(!win_names.contains(&"Alex"), "Alex is macOS-only");
}
#[test]
fn test_media_device_ids_are_seeded_per_fingerprint() {
let devices_json = |script: &str| -> serde_json::Value {
let start = script
.find("const fakeDevices = ")
.expect("fake device list present")
+ "const fakeDevices = ".len();
let end = script[start..].find(';').expect("device list terminated");
serde_json::from_str(&script[start..start + end])
.expect("media device list must be valid JSON")
};
let script = build_evasion_script(&StealthConfig::default());
assert!(
!script.contains("5f8b2c1e9a3d"),
"hardcoded cross-session groupId must not reappear"
);
let devices = devices_json(&script).as_array().cloned().unwrap();
assert!(!devices.is_empty());
let mut ids: Vec<&str> = devices
.iter()
.map(|d| d["deviceId"].as_str().unwrap())
.collect();
let defaults = ids.iter().filter(|id| **id == "default").count();
ids.retain(|id| *id != "default");
assert_eq!(
ids.len(),
ids.iter().collect::<std::collections::HashSet<_>>().len(),
"non-default device IDs must be unique within an identity"
);
assert!(
ids.iter().all(|id| id.len() == 12),
"device IDs must be 12 hex chars like real enumerated IDs"
);
assert!(defaults >= 2, "audio in/out pair should include 'default'");
let fps_a = Fingerprint::random();
let fps_b = Fingerprint::random();
let ids_a =
devices_json(&build_evasion_script_for(&StealthConfig::default(), &fps_a)).to_string();
let ids_b =
devices_json(&build_evasion_script_for(&StealthConfig::default(), &fps_b)).to_string();
assert_ne!(
ids_a, ids_b,
"distinct identities must not share device IDs (linkability)"
);
}
#[test]
fn test_get_image_data_is_noised_with_canvas_spoof() {
let on = build_evasion_script(&StealthConfig::default());
assert!(
on.contains("CanvasRenderingContext2D.prototype.getImageData"),
"getImageData must be hooked when canvas spoofing is on"
);
let off = build_evasion_script(&StealthConfig {
webgl_spoof: false,
canvas_spoof: false,
audio_spoof: false,
..StealthConfig::default()
});
assert!(
!off.contains("getImageData"),
"canvas hooks must be absent when all fingerprint spoofs are off"
);
}
#[test]
fn test_intl_shim_clones_options() {
let script = build_evasion_script(&StealthConfig::default());
assert!(
script.contains("Object.assign({}, options)"),
"Intl.DateTimeFormat shim must clone options before injecting timeZone"
);
}