mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 02:52:07 +08:00
test: trim redundant test coverage
This commit is contained in:
+17
-125
@@ -166,24 +166,26 @@ impl<T, E: std::fmt::Display> WithErrorCode<T> for Result<T, E> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CommandFailure, WithErrorCode as _, coded_error, proxy_aware_coded_error, proxy_aware_error};
|
||||
use crate::core::proxy_control::SysproxyFailure;
|
||||
use anyhow::Context as _;
|
||||
|
||||
use super::{CommandFailure, WithErrorCode as _, proxy_aware_coded_error};
|
||||
use crate::core::proxy_control::SysproxyFailure;
|
||||
|
||||
#[test]
|
||||
fn coded_error_preserves_stable_code_and_diagnostic_detail() {
|
||||
fn command_failure_matches_the_frontend_wire_contract() {
|
||||
assert_eq!(
|
||||
coded_error("CORE_RESTART_FAILED", "connection refused"),
|
||||
CommandFailure {
|
||||
code: Some("CORE_RESTART_FAILED".to_owned()),
|
||||
detail: "connection refused".to_owned(),
|
||||
operation: None,
|
||||
}
|
||||
serde_json::to_value(CommandFailure::coded("CORE_START_FAILED", "connection refused"))
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
serde_json::json!({ "code": "CORE_START_FAILED", "detail": "connection refused" })
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(CommandFailure::plain("disk full")).unwrap_or(serde_json::Value::Null),
|
||||
serde_json::json!({ "detail": "disk full" })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_error_code_preserves_whole_anyhow_context_chain() {
|
||||
fn coded_errors_preserve_the_anyhow_context_chain() {
|
||||
let source: anyhow::Result<()> = Err(anyhow::anyhow!("connection refused"))
|
||||
.context("failed to reach the mihomo core")
|
||||
.context("failed to restart the core");
|
||||
@@ -193,37 +195,15 @@ mod tests {
|
||||
.err()
|
||||
.unwrap_or_else(|| CommandFailure::plain("the call should have failed"));
|
||||
|
||||
assert_eq!(failure.code.as_deref(), Some("CORE_RESTART_FAILED"));
|
||||
assert_eq!(
|
||||
failure,
|
||||
CommandFailure {
|
||||
code: Some("CORE_RESTART_FAILED".to_owned()),
|
||||
detail: "failed to restart the core: failed to reach the mihomo core: connection refused".to_owned(),
|
||||
operation: None,
|
||||
}
|
||||
failure.detail,
|
||||
"failed to restart the core: failed to reach the mihomo core: connection refused"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_error_code_leaves_plain_display_errors_untouched() {
|
||||
let source: Result<(), &str> = Err("plain failure");
|
||||
|
||||
let failure = source
|
||||
.with_error_code("PROFILE_READ_FAILED")
|
||||
.err()
|
||||
.unwrap_or_else(|| CommandFailure::plain("the call should have failed"));
|
||||
|
||||
assert_eq!(
|
||||
failure,
|
||||
CommandFailure {
|
||||
code: Some("PROFILE_READ_FAILED".to_owned()),
|
||||
detail: "plain failure".to_owned(),
|
||||
operation: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_classification_outranks_the_code_naming_the_operation() {
|
||||
fn system_proxy_classification_outranks_the_operation_code() {
|
||||
let failure = anyhow::Error::new(sysproxy::Error::RequiresAdminPrivileges)
|
||||
.context(SysproxyFailure::PrivilegeRequired)
|
||||
.context("failed to restart the core");
|
||||
@@ -231,94 +211,6 @@ mod tests {
|
||||
let reported = proxy_aware_coded_error(&failure, "CORE_RESTART_FAILED");
|
||||
|
||||
assert_eq!(reported.code.as_deref(), Some("SYSPROXY_PRIVILEGE_REQUIRED"));
|
||||
assert!(reported.detail.contains("failed to restart the core"), "{reported:?}");
|
||||
assert!(reported.detail.contains("admin privileges"), "{reported:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_a_classification_the_callers_code_is_used() {
|
||||
let failure = anyhow::anyhow!("connection refused").context("failed to start the core");
|
||||
|
||||
let reported = proxy_aware_coded_error(&failure, "CORE_START_FAILED");
|
||||
|
||||
assert_eq!(
|
||||
reported,
|
||||
CommandFailure {
|
||||
code: Some("CORE_START_FAILED".to_owned()),
|
||||
detail: "failed to start the core: connection refused".to_owned(),
|
||||
operation: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands_that_report_plain_text_stay_uncoded_without_a_classification() {
|
||||
let failure = anyhow::anyhow!("disk full").context("failed to save the configuration");
|
||||
|
||||
let reported = proxy_aware_error(&failure);
|
||||
|
||||
assert_eq!(
|
||||
reported,
|
||||
CommandFailure {
|
||||
code: None,
|
||||
detail: "failed to save the configuration: disk full".to_owned(),
|
||||
operation: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands_that_report_plain_text_still_carry_a_classification() {
|
||||
let failure = anyhow::Error::new(SysproxyFailure::DirectFallback {
|
||||
detail: "service could not set the proxy".to_owned(),
|
||||
})
|
||||
.context("failed to apply the verge patch");
|
||||
|
||||
let reported = proxy_aware_error(&failure);
|
||||
|
||||
assert_eq!(reported.code.as_deref(), Some("SYSPROXY_DIRECT_FALLBACK"));
|
||||
assert!(
|
||||
reported.detail.contains("failed to apply the verge patch"),
|
||||
"{reported:?}"
|
||||
);
|
||||
assert!(
|
||||
reported.detail.contains("service could not set the proxy"),
|
||||
"{reported:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_command_that_knows_the_request_can_say_which_way_it_went() {
|
||||
let asking_on = CommandFailure::coded("SYSPROXY_PRIVILEGE_REQUIRED", "refused")
|
||||
.asking_for(Some(crate::core::notification::FailedOperation::SystemProxyEnable));
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(asking_on).unwrap_or(serde_json::Value::Null),
|
||||
serde_json::json!({
|
||||
"code": "SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"detail": "refused",
|
||||
"operation": "systemProxyEnable",
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(CommandFailure::coded("CORE_START_FAILED", "no").asking_for(None))
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
serde_json::json!({ "code": "CORE_START_FAILED", "detail": "no" })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_serialised_shape_is_what_the_frontend_reads() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(CommandFailure::coded("CORE_START_FAILED", "connection refused"))
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
serde_json::json!({ "code": "CORE_START_FAILED", "detail": "connection refused" })
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(CommandFailure::plain("disk full")).unwrap_or(serde_json::Value::Null),
|
||||
serde_json::json!({ "detail": "disk full" })
|
||||
);
|
||||
assert!(reported.detail.contains("failed to restart the core"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,31 +32,3 @@ const fn system_proxy_operation(payload: &IVerge) -> Option<FailedOperation> {
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::system_proxy_operation;
|
||||
use crate::{config::IVerge, core::notification::FailedOperation};
|
||||
|
||||
#[test]
|
||||
fn only_a_system_proxy_toggle_says_which_way_it_was_going() {
|
||||
let asking_on = IVerge {
|
||||
enable_system_proxy: Some(true),
|
||||
..IVerge::default()
|
||||
};
|
||||
let asking_off = IVerge {
|
||||
enable_system_proxy: Some(false),
|
||||
..IVerge::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
system_proxy_operation(&asking_on),
|
||||
Some(FailedOperation::SystemProxyEnable)
|
||||
);
|
||||
assert_eq!(
|
||||
system_proxy_operation(&asking_off),
|
||||
Some(FailedOperation::SystemProxyDisable)
|
||||
);
|
||||
assert_eq!(system_proxy_operation(&IVerge::default()), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,37 +385,10 @@ pub enum ConfigType {
|
||||
Run,
|
||||
Check,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::mem;
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::expect_used)]
|
||||
fn test_prfitem_from_merge_size() {
|
||||
let merge_item = PrfItem::from_merge(Some("Merge".into())).expect("Failed to create merge item in test");
|
||||
let prfitem_size = mem::size_of_val(&merge_item);
|
||||
let boxed_merge_item = Box::new(merge_item);
|
||||
let box_prfitem_size = mem::size_of_val(&boxed_merge_item);
|
||||
assert!(box_prfitem_size < prfitem_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn test_draft_size_non_boxed() {
|
||||
let draft = Draft::new(IRuntime::new());
|
||||
let iruntime_size = std::mem::size_of_val(&draft);
|
||||
assert_eq!(iruntime_size, std::mem::size_of::<Draft<IRuntime>>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn test_draft_size_boxed() {
|
||||
let draft = Draft::new(Box::new(IRuntime::new()));
|
||||
let box_iruntime_size = std::mem::size_of_val(&draft);
|
||||
assert_eq!(box_iruntime_size, std::mem::size_of::<Draft<Box<IRuntime>>>());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn failed_profile_index_survives_startup_without_cleanup() -> Result<()> {
|
||||
@@ -431,14 +404,8 @@ mod tests {
|
||||
let profile_was_preserved = tokio::fs::try_exists(&active_profile).await?;
|
||||
tokio::fs::remove_dir_all(&profiles_dir).await?;
|
||||
|
||||
assert!(
|
||||
profile_was_preserved,
|
||||
"startup must not delete profiles when profiles.yaml could not be loaded"
|
||||
);
|
||||
assert!(
|
||||
profiles.data_arc().get_items().is_none(),
|
||||
"startup must not replace an unreadable profile index with defaults"
|
||||
);
|
||||
assert!(profile_was_preserved);
|
||||
assert!(profiles.data_arc().get_items().is_none());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,40 +73,23 @@ where
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::expect_used, clippy::panic, reason = "tests assert by panicking")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_selected_port_wins_over_the_merge_config() {
|
||||
assert_eq!(resolve_desired(None, Some(9000), 7897), 9000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_merge_config_answers_when_nothing_is_selected() {
|
||||
fn desired_port_uses_session_then_selection_then_merge_priority() {
|
||||
assert_eq!(resolve_desired(Some(7900), Some(9000), 8080), 7900);
|
||||
assert_eq!(resolve_desired(None, Some(9000), 8080), 9000);
|
||||
assert_eq!(resolve_desired(None, None, 8080), 8080);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_session_fallback_outranks_the_port_still_written_down() {
|
||||
assert_eq!(resolve_desired(Some(7900), Some(7897), 7897), 7900);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_reporting_core_overrides_what_we_configured() {
|
||||
async fn effective_port_prefers_a_bound_core_port() {
|
||||
assert_eq!(resolve_effective(|| async { Ok(7898) }, 7897).await, 7898);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_unreachable_core_leaves_the_configured_port_standing() {
|
||||
assert_eq!(resolve_effective(|| async { Ok(0) }, 7897).await, 7897);
|
||||
assert_eq!(
|
||||
resolve_effective(|| async { anyhow::bail!("core is not running") }, 7897).await,
|
||||
7897
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_core_that_has_not_bound_yet_is_not_believed() {
|
||||
assert_eq!(resolve_effective(|| async { Ok(0) }, 7897).await, 7897);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,19 +565,3 @@ fn fix_dirty_url(input: &str) -> Result<Url> {
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{PrfOption, allow_auto_update_enabled};
|
||||
|
||||
#[test]
|
||||
fn auto_update_defaults_to_enabled_and_preserves_explicit_false() {
|
||||
assert!(allow_auto_update_enabled(None));
|
||||
|
||||
let disabled = PrfOption {
|
||||
allow_auto_update: Some(false),
|
||||
..PrfOption::default()
|
||||
};
|
||||
assert!(!allow_auto_update_enabled(Some(&disabled)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,16 +234,3 @@ impl Logger {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::should_sync_service_writer;
|
||||
use crate::core::manager::RunningMode;
|
||||
|
||||
#[test]
|
||||
fn service_writer_sync_requires_service_running_mode() {
|
||||
assert!(should_sync_service_writer(RunningMode::Service));
|
||||
assert!(!should_sync_service_writer(RunningMode::Sidecar));
|
||||
assert!(!should_sync_service_writer(RunningMode::NotRunning));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,151 +298,3 @@ impl NotificationSystem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FailedOperation, FailureTable};
|
||||
|
||||
fn table() -> FailureTable {
|
||||
FailureTable::default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reading_does_not_clear() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyRestore,
|
||||
"SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"refused".into(),
|
||||
);
|
||||
|
||||
assert_eq!(table.snapshot(), table.snapshot());
|
||||
assert_eq!(table.snapshot().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_code_keeps_only_its_most_recent_failure() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyRestore,
|
||||
"SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"first".into(),
|
||||
);
|
||||
table.record(
|
||||
FailedOperation::SystemProxyRestore,
|
||||
"SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"second".into(),
|
||||
);
|
||||
|
||||
let failures = table.snapshot();
|
||||
assert_eq!(failures.len(), 1);
|
||||
assert_eq!(failures[0].detail, "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_failure_under_a_known_code_gets_a_new_sequence() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyRestore,
|
||||
"SYSPROXY_DIRECT_FALLBACK",
|
||||
"a".into(),
|
||||
);
|
||||
let first = table.snapshot()[0].sequence;
|
||||
table.record(
|
||||
FailedOperation::SystemProxyRestore,
|
||||
"SYSPROXY_DIRECT_FALLBACK",
|
||||
"b".into(),
|
||||
);
|
||||
|
||||
assert!(table.snapshot()[0].sequence > first);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failures_are_reported_oldest_first() {
|
||||
let table = table();
|
||||
table.record(FailedOperation::SystemProxyRestore, "FIRST", "a".into());
|
||||
table.record(FailedOperation::SystemProxyRestore, "SECOND", "b".into());
|
||||
|
||||
let failures = table.snapshot();
|
||||
let codes: Vec<&str> = failures.iter().map(|failure| failure.code.as_str()).collect();
|
||||
assert_eq!(codes, ["FIRST", "SECOND"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_working_proxy_retires_what_it_disproves() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyRestore,
|
||||
"SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"refused".into(),
|
||||
);
|
||||
|
||||
assert!(table.retire_system_proxy(false));
|
||||
assert!(table.snapshot().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_users_request_is_retired_only_by_reaching_the_state_they_asked_for() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyDisable,
|
||||
"SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"refused".into(),
|
||||
);
|
||||
|
||||
assert!(!table.retire_system_proxy(true));
|
||||
assert_eq!(table.snapshot().len(), 1);
|
||||
|
||||
assert!(table.retire_system_proxy(false));
|
||||
assert!(table.snapshot().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_enable_is_retired_by_the_proxy_coming_on() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyEnable,
|
||||
"SYSPROXY_DIRECT_FALLBACK",
|
||||
"fell back".into(),
|
||||
);
|
||||
|
||||
assert!(!table.retire_system_proxy(false));
|
||||
assert!(table.retire_system_proxy(true));
|
||||
assert!(table.snapshot().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stopped_guard_is_retired_by_a_guard_starting_again_and_not_by_an_apply() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyGuard,
|
||||
"SYSPROXY_GUARD_STOPPED",
|
||||
"gave up".into(),
|
||||
);
|
||||
|
||||
assert!(!table.retire_system_proxy(true));
|
||||
assert!(!table.retire_system_proxy(false));
|
||||
assert_eq!(table.snapshot().len(), 1);
|
||||
|
||||
assert!(table.retire_guard());
|
||||
assert!(table.snapshot().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retiring_a_guard_leaves_everything_else_alone() {
|
||||
let table = table();
|
||||
table.record(
|
||||
FailedOperation::SystemProxyEnable,
|
||||
"SYSPROXY_PRIVILEGE_REQUIRED",
|
||||
"refused".into(),
|
||||
);
|
||||
|
||||
assert!(!table.retire_guard());
|
||||
assert_eq!(table.snapshot().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retiring_nothing_reports_nothing() {
|
||||
assert!(!table().retire_system_proxy(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,73 +492,3 @@ impl SilentUpdater {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_version_equal() {
|
||||
assert!(version_lte("2.4.7", "2.4.7"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_less() {
|
||||
assert!(version_lte("2.4.7", "2.4.8"));
|
||||
assert!(version_lte("2.4.7", "2.5.0"));
|
||||
assert!(version_lte("2.4.7", "3.0.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_greater() {
|
||||
assert!(!version_lte("2.4.8", "2.4.7"));
|
||||
assert!(!version_lte("2.5.0", "2.4.7"));
|
||||
assert!(!version_lte("3.0.0", "2.4.7"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_with_v_prefix() {
|
||||
assert!(version_lte("v2.4.7", "2.4.8"));
|
||||
assert!(version_lte("2.4.7", "v2.4.8"));
|
||||
assert!(version_lte("v2.4.7", "v2.4.8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_with_prerelease() {
|
||||
assert!(version_lte("2.4.7", "2.4.8-alpha"));
|
||||
assert!(version_lte("2.4.8-alpha", "2.4.8"));
|
||||
assert!(version_lte("2.4.8-alpha", "2.4.8-beta"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_different_lengths() {
|
||||
assert!(version_lte("2.4", "2.4.1"));
|
||||
assert!(!version_lte("2.4.1", "2.4"));
|
||||
assert!(version_lte("2.4.0", "2.4"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_meta_serialize_roundtrip() {
|
||||
let meta = UpdateCacheMeta {
|
||||
version: "2.5.0".to_string(),
|
||||
downloaded_at: "2026-03-31T00:00:00Z".to_string(),
|
||||
};
|
||||
let json = serde_json::to_string(&meta).unwrap();
|
||||
let parsed: UpdateCacheMeta = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.version, "2.5.0");
|
||||
assert_eq!(parsed.downloaded_at, "2026-03-31T00:00:00Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_meta_invalid_json() {
|
||||
let result = serde_json::from_str::<UpdateCacheMeta>("not valid json");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_meta_missing_required_field() {
|
||||
let result = serde_json::from_str::<UpdateCacheMeta>(r#"{"version":"2.5.0"}"#);
|
||||
assert!(result.is_err()); // missing downloaded_at
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,103 +173,3 @@ pub async fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<Stri
|
||||
Err("file not found".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn normalize_icon_segment_accepts_single_name() {
|
||||
assert!(normalize_icon_segment("group-icon.png").is_ok());
|
||||
assert!(normalize_icon_segment("alpha_1.webp").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_icon_segment_rejects_traversal_and_separators() {
|
||||
for name in ["../x", "..\\x", "a/b", "a\\b", "..", "a..b"] {
|
||||
assert!(normalize_icon_segment(name).is_err(), "name should be rejected: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_icon_segment_rejects_empty() {
|
||||
assert!(normalize_icon_segment("").is_err());
|
||||
assert!(normalize_icon_segment(" ").is_err());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn normalize_icon_segment_rejects_windows_absolute_names() {
|
||||
for name in [r"C:\temp\icon.png", r"\\server\share\icon.png"] {
|
||||
assert!(normalize_icon_segment(name).is_err(), "name should be rejected: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[test]
|
||||
fn normalize_icon_segment_rejects_unix_absolute_names() {
|
||||
assert!(normalize_icon_segment("/tmp/icon.png").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_icon_cache_target_accepts_direct_child_only() {
|
||||
let base = PathBuf::from("icons").join("cache");
|
||||
let valid = ensure_icon_cache_target(&base, "ok.png");
|
||||
assert_eq!(valid.unwrap(), base.join("ok.png"));
|
||||
|
||||
let nested = base.join("nested").join("ok.png");
|
||||
assert!(ensure_icon_cache_target(&base, nested.to_string_lossy().as_ref()).is_err());
|
||||
assert!(ensure_icon_cache_target(&base, "../ok.png").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_svg_accepts_plain_svg() {
|
||||
assert!(looks_like_svg(br#"<svg xmlns="http://www.w3.org/2000/svg"></svg>"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_svg_accepts_xml_prefixed_svg() {
|
||||
assert!(looks_like_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"></svg>"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_svg_accepts_doctype_svg() {
|
||||
assert!(looks_like_svg(
|
||||
br#"<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"><svg xmlns="http://www.w3.org/2000/svg"></svg>"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_svg_accepts_bom_and_leading_whitespace() {
|
||||
assert!(looks_like_svg(
|
||||
b"\xEF\xBB\xBF \n\t<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_svg_rejects_non_svg_payloads() {
|
||||
assert!(!looks_like_svg(br#"{"status":"ok"}"#));
|
||||
assert!(!looks_like_svg(br"text/plain"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_html_detects_common_html_prefixes() {
|
||||
assert!(looks_like_html(br"<!DOCTYPE html><html></html>"));
|
||||
assert!(looks_like_html(br"<html><body>oops</body></html>"));
|
||||
assert!(looks_like_html(br"<head><title>oops</title></head>"));
|
||||
assert!(looks_like_html(
|
||||
b"\xEF\xBB\xBF \n\t<!DOCTYPE HTML><html><body>oops</body></html>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_supported_icon_content_rejects_html_and_accepts_svg() {
|
||||
assert!(!is_supported_icon_content(br"<!DOCTYPE html><html></html>"));
|
||||
assert!(is_supported_icon_content(
|
||||
br#"<svg xmlns="http://www.w3.org/2000/svg"></svg>"#
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,15 +137,3 @@ pub async fn copy_clash_env() {
|
||||
logging!(error, Type::ProxyMode, "Failed to write to clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::toggle_operation;
|
||||
use crate::core::notification::FailedOperation;
|
||||
|
||||
#[test]
|
||||
fn a_failed_toggle_records_which_way_the_user_was_asking() {
|
||||
assert_eq!(toggle_operation(true), FailedOperation::SystemProxyEnable);
|
||||
assert_eq!(toggle_operation(false), FailedOperation::SystemProxyDisable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,19 +103,3 @@ pub async fn notify_event<'a>(event: NotificationEvent<'a>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::needs_system_notification;
|
||||
use crate::utils::window_manager::WindowState;
|
||||
|
||||
#[test]
|
||||
fn a_system_notification_is_sent_exactly_where_the_window_cannot_speak() {
|
||||
assert!(needs_system_notification(WindowState::Hidden));
|
||||
assert!(needs_system_notification(WindowState::Minimized));
|
||||
assert!(needs_system_notification(WindowState::NotExist));
|
||||
|
||||
assert!(!needs_system_notification(WindowState::VisibleFocused));
|
||||
assert!(!needs_system_notification(WindowState::VisibleUnfocused));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,26 +184,3 @@ pub fn reload_main_window_if_needed() {
|
||||
logging!(warn, Type::Window, "重载页面失败: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::restored_window_size_is_too_small;
|
||||
|
||||
#[test]
|
||||
fn restored_window_size_rejects_zero_dimensions() {
|
||||
assert!(restored_window_size_is_too_small(0, 700));
|
||||
assert!(restored_window_size_is_too_small(940, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_window_size_rejects_dimensions_below_minimum() {
|
||||
assert!(restored_window_size_is_too_small(519, 700));
|
||||
assert!(restored_window_size_is_too_small(940, 519));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_window_size_accepts_minimum_or_larger_dimensions() {
|
||||
assert!(!restored_window_size_is_too_small(520, 520));
|
||||
assert!(!restored_window_size_is_too_small(940, 700));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,28 +21,3 @@ macro_rules! singleton {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
struct TestStruct {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn new() -> Self {
|
||||
Self { value: 42 }
|
||||
}
|
||||
}
|
||||
|
||||
singleton!(TestStruct, TEST_INSTANCE);
|
||||
|
||||
#[test]
|
||||
fn test_singleton_macro() {
|
||||
let instance1 = TestStruct::global();
|
||||
let instance2 = TestStruct::global();
|
||||
|
||||
assert_eq!(instance1.value, 42);
|
||||
assert_eq!(instance2.value, 42);
|
||||
assert!(std::ptr::eq(instance1, instance2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,31 +30,3 @@ pub fn format_bytes_per_second(bytes_per_sec: u64) -> String {
|
||||
format!("{:.0}{}", value.round(), SPEED_UNITS[unit_index])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::format_bytes_per_second;
|
||||
|
||||
#[test]
|
||||
fn format_handles_byte_boundaries() {
|
||||
assert_eq!(format_bytes_per_second(0), "0B/s");
|
||||
assert_eq!(format_bytes_per_second(999), "999B/s");
|
||||
assert_eq!(format_bytes_per_second(1000), "1.0K/s");
|
||||
assert_eq!(format_bytes_per_second(1024), "1.0K/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_handles_decimal_and_integer_rules() {
|
||||
assert_eq!(format_bytes_per_second(9 * 1024), "9.0K/s");
|
||||
assert_eq!(format_bytes_per_second(10 * 1024 - 1), "10K/s");
|
||||
assert_eq!(format_bytes_per_second(10 * 1024), "10K/s");
|
||||
assert_eq!(format_bytes_per_second(123 * 1024), "123K/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_handles_unit_promotion_after_rounding() {
|
||||
assert_eq!(format_bytes_per_second(999 * 1024 + 512), "1.0M/s");
|
||||
assert_eq!(format_bytes_per_second(1024 * 1024), "1.0M/s");
|
||||
assert_eq!(format_bytes_per_second(1536 * 1024), "1.5M/s");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,23 +63,3 @@ fn append_error(path: &Path, detail: &str) -> Result<()> {
|
||||
file.flush().context("failed to flush startup log")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::append_error;
|
||||
|
||||
#[test]
|
||||
fn startup_log_is_created_and_appended() -> anyhow::Result<()> {
|
||||
let root = std::env::temp_dir().join(format!("startup-error-log-{}", std::process::id()));
|
||||
let path = root.join("nested/startup.log");
|
||||
|
||||
append_error(&path, "first failure")?;
|
||||
append_error(&path, "second failure")?;
|
||||
|
||||
let content = std::fs::read_to_string(&path)?;
|
||||
assert!(content.contains("ERROR [Startup] first failure"));
|
||||
assert!(content.contains("ERROR [Startup] second failure"));
|
||||
std::fs::remove_dir_all(root)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user