Refactor VPN proxy routing and session handling

This commit is contained in:
2026-07-07 22:07:17 +03:00
parent 59f2264a2e
commit 7dbf786c56
25 changed files with 1921 additions and 236 deletions

View File

@@ -1,9 +1,9 @@
#[path = "../src/activity.rs"]
mod activity;
#[path = "../src/component_detection.rs"]
mod component_detection;
#[path = "../src/commands.rs"]
mod commands;
#[path = "../src/component_detection.rs"]
mod component_detection;
#[path = "../src/models.rs"]
mod models;
#[path = "../src/adapters/proxifyre.rs"]
@@ -17,9 +17,9 @@ mod validation;
use commands::{
apply_profiles_with_services, build_status, resolve_component_statuses, resolve_preview,
save_profile_to_storage, save_target_to_storage, Clock, CommandError,
DetectedProxyApplyHelper, HelperApplyRequest, HelperApplyResult, ProfileInputDto,
ProfileItemInputDto, ProxyApplyHelper, TargetInputDto,
save_profile_to_storage, save_target_to_storage, Clock, CommandError, DetectedProxyApplyHelper,
HelperApplyRequest, HelperApplyResult, ProfileInputDto, ProfileItemInputDto, ProxyApplyHelper,
TargetInputDto,
};
use component_detection::{
DetectedProxyfier, ProxyfierDetectionHost, ProxyfierEngine, RegistryInstallEntry,
@@ -240,8 +240,8 @@ fn detected_proxy_apply_helper_writes_proxifyre_app_config() {
})
.expect("detected helper should apply");
let applied = fs::read_to_string(install_dir.join("app-config.json"))
.expect("read applied app-config");
let applied =
fs::read_to_string(install_dir.join("app-config.json")).expect("read applied app-config");
assert!(result.success);
assert!(result.changed);
@@ -276,7 +276,9 @@ fn detected_proxy_apply_helper_ignores_plain_proxifier_install() {
assert!(result.success);
assert!(result.changed);
assert_eq!(result.action, "proxifyre.stage-generated-config");
assert!(result.message.contains("совместимая установка ProxiFyre не найдена"));
assert!(result
.message
.contains("совместимая установка ProxiFyre не найдена"));
cleanup(&root);
}
@@ -358,7 +360,10 @@ impl ProxyfierDetectionHost for DetectionHost {
}
fn normalize_path(path: &Path) -> String {
path.display().to_string().replace('/', "\\").to_ascii_lowercase()
path.display()
.to_string()
.replace('/', "\\")
.to_ascii_lowercase()
}
fn test_root(name: &str) -> PathBuf {

View File

@@ -7,7 +7,7 @@ use component_detection::{
detect_proxyfier_install_with_host, proxyfier_component_from_detection, ProxyfierDetectionHost,
ProxyfierEngine, RegistryInstallEntry,
};
use models::{ComponentState};
use models::ComponentState;
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
@@ -25,7 +25,10 @@ fn detects_existing_proxifyre_from_registry_install_location() {
assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre);
assert_eq!(detected.install_dir, PathBuf::from(r"C:\Tools\ProxiFyre"));
assert_eq!(detected.config_path, Some(PathBuf::from(r"C:\Tools\ProxiFyre\app-config.json")));
assert_eq!(
detected.config_path,
Some(PathBuf::from(r"C:\Tools\ProxiFyre\app-config.json"))
);
assert!(detected.running);
let component = proxyfier_component_from_detection(Some(&detected));
@@ -56,7 +59,10 @@ fn env_override_can_point_to_portable_proxifyre_install() {
.expect("env override should be checked before common paths");
assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre);
assert_eq!(detected.executable_path, PathBuf::from(r"D:\Portable\ProxiFyre\ProxiFyre.exe"));
assert_eq!(
detected.executable_path,
PathBuf::from(r"D:\Portable\ProxiFyre\ProxiFyre.exe")
);
}
#[test]
@@ -118,17 +124,16 @@ impl ProxyfierDetectionHost for MockHost {
}
fn path_exists(&self, path: &Path) -> bool {
self.paths.contains(&normalize_path(&path.display().to_string()))
self.paths
.contains(&normalize_path(&path.display().to_string()))
}
fn process_running(&self, process_name: &str) -> bool {
self.processes
.contains(&process_name.to_ascii_lowercase())
self.processes.contains(&process_name.to_ascii_lowercase())
}
fn service_running(&self, service_name: &str) -> bool {
self.services
.contains(&service_name.to_ascii_lowercase())
self.services.contains(&service_name.to_ascii_lowercase())
}
fn registry_install_entries(&self) -> Vec<RegistryInstallEntry> {

View File

@@ -124,7 +124,5 @@ fn rejects_malformed_target_fields() {
assert!(error.iter().any(|item| item.field == "host"));
assert!(error.iter().any(|item| item.field == "port"));
assert!(error.iter().any(|item| item.field == "protocol"));
assert!(error
.iter()
.any(|item| item.field == "requires_component"));
assert!(error.iter().any(|item| item.field == "requires_component"));
}

View File

@@ -1,9 +1,9 @@
#[path = "../src/models.rs"]
mod models;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
#[path = "../src/adapters/proxifyre.rs"]
mod proxifyre;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
use models::{
ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol,
@@ -33,7 +33,10 @@ fn generates_proxifyre_config_for_discord_external_socks5_target() {
assert!(config.bypass_lan);
assert_eq!(config.proxies.len(), 1);
assert_eq!(config.proxies[0].app_names, vec!["Discord"]);
assert_eq!(config.proxies[0].socks5_proxy_endpoint, "192.168.50.111:8080");
assert_eq!(
config.proxies[0].socks5_proxy_endpoint,
"192.168.50.111:8080"
);
assert_eq!(config.proxies[0].supported_protocols, vec!["TCP", "UDP"]);
}
@@ -57,6 +60,32 @@ fn skips_disabled_profiles_when_generating_proxifyre_config() {
assert!(config.proxies.is_empty());
}
#[test]
fn includes_folder_paths_when_generating_proxifyre_config() {
let adapter = ProxiFyreAdapter::default();
let mut profile = discord_profile("home-gateway");
profile.items.push(ProfileItem {
item_type: ProfileItemType::Folder,
value: r"C:\Games\MyGame".to_string(),
recursive: true,
});
let profiles = vec![profile];
let targets = vec![external_socks5_target()];
let components = Vec::new();
let generated = adapter
.generate_config(ProxyRouterRequest::new(&profiles, &targets, &components))
.expect("folder paths should be accepted by ProxiFyre config generation");
let config: ProxiFyreConfig =
serde_json::from_str(&generated.contents).expect("generated config json");
assert_eq!(generated.routed_apps, 2);
assert_eq!(
config.proxies[0].app_names,
vec!["Discord", r"C:\Games\MyGame"]
);
}
#[test]
fn blocks_local_singbox_target_when_required_component_is_missing() {
let adapter = ProxiFyreAdapter::default();

View File

@@ -1,9 +1,9 @@
#[path = "../src/models.rs"]
mod models;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
#[path = "../src/adapters/proxifyre.rs"]
mod proxifyre;
#[path = "../src/adapters/proxy_router.rs"]
mod proxy_router;
#[path = "../src/adapters/singbox.rs"]
mod singbox;
@@ -14,8 +14,8 @@ use models::{
use proxifyre::{ProxiFyreAdapter, ProxiFyreConfig};
use proxy_router::{ProxyRouterAdapter, ProxyRouterRequest};
use singbox::{
SingBoxAdapter, SingBoxCheckResult, SingBoxConfig, SingBoxConfigChecker,
SingBoxConfigError, SingBoxConfigErrorKind, SingBoxGenerationRequest, SINGBOX_OUTPUT_FILE,
SingBoxAdapter, SingBoxCheckResult, SingBoxConfig, SingBoxConfigChecker, SingBoxConfigError,
SingBoxConfigErrorKind, SingBoxGenerationRequest, SINGBOX_OUTPUT_FILE,
};
use std::{
cell::RefCell,
@@ -116,7 +116,10 @@ fn blocks_local_singbox_config_when_component_is_not_running() {
)
.expect_err("local sing-box target requires running component");
assert_eq!(error.kind, SingBoxConfigErrorKind::RequiredComponentNotRunning);
assert_eq!(
error.kind,
SingBoxConfigErrorKind::RequiredComponentNotRunning
);
assert!(checker.calls.borrow().is_empty());
}
@@ -129,11 +132,7 @@ fn propagates_failed_singbox_check_as_structured_error() {
let error = adapter
.generate_config(
SingBoxGenerationRequest::new(
&targets,
&components,
Some(Path::new("sing-box.exe")),
),
SingBoxGenerationRequest::new(&targets, &components, Some(Path::new("sing-box.exe"))),
&checker,
)
.expect_err("failed sing-box check should block generated config");
@@ -156,7 +155,10 @@ fn external_proxifyre_apply_does_not_require_singbox_component() {
serde_json::from_str(&generated.contents).expect("generated proxifyre json");
assert_eq!(config.proxies.len(), 1);
assert_eq!(config.proxies[0].socks5_proxy_endpoint, "192.168.50.111:8080");
assert_eq!(
config.proxies[0].socks5_proxy_endpoint,
"192.168.50.111:8080"
);
}
struct RecordingChecker {

View File

@@ -45,7 +45,10 @@ fn roundtrips_profiles_targets_components_and_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_components().expect("read components"),
components
);
assert_eq!(storage.read_activity().expect("read activity"), activity);
cleanup(&root);