Release v2.0.0
CI / Windows baseline (push) Canceled after 0s

This commit is contained in:
2026-09-10 20:59:52 +03:00
parent 9c987df6e9
commit efda8eb98f
142 changed files with 68308 additions and 9333 deletions
+31 -35
View File
@@ -1,7 +1,6 @@
use proxywarden_lib::models::{
ActivityEntry, ActivityLevel, ComponentId, ComponentState, ComponentStatus, LocalSingBoxConfig,
Profile, ProfileItem, ProfileItemType, Protocol, ProxyProtocol, SubscriptionCache,
SubscriptionServer, Target, TargetKind,
ActivityEntry, ActivityLevel, LocalSingBoxConfig, Profile, ProfileItem, ProfileItemType,
Protocol, ProxyProtocol, SubscriptionCache, SubscriptionServer, Target, TargetKind,
};
use proxywarden_lib::storage::{backup_path, default_config_root, JsonStorage, StoragePaths};
use std::fs;
@@ -17,13 +16,12 @@ fn storage_defaults_to_programdata_root() {
}
#[test]
fn roundtrips_profiles_targets_components_and_activity() {
fn roundtrips_profiles_targets_and_activity() {
let root = test_root("roundtrip");
let storage = JsonStorage::new(root.clone());
let profiles = vec![sample_profile("discord")];
let targets = vec![sample_target("home-gateway")];
let components = vec![sample_component()];
let activity = vec![sample_activity(
"created",
"2026-01-01T10:00:00Z",
@@ -32,15 +30,10 @@ fn roundtrips_profiles_targets_components_and_activity() {
storage.write_profiles(&profiles).expect("write profiles");
storage.write_targets(&targets).expect("write targets");
write_json(&storage.paths().components_file, &components);
write_json(&storage.paths().activity_file, &activity);
assert_eq!(storage.read_profiles().expect("read profiles"), profiles);
assert_eq!(storage.read_targets().expect("read targets"), targets);
assert_eq!(
storage.read_components().expect("read components"),
components
);
assert_eq!(storage.read_activity().expect("read activity"), activity);
cleanup(&root);
@@ -86,6 +79,9 @@ fn roundtrips_local_singbox_config_and_subscription_cache() {
config.subscription_display_url(),
Some("https://sub.example.test/...".to_string())
);
let persisted = fs::read_to_string(&storage.paths().local_singbox_file)
.expect("read persisted local sing-box config");
assert!(!persisted.contains("install_root"));
cleanup(&root);
}
@@ -155,13 +151,13 @@ fn reads_percent_encoded_singbox_tags_as_utf8() {
assert_eq!(config.selected_server_tag, Some(decoded_tag.to_string()));
assert_eq!(cache.servers[0].tag, decoded_tag);
assert_eq!(cache.config["outbounds"][0]["tag"], decoded_tag);
assert_eq!(cache.config["outbounds"][0]["tag"], encoded_tag);
cleanup(&root);
}
#[test]
fn invalid_subscription_cache_without_backup_returns_error_and_moves_corrupt_file() {
fn invalid_subscription_cache_without_backup_preserves_corruption_across_repeated_reads() {
let root = test_root("invalid-subscription-cache");
let storage = JsonStorage::new(root.clone());
fs::create_dir_all(&storage.paths().state_dir).expect("create state dir");
@@ -176,16 +172,14 @@ fn invalid_subscription_cache_without_backup_returns_error_and_moves_corrupt_fil
.expect_err("invalid cache should not silently fallback");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
assert!(!storage.paths().singbox_subscription_cache_file.exists());
assert!(has_corrupt_sibling(
&storage.paths().singbox_subscription_cache_file
));
assert!(storage.paths().singbox_subscription_cache_file.exists());
assert!(storage.read_singbox_subscription_cache().is_err());
cleanup(&root);
}
#[test]
fn invalid_json_without_backup_returns_error_and_moves_corrupt_file() {
fn invalid_json_without_backup_preserves_corruption_across_repeated_reads() {
let root = test_root("invalid-json-no-backup");
let storage = JsonStorage::new(root.clone());
fs::create_dir_all(&storage.paths().config_dir).expect("create config dir");
@@ -196,8 +190,8 @@ fn invalid_json_without_backup_returns_error_and_moves_corrupt_file() {
.expect_err("invalid profiles should not silently fallback");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
assert!(!storage.paths().profiles_file.exists());
assert!(has_corrupt_sibling(&storage.paths().profiles_file));
assert!(storage.paths().profiles_file.exists());
assert!(storage.read_profiles().is_err());
cleanup(&root);
}
@@ -308,6 +302,24 @@ fn cleanup(root: &Path) {
let _ = fs::remove_dir_all(root);
}
#[test]
fn missing_primary_restores_valid_backup_but_never_defaults_over_invalid_backup() {
let root = test_root("missing-source-backup");
let storage = JsonStorage::new(root.clone());
let saved = vec![sample_profile("preserved")];
storage.write_profiles(&saved).unwrap();
storage.write_profiles(&[]).unwrap();
fs::remove_file(&storage.paths().profiles_file).unwrap();
assert_eq!(storage.read_profiles().unwrap(), saved);
assert_eq!(storage.read_profiles().unwrap(), saved);
fs::remove_file(&storage.paths().profiles_file).unwrap();
fs::write(backup_path(&storage.paths().profiles_file), "{broken").unwrap();
assert!(storage.read_profiles().is_err());
assert!(storage.read_profiles().is_err());
assert!(!storage.paths().profiles_file.exists());
cleanup(&root);
}
fn write_json<T: serde::Serialize + ?Sized>(path: &Path, value: &T) {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create json parent dir");
@@ -363,22 +375,6 @@ fn sample_target(id: &str) -> Target {
}
}
fn sample_component() -> ComponentStatus {
ComponentStatus {
id: ComponentId::Proxyfier,
name: "ProxiFyre".to_string(),
state: ComponentState::Missing,
installed: false,
running: false,
version: None,
path: None,
service_name: Some("ProxiFyreService".to_string()),
service_status: None,
problems: vec!["ProxiFyre не установлен".to_string()],
actions: vec!["Установить ProxiFyre".to_string()],
}
}
fn sample_subscription_cache() -> SubscriptionCache {
SubscriptionCache {
config: serde_json::json!({