Clarify active Windows client architecture
This commit is contained in:
195
apps/windows-client/src-tauri/tests/storage_tests.rs
Normal file
195
apps/windows-client/src-tauri/tests/storage_tests.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
#[path = "../src/activity.rs"]
|
||||
mod activity;
|
||||
#[path = "../src/models.rs"]
|
||||
mod models;
|
||||
#[path = "../src/storage.rs"]
|
||||
mod storage;
|
||||
|
||||
use models::{
|
||||
ActivityEntry, ActivityLevel, ComponentId, ComponentState, ComponentStatus, Profile,
|
||||
ProfileItem, ProfileItemType, Protocol, ProxyProtocol, Target, TargetKind,
|
||||
};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use storage::{backup_path, default_config_root, JsonStorage, StoragePaths};
|
||||
|
||||
#[test]
|
||||
fn storage_defaults_to_programdata_root() {
|
||||
let expected = PathBuf::from(r"C:\ProgramData\VpnProxy");
|
||||
|
||||
assert_eq!(default_config_root(), expected);
|
||||
assert_eq!(StoragePaths::default().root, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrips_profiles_targets_components_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",
|
||||
ActivityLevel::Success,
|
||||
)];
|
||||
|
||||
storage.write_profiles(&profiles).expect("write profiles");
|
||||
storage.write_targets(&targets).expect("write targets");
|
||||
storage
|
||||
.write_components(&components)
|
||||
.expect("write components");
|
||||
storage.write_activity(&activity).expect("write 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_json_falls_back_to_empty_collection() {
|
||||
let root = test_root("invalid-json");
|
||||
let storage = JsonStorage::new(root.clone());
|
||||
storage.ensure_dirs().expect("create storage dirs");
|
||||
fs::write(&storage.paths().profiles_file, "{not valid json").expect("write invalid json");
|
||||
|
||||
assert_eq!(
|
||||
storage.read_profiles().expect("invalid profiles fallback"),
|
||||
Vec::<Profile>::new()
|
||||
);
|
||||
|
||||
cleanup(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_creates_backup_before_overwriting_source_file() {
|
||||
let root = test_root("backup");
|
||||
let storage = JsonStorage::new(root.clone());
|
||||
let first = vec![sample_profile("first")];
|
||||
let second = vec![sample_profile("second")];
|
||||
|
||||
storage.write_profiles(&first).expect("first write");
|
||||
storage.write_profiles(&second).expect("second write");
|
||||
|
||||
let backup = backup_path(&storage.paths().profiles_file);
|
||||
assert!(backup.exists(), "backup file should exist");
|
||||
|
||||
let backup_contents = fs::read_to_string(backup).expect("read backup");
|
||||
let backup_profiles: Vec<Profile> =
|
||||
serde_json::from_str(&backup_contents).expect("backup json");
|
||||
|
||||
assert_eq!(backup_profiles, first);
|
||||
assert_eq!(storage.read_profiles().expect("current profiles"), second);
|
||||
|
||||
cleanup(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn activity_entries_are_sorted_and_capped() {
|
||||
let root = test_root("activity");
|
||||
let storage = JsonStorage::with_activity_limit(root.clone(), 2);
|
||||
|
||||
storage
|
||||
.append_activity(sample_activity(
|
||||
"old",
|
||||
"2026-01-01T10:00:00Z",
|
||||
ActivityLevel::Info,
|
||||
))
|
||||
.expect("append old");
|
||||
storage
|
||||
.append_activity(sample_activity(
|
||||
"new",
|
||||
"2026-01-03T10:00:00Z",
|
||||
ActivityLevel::Success,
|
||||
))
|
||||
.expect("append new");
|
||||
storage
|
||||
.append_activity(sample_activity(
|
||||
"middle",
|
||||
"2026-01-02T10:00:00Z",
|
||||
ActivityLevel::Warning,
|
||||
))
|
||||
.expect("append middle");
|
||||
|
||||
let entries = storage.read_activity().expect("read capped activity");
|
||||
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(
|
||||
entries
|
||||
.iter()
|
||||
.map(|entry| entry.id.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["new", "middle"]
|
||||
);
|
||||
|
||||
cleanup(&root);
|
||||
}
|
||||
|
||||
fn test_root(name: &str) -> PathBuf {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("system clock before unix epoch")
|
||||
.as_nanos();
|
||||
|
||||
std::env::temp_dir().join(format!("vpn-proxy-storage-{name}-{timestamp}"))
|
||||
}
|
||||
|
||||
fn cleanup(root: &Path) {
|
||||
let _ = fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
fn sample_profile(id: &str) -> Profile {
|
||||
Profile {
|
||||
id: id.to_string(),
|
||||
name: format!("Profile {id}"),
|
||||
enabled: true,
|
||||
target_id: "home-gateway".to_string(),
|
||||
protocols: vec![Protocol::Tcp, Protocol::Udp],
|
||||
items: vec![ProfileItem {
|
||||
item_type: ProfileItemType::Process,
|
||||
value: "Discord".to_string(),
|
||||
recursive: false,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_target(id: &str) -> Target {
|
||||
Target {
|
||||
id: id.to_string(),
|
||||
name: "Home Gateway".to_string(),
|
||||
kind: TargetKind::External,
|
||||
protocol: ProxyProtocol::Socks5,
|
||||
host: "192.168.50.111".to_string(),
|
||||
port: 8080,
|
||||
requires_component: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_component() -> ComponentStatus {
|
||||
ComponentStatus {
|
||||
id: ComponentId::Proxyfier,
|
||||
name: "ProxiFyre".to_string(),
|
||||
state: ComponentState::Missing,
|
||||
installed: false,
|
||||
running: false,
|
||||
version: None,
|
||||
path: None,
|
||||
problems: vec!["ProxiFyre не установлен".to_string()],
|
||||
actions: vec!["Установить ProxiFyre".to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_activity(id: &str, at: &str, level: ActivityLevel) -> ActivityEntry {
|
||||
ActivityEntry {
|
||||
id: id.to_string(),
|
||||
at: at.to_string(),
|
||||
level,
|
||||
title: format!("Activity {id}"),
|
||||
message: "Storage test activity".to_string(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user