196 lines
5.7 KiB
Rust
196 lines
5.7 KiB
Rust
#[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(),
|
|
}
|
|
}
|