@@ -105,6 +105,38 @@ fn trusted_check_persists_redacted_state_recovers_backup_and_reports_stale() {
|
||||
.expect("restored state must be JSON");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_multi_platform_release_selects_only_the_exact_windows_asset() {
|
||||
let workspace = TestWorkspace::new();
|
||||
let service = workspace.service();
|
||||
let component = catalog_component(&service, ComponentId::SingBox);
|
||||
let response = metadata_response(
|
||||
&component,
|
||||
"1.14.0",
|
||||
"sing-box-1.14.0-windows-amd64.zip",
|
||||
b"asset",
|
||||
DigestMode::Trusted,
|
||||
);
|
||||
let mut metadata: Value = serde_json::from_slice(&response.body).unwrap();
|
||||
// Real sing-box 1.14.0 publishes 167 assets. Response bytes are already bounded.
|
||||
for index in 1..167 {
|
||||
metadata["assets"].as_array_mut().unwrap().push(json!({
|
||||
"id": 1000 + index,
|
||||
"name": format!("other-platform-{index}.zip"),
|
||||
"size": 10,
|
||||
"browser_download_url": format!("https://github.com/SagerNet/sing-box/releases/download/v1.14.0/other-platform-{index}.zip")
|
||||
}));
|
||||
}
|
||||
let transport = MockTransport::new(vec![ResponseSpec::ok(
|
||||
serde_json::to_vec(&metadata).unwrap(),
|
||||
)]);
|
||||
let checked = service
|
||||
.check_for_update(ComponentId::SingBox, CHECKED_AT, &transport)
|
||||
.unwrap();
|
||||
assert_eq!(checked.latest_known_version, "1.14.0");
|
||||
assert_eq!(checked.trust, UpdateCheckTrust::Trusted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_malformed_digest_is_observable_and_never_downloaded() {
|
||||
for (mode, expected) in [
|
||||
|
||||
@@ -38,6 +38,26 @@ fn interrupted_commit_restores_primary_and_backup_before_next_read() {
|
||||
assert!(fixture.storage.read_profiles().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rollback_leaves_unchanged_files_in_place() {
|
||||
let fixture = Fixture::new();
|
||||
let path = &fixture.storage.paths().profiles_file;
|
||||
let transaction = ConfigurationTransaction::begin(&fixture.storage, None).unwrap();
|
||||
// Deny replacement on Windows, while allowing the recovery code to read.
|
||||
let mut options = fs::OpenOptions::new();
|
||||
options.read(true);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
options.share_mode(1);
|
||||
}
|
||||
let held = options.open(path).unwrap();
|
||||
let modified = held.metadata().unwrap().modified().unwrap();
|
||||
transaction.abort().unwrap();
|
||||
assert_eq!(fs::metadata(path).unwrap().modified().unwrap(), modified);
|
||||
assert!(read_guard(&fixture.storage).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_lock_rejects_second_writer_and_reader() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
//! Explicit checks of real saved data/network. Never runs automatically in CI.
|
||||
#![cfg(all(windows, debug_assertions))]
|
||||
|
||||
use proxywarden_lib::{
|
||||
component_catalog::ComponentId,
|
||||
component_packages::{
|
||||
ComponentPackageService, NativePackageSignatureVerifier, ReqwestUpdateTransport,
|
||||
},
|
||||
configuration_transaction::read_guard,
|
||||
privileged_jobs::{EpochClock, SystemEpochClock},
|
||||
storage::{default_config_root, JsonStorage, StoragePaths},
|
||||
subscription::{fetch_subscription_with_identity, SubscriptionFetchIdentity},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
#[ignore = "Real user data/network; requires an explicitly authorized operator action"]
|
||||
fn installed_singbox_data_check() {
|
||||
let action = std::env::var("PROXYWARDEN_LIVE_SINGBOX_ACTION")
|
||||
.expect("set the explicitly authorized action");
|
||||
let storage = JsonStorage::new(default_config_root());
|
||||
match action.as_str() {
|
||||
"recover" => {
|
||||
let _guard = read_guard(&storage).expect("recover saved configuration");
|
||||
println!("Configuration recovery succeeded");
|
||||
}
|
||||
"subscription" => {
|
||||
let config = {
|
||||
let _guard = read_guard(&storage).expect("read saved configuration");
|
||||
storage
|
||||
.read_local_singbox_config()
|
||||
.expect("saved subscription settings")
|
||||
};
|
||||
let identity =
|
||||
SubscriptionFetchIdentity::with_device_hwid(config.device_hwid.as_deref());
|
||||
let result = fetch_subscription_with_identity(
|
||||
config
|
||||
.subscription_url
|
||||
.as_deref()
|
||||
.expect("saved subscription exists"),
|
||||
&identity,
|
||||
);
|
||||
// Never include the subscription URL, response body, credentials or server names.
|
||||
assert!(result.is_ok(), "Saved subscription fetch/parse failed");
|
||||
let cache = result.unwrap();
|
||||
assert!(
|
||||
!cache.servers.is_empty(),
|
||||
"subscription returned no servers"
|
||||
);
|
||||
println!(
|
||||
"Saved subscription fetched and parsed: {} servers",
|
||||
cache.servers.len()
|
||||
);
|
||||
}
|
||||
"download" => {
|
||||
let packages = ComponentPackageService::open(
|
||||
PathBuf::from(r"C:\Program Files\ProxyWarden\bundled\components"),
|
||||
&StoragePaths::default(),
|
||||
)
|
||||
.expect("installed offline catalog");
|
||||
let transport = ReqwestUpdateTransport::new().expect("update transport");
|
||||
let checked = packages
|
||||
.check_for_update(
|
||||
ComponentId::SingBox,
|
||||
SystemEpochClock.now_epoch_seconds(),
|
||||
&transport,
|
||||
)
|
||||
.expect("release metadata and independent digest");
|
||||
println!(
|
||||
"Latest release: {}; trust {:?}",
|
||||
checked.latest_known_version, checked.trust
|
||||
);
|
||||
let package = packages
|
||||
.download_checked_update(
|
||||
ComponentId::SingBox,
|
||||
&transport,
|
||||
&NativePackageSignatureVerifier,
|
||||
)
|
||||
.expect("verified download into application cache");
|
||||
println!("Downloaded and verified sing-box {}", package.version);
|
||||
}
|
||||
_ => panic!("unsupported explicit data-check action"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user